Семь удивительных «возможностей» Javascript

от автора

За последние несколько месяцев я сделал несколько доработок для 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *