Skip to content

Latest commit

 

History

History
123 lines (78 loc) · 11.6 KB

File metadata and controls

123 lines (78 loc) · 11.6 KB

Вы не знаете JS: Область видимости и замыкания

Приложение B: Полифиллинг блочной области видимости

В главе 3 мы исследовали блочную область видимости. Мы отметили, что операторы with и catch оба являются крошечными примерами блочной области видимости, которые существуют в JavaScript с тех пор как появился ES3.

Но в ES6 был представлен let, который окончательно дал полные, неограниченные возможности применять блочную область видимости в нашем коде. Есть много впечатляющих вещей, как функциональных, так и стилистических, которые появились благодаря блочной области видимости.

Но что если мы хотим использовать блочную область видимости в пред-ES6 окружении?

Представим такой код:

{
	let a = 2;
	console.log( a ); // 2
}

console.log( a ); // ReferenceError

Он отлично работает в ES6 окружении. Но можем ли мы также сделать в пред-ES6? catch — вот ответ.

try{throw 2}catch(a){
	console.log( a ); // 2
}

console.log( a ); // ReferenceError

Ого! Какой это уродливый, странновыглядящий код. Тут try/catch, которые используются, чтобы принудительно вызвать ошибку, но эта "ошибка" — всего лишь значение 2, а затем объявление переменной, которая получает это значение в блоке catch(a). Мозг: взорван!

Все правильно, в блоке catch есть блочная область видимости, которая похоже может использоваться как полифиллинг для блочной области видимости в пред-ES6 окружении.

"Но...", - скажете вы. "...никто не хочет писать ужасный код подобный этому!" Это правда. Никто не пишет такой код, который выдает компилятор CoffeeScript. Но суть не в этом.

Суть в том, что утилиты могут транспилировать код на ES6, чтобы он мог работать в пред-ES6 окружении. Можно писать код с блочными областями видимости и извлекать преимущества из такой функциональности и дать возможность утилите во время сборки проекта позаботиться о том, чтобы сгенерировать код, который действительно будет работать после публикации.

Это и в самом деле предпочтительный путь миграции для всего (кхм, большей части) ES6: использовать транспилятор кода чтобы брать код на ES6 и выдать ES5-совместимый код на период перехода от пред-ES6 к ES6.

Traceur

Гугл поддерживает проект, называемый "Traceur", единственной задачей которого является транспиляция возможностей ES6 в пред-ES6 (в основном ES5, но не только!) для повседневного использования. Комитет TC39 полагается на этот инструмент (и другие), чтобы проверять на практике семантику тех возможностей, которые он выпускает.

Во что же превратит Traceur наш код? Вы угадали!

{
	try {
		throw undefined;
	} catch (a) {
		a = 2;
		console.log( a );
	}
}

console.log( a );

Так что с использованием таких утилит мы можем получать все преимущества блочной области видимости независимо от того, будет ли это работать только в ES6 или нет, потому что try/catch используется (и работает именно так) со времен ES3.

Блоки: неявные против явных

В главе 3 мы обозначили некоторые потенциальные проблемы с обслуживаемостью/рефакторингом когда представили блочную область видимости. А есть ли другой путь получить преимущества блочной области видимости, но уменьшив эти недостатки?

Рассмотрим еще одну альтернативную форму let, называемую "let-блок" или "оператор let" (в противоположность "объявлениям let", рассмотренным ранее).

let (a = 2) {
	console.log( a ); // 2
}

console.log( a ); // ReferenceError

Вместо неявного "угона" существующего блока let-оператор создает явный блок со своей собственной областью видимости. Но явный блок выделяется не только этим и возможно более удобным рефакторингом кода, с ним код получается чище грамматически, с помощью принудительного переноса всех определений наверх блока. Это облегчает понимание любого блока, а также того, что попадает в область его видимости, а что — нет.

Как шаблон, он отражает подход, когда многие люди используют область видимости функции и они вручную перемещают/поднимают все свои объявления var вверх функции. let-оператор помещает их в начало блока намеренно и если вы не используете объявления let, разбросанные повсюду как попало, то объявления в блочной области видимости немного легче находить и управлять ими.

Но есть проблема! let в форме оператора не включен в ES6. И официальный компилятор Traceur также не принимает такую форму кода как корректную.

У нас есть два варианта. Можно отформатировать код используя ES6-совместимый синтаксис и добавить немного дисциплины в коде:

/*let*/ { let a = 2;
	console.log( a );
}

console.log( a ); // ReferenceError

Но инструменты призваны решать наши проблемы. Поэтому вторым вариантом будет писать явно блоки оператора let и позволить утилите сконвертировать их в корректный, работающий код.

Поэтому, я создал утилиту, названную "let-er" для решения этой единственной проблемы. let-er — транспилятор кода на этапе сборки, но его единственной задачей является находить let-операторы и транспилировать их. Она оставит в целости и сохранности весь остальной ваш код, включая любые let-объявления. Вы можете безопасно пользоваться let-er как первым звеном транспиляции ES6, а затем передать код во что-то подобное Traceur если надо.

Более того, в let-er есть опция настройки --es6, при включении которой (по умолчанию выключена), меняется получаемый код. Вместо полифильного хака try/catch из ES3, let-er возьмет наш код и выдаст полностью ES6-совместимый, без всяких хаков:

{
	let a = 2;
	console.log( a );
}

console.log( a ); // ReferenceError

Так что вы можете начать пользоваться let-er прямо сейчас и выпускать код под все пред-ES6 среды, а когда вам требуется только ES6, можно добавить опцию и сразу же получать только ES6-код.

И что более важно, вы можете использовать более предпочтительную и более явную форму let-оператора даже не смотря на то, что он не является официальной частью какой-либо версии ES (пока что).

Производительность

Позвольте мне напоследок добавить пару слов о производительности try/catch и/или чтобы рассмотреть вопрос: "почему бы просто не использовать IIFE для создания области видимости?"

Во-первых, производительность try/catch ниже, но нет ни одного разумного предположения, что в этом случае так и есть, или даже что так будет всегда в таких случаях. Поскольку официальный подтвержденный TC39 ES6-транспилятор использует try/catch, команда Traceur попросила Chrome улучшить производительность try/catch и у них конечно же есть мотивация так и сделать.

Во-вторых, IIFE — не справедливое равноценное сравнение с try/catch, поскольку функция, обернутая вокруг любого обычного кода, меняет значение внутри этого кода у операторов this, return, break и continue. IIFE - не замена в повседневных задачах. Ее можно использовать вручную только в особых случаях.

В итоге, вопрос превращается в такой: нужна ли вам блочная область видимости или нет. Если нужна, эти утилиты дадут вам такую возможность. Если нет, продолжайте использовать var и кодировать!

Google Traceur

let-er