- PVSM.RU - https://www.pvsm.ru -
JavaScript. Одновременно пугающий и притягательный. Я уверен, если бы Пабло Пикассо был программистом, то он создал именно этот язык. Null здесь является объектом, пустым массивом, он равен false, а функции летают рядом с ним как теннисные мячики.
Целевая аудитория статьи — продвинутые разработчики, которые хотят узнать больше о JavaScript. Это коллекция странностей и хорошо хранимых секретов. Секреты некоторых глав могут пригодиться вам при разработке собственных программных продуктов, другие просто являются предметами для смеха и осуждения. В любом случае, мы начинаем!
Давайте начнем с известной всем странности. Null — объект, причем самый высший из них. Null? Объект? «Этого не может быть, так как null — это полное отсутствие какого-либо значения», — скажете вы. И вы, несомненно, правы. Однако, как бы то ни было, у меня есть доказательство. Вот оно:
alert (typeof null); //выводит 'object'
Несмотря на это, null не является экземпляром object. (Если вы не знали, значения в JavaScript являются экземплярами базового объекта. Каждое число является экземпляром объекта Number, каждый объект является экземпляром объекта object и так далее.) Это возвращает нам разум, ведь null — отсутствие значения, значит он не может быть экземпляром чего-либо. Так следующая строка выводит false:
alert (null instanceof Object); //false
Вы удивлены, что null — это объект? Тогда подумайте вот о чем — NaN («Not a Number» — не число) является числом! Чем дальше, тем страшнее! NaN даже не равен самому себе. (Ваша голова еще не болит?)
alert (typeof NaN); //выводит 'Number'
alert (NaN === NaN); //выводит false
По факту, NaN не равен вообще ничему. Единственный способ сравнить что-нибудь с NaN — функция isNaN().
Вот и другая странность JavaScript:
alert (new Array() == false); //выводит true
Чтобы понять, что здесь происходит, вы должны принять концепцию правдочки и кривдочки (truthy and falsy). Есть «облегченные» виды true и false, которые могут вас подвести, если вы углубитесь в логику и философию.
Я прочитал много определений, что такое правдочка и кривдочка, и простейшее, по моему мнению, звучит так: в JavaScript каждый объект имеет встроенное булевское значение, которое вызывается, когда объект должен вести себя как булевский; например, когда вы сравниваете его с false.
Так как яблоки нельзя сравнивать с жемчугом, когда JavaScript вызывает сравнение объектов разных типов, он выполняет их приведение к общему типу. False, zero, null, undefined, пустые строки и NaN — все приводится к false — не постоянно, а только в контексте данного выражения. Вот пример:
var someVar = 0;
alert (someVar == false); //выводит false
Здесь мы запрашиваем сравнение числа 0 и значения false. Так как данные не являются однотипными, JavaScript неявно преобразует их значения к значениям встроенных булевских флажков, который (в случае с нулем) равен кривдочке.
Вы могли заметить, что я не включал пустые массивы в список объектов, приводящихся к false. Они ведут себя очень интересно — они вычисляются как правдочка, но, при сравнении с булевским значением, ведут себя как кривдочка. Вы еще живы? Хорошо, потому что я подготовил для вас еще один пример:
var someVar = []; //пустой массив
alert (someVar == false); //выводит true
if (someVar) alert ('hello'); //выводит 'hello', значит someVar является true
Чтобы избежать приведения, вы можете использовать оператор сравнения типа и значения === (вместо ==, который сравнивает только значения.)
var someVar = 0;
alert (someVar == 0); // выводит true, 0 - кривдочка
alert (someVar === 0); // выводит false, 0 - число, а не булевское значение
Фух. Как вы уже поняли, приведение типов в JavaScript — довольно сложная тема, так что вам стоит посвятить еще пару вечеров на изучение данной темы.
Вы можете пообсуждать этот вопрос со мной здесь(eng.) [1] или, если вы уже достаточно готовы, то можете прочитать точный процесс приведения, он описан в документации [2].
Это один из самых секретных секретов JavaScript, он пришел к нам в версии 1.3. Большинство использований replace выглядит как-то так:
alert('10 13 21 48 52'.replace(/d+/g, '*')); //заменить все числа на *
Это простая замена, число — звездочка. Но что, если мы хотим получить возможность самому выбрать место для замены? Что если мы хотим заменить только те числа, которые меньше 30? Это может быть достигнуто с помощью того же метода replace. Мы просто должны вызвать callback функцию при каждом совпадении:
alert('10 13 21 48 52'.replace(/d+/g, function(match) {
return parseInt(match) < 30 ? '*' : match;
}));
Для каждого совпадения JavaScript будет вызывать нашу функцию, передавая наше совпадение в качестве параметра. Потом, мы вернем звездочку (если число меньше 30) или параметр без изменений.
Многие продвинутые JavaScript разработчики используют только match и replace при работе с регулярными выражениями. Но JavaScript дает нам больше возможностей, чем просто 2 метода.
Наибольший интерес представляет test(), которые работает как match, но не возвращает совпадение, он просто проверяет вхождение паттерна в строку.
alert (/w{3,}/.test('Hello')); //выводит 'true'
Код сверху ищет в строке 3 и более буквенно-числовых символа и, так как строка Hello соответствует этим требованиям, мы получаем true. Нам не нужно само совпадение, только результат.
Также, объект RegExp, с помощью которого вы можете создавать динамические регулярные выражения, противоположен статичному. Это дает преимущество в виде короткой записи (мы просто окружили паттерн слэшами). Но, в данном случае, вы не можете указывать на переменные, так что создание динамических паттернов невозможно. С RegExp() вы можете это сделать
function findWord(word, string) {
var instancesOfWord = string.match(new RegExp('b'+word+'b', 'ig'));
alert(instancesOfWord);
}
findWord('car', 'Carl went to buy a car but had forgotten his credit card.');
Здесь мы создаем динамический паттерн, который основан на значении аргумента word. Функция возвращает число нахождения слова word в строке целиком (не частью другого слова). Так, наш пример вернет car однажды, игнорируя это слово, входящее в Carl и card. Мы делаем это с помощью флага b.
Так как RegExp определен как строка, а не с помощью короткого синтаксиса, мы можем использовать переменные при построении паттерна. Это означает, что мы должны дважды экранировать любые специальные символы, так как мы сделали с символом границы слова.
Контекст, в котором что-то выполняется, определяет, какие переменные доступны. Свободный JavaScript (тот, который запускается не из функции) оперирует с глобальным контекстом объекта window, к которому всё имеет доступ, а локальные переменные, которые объявлены внутри функции доступны только внутри этой функции, не снаружи.
var animal = 'dog';
function getAnimal(adjective) { alert(adjective+' '+this.animal); }
getAnimal('lovely'); //выводит 'lovely dog';
В этом примере наша переменная и функция объявлены в глобальном контексте. Так как он всегда указывает на текущий контекст, в этом пример он указывает на window. Поэтому, когда функция ищет window.animal, она находит его. Пока все нормально. Но, мы можем заставить нашу функцию думать, что она запускается в другом контексте, не смотря на родной контекст. Мы можем провернуть это с помощью метода call(), а не с помощью функции.
var animal = 'dog';
function getAnimal(adjective) { alert(adjective+' '+this.animal); };
var myObj = {animal: 'camel'};
getAnimal.call(myObj, 'lovely'); //выводит 'lovely camel'
Здесь наша функция вызывается не в контексте window, а в контексте myObj как аргумент метода call(). Call() делает вид, что функция является методом myObj. Заметьте, что любые аргументы, которые мы передаем в call() после первого будут переданы в функцию.
Я слышал разработчиков, которые говорили, что они программируют многие годы и никогда не использовали это, потому что это ненормальное поведение кода. Хотя, это довольно интересно.
Кстати, apply() выполняет ту же работу, что и call(), кроме того, что аргументы передаются как массив. Вот пример:
getAnimal.apply(myObj, ['lovely']); //аргументы передаются массивом
Ничего не запрещает этого:
(function() { alert('hello'); })(); //выводит 'hello'
Синтаксис очень прост: мы объявляем функцию и сразу же вызываем ее как и другие функции, с помощью скобок. Вы можете спросить: «Зачем нам использовать это?» Это может выглядеть как несоответствие терминов: функция обычно содержит код, который мы хотим выполнить позже, не сейчас, иначе, мы не помещаем его в функцию.
Одно из преимуществ само-исполняющихся инструкций (СИИ) — использование текущих значений переменных внутри отложенного кода. Вот проблема:
var someVar = 'hello';
setTimeout(function() { alert(someVar); }, 1000);
var someVar = 'goodbye';
Новички на форумах спрашивают, почему alert выводит goodbye, а не hello. Ответ в том, что значение переменной someVar не вычисляется до запуска кода. А к тому времени, переменная уже перезаписана значением goodbye.
СИИ дают возможность решить эту проблему. Вместо использования timeout callback, мы возвращаем его из СИИ, в который мы передаем текущее значение переменной в качестве аргумента. Это означает, что мы передаем и изолируем текущее значение переменной, защищая его от того, что происходит с настоящей переменной someVar. Это как сделать фотографию автомобиля перед перекраской, фото не обновится, оно всегда будет показывать цвет машины на момент взятия фотографии.
var someVar = 'hello';
setTimeout((function(someVar) {
return function() { alert(someVar); }
})(someVar), 1000);
var someVar = 'goodbye';
Теперь, мы видим hello, как мы и хотели, потому что функция показывает изолированную версию переменной.
Я никогда не мог понять, почему Mozilla делает это. Конечно, я должен показать пример:
Hello, world!
<script>
var ie = navigator.appVersion.indexOf('MSIE') != -1;
var p = document.getElementById('somePara');
alert(ie ? p.currentStyle.color : getComputedStyle(p, null).color);
</script>
Когда большинство браузеров возвращает ff9900, Firefox вернет rgb(255, 153, 0) эквивалент. Есть функции, которые конвертируют RGB в HEX.
Как вы могли заметить, у IE (ненавижу тебя — прим. пер.) есть иные методы определения вычисленных стилей.
Эта странность встречается не только в JavaScript, это проблема всей компьютерной науки. Вывод будет такой — 0.30000000000000004.
Эта проблема называется компьютерной неточностью. Когда JavaScript выполняет строку, он переводит значения в их двоичный эквивалент.
Вот где проблема начинается — 0.1 не 0.1 в двоичном эквиваленте, но близкое к этому значение. Это как перевести текст с русского на белорусский. Похоже, но не одинаково.
Остальное выходит за рамки этой статьи (у нас тут не математический кружок)
Данная тема широко рассматривается на форумах о компьютерной науке и разработке. Вы можете предложить свои пути решения этой проблемы.
Хорошо, давайте закончим глупостью. Это странно звучит, но undefined — незарезервированное слово в JavaScript, хотя и имеет особое значение — показать, что переменная не определена.
var someVar;
alert(someVar == undefined); //выводит true
Пока все в порядке. Но:
undefined = "I'm not undefined!";
var someVar;
alert(someVar == undefined); //выводит false!
Вы можете обратиться к списку всех зарезервированных слов JavaScript [3].
Автор: paththeir
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/28478
Ссылки в тексте:
[1] здесь(eng.): http://www.mitya.co.uk/blog/2011/Apr/Twisted-logic-understanding-truthy-and-falsy-174
[2] документации: http://www.mozilla.org/js/language/E262-3.pdf
[3] списку всех зарезервированных слов JavaScript: https://developer.mozilla.org/en/JavaScript/Reference/Reserved_Words
[4] Источник: http://habrahabr.ru/post/171359/
Нажмите здесь для печати.