Кодинг
★ Рубрика: Кодинг
★ Тема: JavaScript

Хитрости JavaScript

Небольшая подборка интересных хитростей JavaScript, о которых знает не каждый опытный программист.

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;
Источник: http://habrahabr.ru/post/261785/
 Похожие публикации: JavaScript

Войти и комментировать [ Вход | Регистрация ]