За последние несколько месяцев я сделал несколько доработок для JSHint
, в основном с целью изучить ES6 (я особенно горжусь тем, как переделано обнаружение областей видимости для переменных). Во время этого процесса я наткнулся на несколько вещей, которые меня удивили — в основном, в ES6, однако есть и кое-что про ES3, что я до этого никогда не использовал.
Break из любого блока
Наверняка вы знаете, что в любом цикле можно использовать ключевые слова break
и continue
— это стандартная возможность в современных языках программирования. Однако не все знают, что циклам можно давать метки и с их помощью прерывать любой конкретный цикл:
outer: for(var i = 0; i < 4; i++) { while(true) { continue outer; } }
То же самое применимо и к break
. Вы наверняка видели, как он используется в выражении switch
:
switch(i) { case 1: break; }
Вообще говоря, именно поэтому Крокфорд не советует добавлять отступы перед case
— выражение break
выкидывает из блока switch
, а не case
, но мне вариант с отступами кажется более читабельным. Выражения switch
также можно помечать меткой:
myswitch: switch(i) { case 1: break myswitch; }
Также можно объявлять блоки просто так. Знаю, что это также доступно в C#, и наверняка в других языках тоже:
{ { console.log("Я внутри произвольного блока"); } }
Если сложить все это вместе, можно выйти из любого блока с помощью метки:
outer: { inner: { if (true) { break outer; } } console.log("Эта строчка никогда не выполнится"); }
Разумеется, это относится только к break
— оператор continue
допустим только внутри цикла. Я ни разу не видел метки в коде на Javascript — скорее всего, потому, что если вдруг понадобится экстренно выйти из более чем одного блока, это повод переписать код на функцию с return
.
Однако, если бы мне вдруг захотелось написать функцию с единственной точкой выхода (что, вообще-то говоря, не в моем вкусе) — можно было бы использовать этот подход. Вот, например, функция с несколькими точками выхода:
function(a, b, c) { if (a) { if (b) { return true; } doSomething(); if (c) { return c; } } return b; }
Добавляем метки, и получается вот что:
function(a, b, c) { var returnValue = b; myBlock: if (a) { if (b) { returnValue = true; break myBlock; } doSomething(); if (c) { returnValue = c; } } return returnValue; }
Или же, можно было бы использовать больше блоков:
function(a, b, c) { var returnValue = b; if (a) { if (b) { returnValue = true; } else { doSomething(); if (c) { returnValue = c; } } } return returnValue; }
Вообще, вариант с метками мне нравится меньше всех, но может только потому, что я к нему не привык?
Деструктуризация существующей переменной
Сперва — фишка, которую я не могу могу объяснить. В ES3, судя по всему, можно добавить скобки вокруг переменной при присваивании и это будет работать:
var a; (a) = 1; assertTrue(a === 1);
Если вы знаете, зачем кому-то может понадобиться так делать, то напишите, пожалуйста, в комментариях.
Деструктуризация — это процесс получения значения переменной из объекта или массива. Чаще всего можно видеть подобный пример:
function pullOutInParams({a}, [b]) { console.log(a, b); } function pullOutInLet(obj, arr) { let {a} = obj; let [b] = arr; console.log(a, b); } pullOutInParams({a: "Hello" }, ["World"]); pullOutInLet({a: "Hello" }, ["World"]);
Но можно сделать то же самое и без let
, var
и const
. Для массива достаточно написать вот так:
var a; [a] = array;
А вот с объектом не получится — его необходимо обернуть в круглые скобки:
var a; ({a} = array);
Причина в том, что это дает слишком большой простор для двусмысленного толкования и ошибок, связанных с анонимными блоками кода, потому как автоматическая расстановка точек с запятой превращает идентификаторы в вычисляемые выражения, а они могут иметь побочные эффекты:
var a = { get b() { console.log("Превед!"); } }; with(a) { { b } }
Возвращаясь к изначальному примеру, где мы заключили присваивание в круглые скобки — вопреки предположениям, это не имеет никакого отношения к деструктуризации:
var a, b, c; (a) = 1; [b] = [2]; ({c} = { c : 3 });
Деструктуризация с числами
Еще один аспект деструктуризации, о которой не все могут подозревать — это то, что названия свойств не обязательно должны быть незакавыченными строками. Это могут быть числа:
var {1 : a} = { 1: true };
Или строки в кавычках:
var {"1" : a} = { "1": true };
А еще можно вычислять имя свойства из выражения:
var myProp = "1"; var {[myProp] : a} = { [myProp]: true };
Это позволяет с легкостью написать очень запутанный код:
var a = "a"; var {[a] : [a]} = { a: [a] };
Объявления класса привязаны к блоку
Объявления функции поднимаются в самый верх блока, что позволяет использовать их до объявления:
func(); function func() { console.log("Все в порядке"); }
А вот если функция объявляется в ходе присваивания переменной, то поднимается только объявление переменной, но не присваивание ей значения:
func(); // func объявлена, но не имеет значения, поэтому ошибка "func не является функцией" var func = function func() { console.log("Всё в порядке"); };
Классы — одна из наиболее популярных частей спецификации ES6, и всегда считались своего рода синтаксическим сахаром для функций. Однако если вы думаете, что этот код заработает, то вы ошибаетесь:
new func(); class func { constructor() { console.log("Все в порядке"); } }
Несмотря на сходство с первым примером, оно не работает. На самом деле это эквивалент следующего кода:
new func(); let func = function func() { console.log("Fine"); }
Тут мы пытаемся обратиться к func
внутри временной мертвой зоны, что является синтаксической ошибкой.
Параметры-тёзки
Я предполагал, что у функции не может быть двух параметров с одним и тем же именем — а на самом деле может!
function func(a, a) { console.log(a); } func("Привет", "Мир"); // выводит "Мир"
Однако в strict mode всё не так:
function func(a, a) { "use strict"; console.log(a); } func("Привет", "Мир"); // в Chrome будет ошибка - SyntaxError: Strict mode function may not have duplicate parameter names
Оператор typeof
небезопасен
Ладно, ладно, я украл это наблюдение, но повторить все равно будет не лишним.
До ES6 было широко известно, что с помощью оператора typeof
можно безопасно узнать, объявлен ли идентификатор, даже если ему не присвоено значение:
if (typeof Symbol !== "undefined") { // Symbol доступен } // Этот код выкинет исключение, если Symbol не объявлен if (Symbol !== "undefined") { }
Но теперь это работает только в том случае, если вы не объявили переменную с помощью let
или const
. Всему виной ВМЗ, из-за которой обращение к переменной до ее присваивания является синтаксической ошибкой, даже несмотря на то, что «под капотом» объявление переменной все равно поднимается в самый верх блока.
if (typeof Symbol !== "undefined") { // Symbol доступен } let Symbol = true; // вызывает синтаксическую ошибку в условии выше!
Создание массива
Я всегда избегал создания массива с помощью ключевого слова new
. В основном потому, что аргументы могут быть либо длиной массива, либо его элементами:
new Array(1); // [undefined] new Array(1, 2); // [1, 2]
Однако коллега недавно наткнулся на кое-что, что мне раньше не встречалось:
var arr = new Array(10); for(var i = 0; i < arr.length; i++) { arr[i] = i; } console.dir(arr);
Этот код выдает массив с числами от 0 до 9. А что будет, если отрефакторить его с использованием map
?
var arr = new Array(10); arr = arr.map(function(item, index) { return index; }); console.dir(arr);
Массив остался неизменным. Судя по всему, конструктор, принимающий длину, создает массив и задает свойство length
, но не создает никаких элементов. Поэтому обратиться к свойству можно, а перечислить элементы нельзя. А если задать значение какому-нибудь элементу?
var arr = new Array(10); arr[8] = undefined; arr = arr.map(function(item, index) { return index; }); console.dir(arr);
Получаем массив, где восьмому элементу присвоено число 8, но все остальные значения не заданы. Если посмотреть на код полифилла для функции map
, она проверяет заданность свойства с помощью оператора in
. Такого же поведения можно достичь с помощью литералов массива:
var arr = []; arr[9] = undefined; // или же var arr = []; arr.length = 10;
Другие жемчужины
В блоге разработчиков Mozilla есть отличная статья про функции со стрелками, где говорится о том, что комментарии можно помечать символом <--
. Неплохо почитать и остальные посты в блоге.
ссылка на оригинал статьи http://habrahabr.ru/post/261785/
Добавить комментарий