- PVSM.RU - https://www.pvsm.ru -

10 странностей и секретов JavaScript

JavaScript. Одновременно пугающий и притягательный. Я уверен, если бы Пабло Пикассо был программистом, то он создал именно этот язык. Null здесь является объектом, пустым массивом, он равен false, а функции летают рядом с ним как теннисные мячики.

Целевая аудитория статьи — продвинутые разработчики, которые хотят узнать больше о JavaScript. Это коллекция странностей и хорошо хранимых секретов. Секреты некоторых глав могут пригодиться вам при разработке собственных программных продуктов, другие просто являются предметами для смеха и осуждения. В любом случае, мы начинаем!

Типы данных и объявления

1. Null — это объект

Давайте начнем с известной всем странности. Null — объект, причем самый высший из них. Null? Объект? «Этого не может быть, так как null — это полное отсутствие какого-либо значения», — скажете вы. И вы, несомненно, правы. Однако, как бы то ни было, у меня есть доказательство. Вот оно:

alert (typeof null); //выводит 'object'

Несмотря на это, null не является экземпляром object. (Если вы не знали, значения в JavaScript являются экземплярами базового объекта. Каждое число является экземпляром объекта Number, каждый объект является экземпляром объекта object и так далее.) Это возвращает нам разум, ведь null — отсутствие значения, значит он не может быть экземпляром чего-либо. Так следующая строка выводит false:

alert (null instanceof Object); //false
2. NaN — это число

Вы удивлены, что null — это объект? Тогда подумайте вот о чем — NaN («Not a Number» — не число) является числом! Чем дальше, тем страшнее! NaN даже не равен самому себе. (Ваша голова еще не болит?)

alert (typeof NaN); //выводит 'Number'
alert (NaN === NaN); //выводит false

По факту, NaN не равен вообще ничему. Единственный способ сравнить что-нибудь с NaN — функция isNaN().

3. Массив без ключей == ложь (или о правдочке и кривдочке)

Вот и другая странность 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].

Регулярные выражения

4. Метод replace() может принимать callback функцию

Это один из самых секретных секретов 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) или параметр без изменений.

5. Регулярные выражения — больше чем найти и заменить

Многие продвинутые 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 определен как строка, а не с помощью короткого синтаксиса, мы можем использовать переменные при построении паттерна. Это означает, что мы должны дважды экранировать любые специальные символы, так как мы сделали с символом границы слова.

Функции и контексты

6. Вы можете подделать контекст

Контекст, в котором что-то выполняется, определяет, какие переменные доступны. Свободный 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']); //аргументы передаются массивом
7. Функции могут вызывать сами себя

Ничего не запрещает этого:

(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, как мы и хотели, потому что функция показывает изолированную версию переменной.

Браузер

8. Firefox читает и возвращает цвета в RGB, не в HEX

Я никогда не мог понять, почему 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 (ненавижу тебя — прим. пер.) есть иные методы определения вычисленных стилей.

Дополнительно

9. 0.1 + 0.2 !== 0.3

Эта странность встречается не только в JavaScript, это проблема всей компьютерной науки. Вывод будет такой — 0.30000000000000004.

Эта проблема называется компьютерной неточностью. Когда JavaScript выполняет строку, он переводит значения в их двоичный эквивалент.

Вот где проблема начинается — 0.1 не 0.1 в двоичном эквиваленте, но близкое к этому значение. Это как перевести текст с русского на белорусский. Похоже, но не одинаково.

Остальное выходит за рамки этой статьи (у нас тут не математический кружок)

Данная тема широко рассматривается на форумах о компьютерной науке и разработке. Вы можете предложить свои пути решения этой проблемы.

10. Неопределенное может быть определено

Хорошо, давайте закончим глупостью. Это странно звучит, но 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/