Статья описывает особенности Javascript ES3, которые имеют свойство забываться. Одни из них — странности языка, а другие просто свойства, которые, по-моему, труднозапоминаемы. Я постарался собрать здесь не те из них, по которым можно дать просто ссылку на описание языка (как по различиям между apply и call), а о которых мало сказано в руководствах.
Прежде чем начать, хочу уточнить 3 момента, которые я не стараюсь доказать этой статьёй:
1. Я не говорю, что эти особенности забываются всеми или что они такие по сути. Я их нахожу трудными лично для меня (например, забываются детали, если их не освежать в памяти). Они могут быть лёгкими для вас. Тем не менее, думаю, что этот сборник может быть полезен не только мне.
2. Я не говорю, что нужно использовать эти части языка, а лишь утверждаю, что если вы знаете их, то сможете легко читать код других разработчиков. Большинство их могут быть полезны, но не всегда наличие их в коде — лучшее решение.
3. Я не затрагиваю особенностей ES5 — ни Strict, ни вообще, ни ES6 в целом. Я расскажу о них позже. Я признаю, что некоторые из обсуждаемых особенностей изменяются или развиваются в последующих версиях ECMAScript.
C учётом сказанного, начнём.
1. −0 = +0 = 0 (или строгий нуль равен положительному и отрицательному нулям)
В JavaScript есть значение числа -0 и +0, показывающее, с какой стороны вы подошли к нулю. Но оно преобразуется к строгому нулю при сравнении.
jsfiddle.net/codylindley/6phD9/light/
console.log(-0); //logs 0
console.log(+0); //logs 0
console.log(-0 === +0); //logs true
console.log(0 === -0); //logs true
console.log(0 === +0); //logs true
2. Можно вызвать конструктор без ключевого слова new
Забыли или надоело писать new — нет проблемы, конструктор вызывается и так (но не так). Надоело писать скобки после конструктора — то же самое.
jsfiddle.net/codylindley/nHCvx/light/
var Human = function(){
this.type = 'human';
console.log('I was called');
};
new Human; //вызов без скобок выполняет конструктор и выводит "I was called"
console.log((new Human).type); //выводит human
3. instanceof не работает с примитивами
Оператор instanceof возвращает false для строк, чисел и логических переменных. Если же они расположены в объектах (т.е. в обёртках примитивов), то нормально работает. Не забывайте, что null и undefined не имеют объектной обёртки.
jsfiddle.net/codylindley/bpJjZ/light/
//instanceof не работает на примитивах
console.log("" instanceof String); //выводит false
console.log(3 instanceof Number); //выводит false
console.log(false instanceof Boolean); //выводит false
//instanceof работает на сложных значениях (объектах)
console.log(new String() instanceof String); //выводит true
console.log(new Number() instanceof Number); //выводит true
console.log(new Boolean() instanceof Boolean); //выводит true
console.log([] instanceof Array); //выводит true
console.log({} instanceof Object); //выводит true
console.log(/foo/ instanceof RegExp); //выводит true
//к сведению,
console.log([] instanceof Object); //выводит true
4. Оператор typeof — неуниверсальный и с ошибками
Применимость typeof ограничивается примитивами, но и там имеет ошибку: (typeof null === «object») == true, из-за чего самописно-костыльные способы проверок оказываются наилучшими./
jsfiddle.net/codylindley/U64aZ/light/
//удобно для значений примитивов string, number, и boolean
console.log(typeof ""); //выводит "string"
console.log(typeof 4); //выводит number
console.log(typeof true); //выводит boolean
console.log(typeof undefined); //выводит undefined
//но ошибочно, когда переходим к null
console.log(typeof null); //ЧТО?? Неправильно! выводит Object
5. Булевый объект от ложных значений всегда true
console.log(!!(new Boolean(false)) ) //true
new Boolean(new Boolean(false)) — не false, потому что возвращает объект-обёртку. А объект (следите за руками) всегда преобразуется к true. Поэтому названный софизм относится вообще к любому значению конструктора. А настоящий false создают лишь примитивы (NaN, false, 0, null, undefined, и '') и значения выражений.
6. Аргументы или параметры функции?
Аргументы используются в момент вызова функции. Мы передаём функции аргументы. Параметры устанавливаются в момент определения функции. Таким образом, говорят, что параметры используются для определения функции. Эти слова взаимозаменяемы, но знание их разницы может пригодиться.
jsfiddle.net/codylindley/8Zkdd/light/
var myFunction = function(x,y,z){ //x,y,z - параметры
return x+y+z
};
myFunction(1,2,3); //1,2,3 - аргументы
7. Изменение значения параметра не изменяет значение аргумента
Например, если вы передадите массив в функцию, то изменение значения параметра не изменит исходного значения параметра.
jsfiddle.net/codylindley/c2xb4/light/
var myFunction = function(parameter){
parameter = undefined; //изменяем значение параметра на undefiend
console.log(parameter);
};
var myArray = [1,2,3];
myFunction(myArray); //вызываем myFunction, передаём массив
//myArray - не undefined , несмотря на то, что он как параметр изменён в функции
console.log(myArray); //logs [1,2,3]
8. Вызов Boolean() как функции преобразует любое значение в boolean
Вызов конструктора как функции (без new) с аргументом преобразует его в логическое.
jsfiddle.net/codylindley/4wQLd/light/
//всё, что ниже, возвращает false
console.log(Boolean(undefined));
console.log(Boolean(null));
console.log(Boolean(0));
console.log(Boolean(''));
console.log(Boolean(NaN));
//всё, что ниже, возвращает true
console.log(Boolean(1));
console.log(Boolean('false'));
console.log(Boolean([]));
console.log(Boolean({}));
console.log(Boolean(function(){}));
console.log(Boolean(/foo/));
Конечно, двойная инверсия !!() даст тот же эффект. Но это недостаточно выразительно, чтобы мне нравилось.
jsfiddle.net/codylindley/jNPvH/light/
//всё, что ниже, возвращает false
console.log(!!undefined);
console.log(!!null);
console.log(!!0);
console.log(!!'');
console.log(!!NaN);
//всё, что ниже, возвращает true
console.log(!!1);
console.log(!!'false');
console.log(!![]);
console.log(!!{});
console.log(!!function(){});
console.log(!!/foo/);
9. Вызов конструктора примитивных объектов без new возвращает примитивы
jsfiddle.net/codylindley/ymGmG/light/
//выводит true
console.log(String('foo') === 'foo');
console.log(Number(5) === 5);
console.log(Boolean('true') === true);
//выводит false, потому что создаётся оболочка объекта
console.log(new String('foo') === 'foo');
console.log(new Number(5) === 5);
console.log(new Boolean('true') === true);
10. Object() создаёт объектные оболочки примитивов
В отличие от других конструкторов, Object() оборачивает примитивы в оболочки.
jsfiddle.net/codylindley/tGChw/light/
//примитив number в оболочке объекта
console.log(Object(1) instanceof Number); //logs true
//примитив string в оболочке объекта
console.log(Object('foo') instanceof String);
//примитив boolean в оболочке объекта
console.log(Object(true) instanceof Boolean);
11. Доступ к свойствам примитивов таит ошибку
Всё отлично пишется в примитивы, кто бы что там ни говорил. Проблем нет и при чтении. Если не считать, что читается всегда undefined. Причина в том, что объектная оболочка для примитива не создаётся.
jsfiddle.net/codylindley/E7GGK/light/
//создаём примитивы
var N = 5;
var S = 'foo';
var B = true;
//добавим к примитивам свойства, которые исчезнут, если у примитивов нет обёрток объектов
N.test = 'test';
S.test = 'test';
B.test = 'test';
//проверим, что свойств не осталось
console.log(N.test); //выводит undefined
console.log(S.test); //выводит undefined
console.log(B.test); //выводит undefined
12. delete не удаляет унаследованные свойства
jsfiddle.net/codylindley/xYsuS/light/
var Person = function(){}; //создаёт конструктор Person
Person.prototype.type = 'person'; //наследуемое свойство
var cody = new Person(); //потомок Person
delete cody.type //удаляем (?) type
console.log(cody.type) //выводит person, потому что сын за отца не отвечает
13. Объектные оболочки строк массивоподобны
Из-за того, что оболочка строкового объекта создаёт массивоподобный объект, (т.е. console.log(new String('foo')); //выводит foo {0=«f», 1=«o», 2=«o»} ), можно использовать индексирование символов (кроме IE7-).
jsfiddle.net/codylindley/7p2ed/light/
//примитив строки
var foo = 'foo';
//доступ к символам как к массиву
console.log(foo[0]) //выводит "f", потому что создаётся оболочка объекта {0="f", 1="o", 2="o"}
//есть доступ к свойствам через угловые скобки
console.log(foo['length']); //выводит 3
14. Доступ к свойствам из примитивов чисел
Первая точка после примитива числа распознаётся как десятичная точка и конфликтует с точкой в смысле доступа к свойству. Но вторая…
jsfiddle.net/codylindley/Pn6YT/light/
//скобочная нотация
console.log(2['toString']()); //logs 2
//точечная нотация доступа к свойствам после десятичной точки.
console.log(2..toString()); //logs 2
//скобки для точечной нотации
console.log((2).toString()); //logs 2
15. Array.length можно переписывать, что имеет побочные эффекты
Явным присваиванием свойства length можно добавить ряд undefined-значений в конец массива или укоротить массив.
jsfiddle.net/codylindley/9Pp9h/light/
var myArray1 = [];
myArray1.length = 3;
console.log(myArray1); //выводит [undefined, undefined, undefined]
//удалить часть значений
myArray2 = [1,2,3,4,5,6,7,8,9]
myArray2.length = 1;
console.log(myArray2); //выводит [1]
16. Логический оператор "||" работает до первого истинного значения
Если значение левого аргумента в этом операторе приведётся к true, правое значение вычисляться не будет, что образно именуется термином «short-circuiting» (закорачивание, короткое замыкание, или для данного случая — «идти коротким путём»). Фактически, обнаружение true в первом значении приводит к незатрагиванию функций и выражений правой части, неизменению значений в их скрытых сеттерах.
jsfiddle.net/codylindley/NUKKZ/light/
//возвращает первое значение, преобразуемое к true, затем выражения не вычисляются
var foo = false || 0 || '' || 4 || 'foo' || true;
console.log(foo); //выводит 4, потому что число приводится
// к первому истинному логическому значению в цепочке
17. Логический оператор "&&" работает до первого ложного значения
Аналогично, но с обратным логическим значением, выполняется до первого фола включительно.
jsfiddle.net/codylindley/DEbk5/light/
//возвращает первое значение, преобразуемое к false, затем выражения не вычисляются
var foo = true && 'foo' && '' && 4 && 'foo' && true;
console.log(foo); //выводит '', потому что строка приводится к false
18. Когда использовать null, а когда — undefined?
Проще говоря, null — это значение (примитив) для описания отсутствия значения, а undefined — это настоящее отсутствие какого-либо примитива, объекта, свойства: пустая ссылка. В обычной практике разработчикам рекомендуется использовать null для индикации явно проставленного отсутствия значений, а undefined — оставлять для Javascript для указания отсутствия того, чего «никогда не было». При использовании null в программе вы отличите сообщения скрипта от следов сознательных установок null.
19. undefined по умолчанию
undefined описывает отсутствие значения. Следующие примеры не создают значений (возвращают undefined).
jsfiddle.net/codylindley/3buPG/light/
jsfiddle.net/codylindley/3buPG/light/
//переменные без присвоенных значений - не имеют значений
var foo;
console.log(foo); //undefined
//попытка доступа к отсутствующему свойству возвращает undefined
console.log(this.foo); //undefined
//передача отсутствующих аргументов функции, ожидающей их, приводит к undefined-параметрам
var myFunction = function f(x){return x}
console.log(myFunction()); //undefined
//функция возвратит undefined, если не выполнила явный return вообще
var myFunc = function f(){};
console.log(myFunc()); //undefined
20. Выражение может работать там, где не сможет оператор
Выражения возвращают значения, в то время как операторы выполняют действия, что ярко иллюстрируется на примере различий между if и тернарным логическим оператором для условного выражения. «if» не может работать при задании параметров функций, условное выражение — может.
jsfiddle.net/codylindley/SSh68/light/
var verify = true;
//оператор if работает с любыми операторами
if(verify){console.log('verify is true');}
//Выражение может работать там, где не могут быть применены операторы
//запись в переменную (странный пример --прим.пер.)
var check = verify ? console.log('verify is true') : console.log('verify is false');
//передача значений в функцию
console.log(verify ? 'verify is true' : '
verify is false');
21. Расположение операторов ++ и --
Если операторы префиксные (перед переменной), то сначала изменяется значение, затем оно возвращается в выражение. Если постфиксные — в выражение возвращается начальное значение, но перед возвратом переменная изменяется.
jsfiddle.net/codylindley/QzG9w/light/
http://jsfiddle.net/codylindley/QzG9w/light/
var boo = 1;
var foo = 1;
console.log(boo++); //даёт 1, но следующий console.log(boo) дал бы 2
console.log(++foo); //даёт 2, возвращает новое значение foo
Автор: spmbt