понедельник, 7 ноября 2016 г.

1.11 Область видимости переменных и замыкания

(Из книги "Говоря на языке JavaScript").
 
В JavaScript следует объявлять переменные посредством ключевого слова var перед тем, как использовать их:
 
var x; // Пока содержит значение undefined.
console.log(y); // Возбуждает исключение: ReferenceError: y is not defined
 
Можно объявить несколько переменных единой инструкцией var:
 
var x = 1, y = 2, z = 3;
 
Однако я рекомендую использовать по одной инструкции для каждой переменной (причина объясняется в разделе Синтаксис). Таким образом, следует переписать этот код так:
 
var x = 1;
var y = 2;
var z = 3;
 
В соответствии с принципом "возвышения" (см. "Возвышенные" переменные), лучше всего объявлять переменные в начале программы.

Переменные видны функциям

Переменная всегда видна внутри функции, в которой ее объявили (чего нельзя сказать о текущем блоке). Например:
 
function foo() {
    var x = -512;
    if (x < 0) {  // (1)
        var tmp = -x;
        ...
    }
    console.log(tmp);  // 512
}
 
Здесь видно, что переменная tmp не ограничена своим блоком, начинающимся со строки (1). Она существует до окончания функции.

"Возвышенные" переменные

Каждое объявление переменной "возвышается": декларация перемещается в начало функции, но обращения к переменной остаются на своих местах. В качестве примера рассмотрим объявление переменной в строке (1) в следующей функции:
 
function foo() {
    console.log(tmp); // undefined
    if (false) {
        var tmp = 3;  // (1)
    }
}
 
Внутри JavaScript-интерпретатора предыдущая функция преобразуется к такому виду:
 
function foo() {
    var tmp;  // hoisted declaration
    console.log(tmp);
    if (false) {
        tmp = 3;  // assignment stays put
    }
}

Замыкания

Каждая функция связана с переменными, которые ее окружают, даже когда она вызывается вне пределов своей области видимости. Например:
 
function createIncrementor(start) {
    return function () {  // (1)
        start++;
        return start;
    }
}
 
Функция, начинающаяся на строке (1) покидает контекст, в котором была создана, но остается связанной с переменной start:
 
var inc = createIncrementor(5);
inc() // Вернет 6.
inc() // Вернет 7.
inc() // Вернет 8.
 
Замыкание - это функция в совокупности с переменными, находящимися в ее области видимости. Таким образом, функция createIncrementor() возвращает как раз то, что называется замыканием.

Паттерн IIFE: внедрение новой области видимости

Иногда требуется внедрить в программу новую область видимости, например, чтобы уберечь переменные от конфликтов с глобальным пространством. В JavaScript вы не можете использовать для этого блоки,  следует использовать функции. Но есть паттерн программирования, позволяющий задействовать функцию в качестве блока. Он называется IIFE (immediately invoked function expression - немедленно исполняемая функция-выражение, произносится “иффи”):
 
(function () {  // open IIFE
    var tmp = ...;  // not a global variable
}());  // close IIFE
 
Убедитесь, что используете этот пример в точности так, как он здесь приведен (за исключением комментариев). IIFE вызывается сразу же после своей декларации. Внутри функции находится новая область видимости, предохраняющая переменную tmp от попадания в глобальное пространство. Подробнее см. Введение новой области видимости посредством IIFE.

Способы использования IIFE: беззаботное использование замыканий

Замыкания сохраняют свою связь с окружающими их переменными, что иногда излишне:
 
var result = [];
for (var i=0; i < 5; i++) {
    result.push(function () { return i });  // (1)
}
console.log(result[1]()); // 5 (not 1)
console.log(result[3]()); // 5 (not 3)
 
Значение, возвращаемое в строке (1), всегда указывает на текущее значение переменной i, а не на то значение, которое оно имело в момент создания внутренней функции. По окончании цикла i приобрело значение 5. Вот почему все функции в массиве возвращают одно и то же значение. Если вы хотите, чтобы функция в строке (1) возвращала текущее состояние  i, вы можете прибегнуть к IIFE:
 
for (var i=0; i < 5; i++) {
    (function () {
        var i2 = i; // copy current i
        result.push(function () { return i2 });
    }());
}
 
Этого результата можно достичь и проще, но пример дан для иллюстрации использования паттерна.
 

Комментариев нет:

Отправить комментарий