Сфера

Область видимости — это набор правил, определяющих, где и как можно искать переменную (идентификатор).

Лексическая область

Лексическая область действия означает, что область действия определяется временем автора решениями о том, где объявляются функции. Фаза лексической компиляции, по сути, может знать, где и как объявлены все идентификаторы, и, таким образом, предсказать, как они будут искаться во время выполнения. (Оптимизация)

  • Пузырь 1 охватывает глобальную область и содержит только один идентификатор: foo.
  • Пузырь 2 охватывает область foo, которая включает в себя три идентификатора: a, bar и b.
  • Пузырь 3 охватывает область действия bar и включает только один идентификатор: c.

Поиски

Поиск области останавливается, как только находит первое совпадение. Одно и то же имя идентификатора может быть указано на нескольких уровнях вложенной области, что называется «затенением» (внутренний идентификатор «затеняет» внешний идентификатор). Независимо от затенения, поиск области всегда начинается с самой внутренней области, выполняемой в данный момент, и движется наружу/вверх до первого совпадения и останавливается.

Функции как области действия

  • Объявление функции
var a = 2;

function foo() { // <-- insert this

	var a = 3;
	console.log( a ); // 3

} // <-- and this
foo(); // <-- and this

console.log( a ); // 2

Ловушка: само имя идентификатора foo «загрязняет» объемлющую область видимости (в данном случае глобальную).

  • Выражение функции
var a = 2;

(function foo(){ // <-- insert this

	var a = 3;
	console.log( a ); // 3

})(); // <-- and this

console.log( a ); // 2

Особенность: (function foo(){ .. }) как выражение означает, что идентификатор foo находится только в области, на которую указывает .., а не во внешней области. Скрытие имени foo внутри себя означает, что оно не загрязняет окружающую область без необходимости.

Лучше всего всегда давать имена выражениям функций.

Блокировать как области действия

В Javascript переменная в блоке фактически ограничивает себя охватывающей областью (функцией или глобальной).

for (var i=0; i<10; i++) {
	console.log( i );
}

К счастью, в ES6 добавлено новое ключевое слово let, которое прикрепляет объявление переменной к области действия любого блока, в котором она содержится.

var foo = true;

if (foo) {
	let bar = foo * 2;
	bar = something( bar );
	console.log( bar );
}

console.log( bar ); // ReferenceError

В дополнение к let в ES6 вводится const, который также создает переменную блочной области, но значение которой является фиксированным.

var foo = true;

if (foo) {
	var a = 2;
	const b = 3; // block-scoped to the containing `if`

	a = 3; // just fine!
	b = 4; // error!
}

console.log( a ); // 3
console.log( b ); // ReferenceError!

Вывоз мусора

Приведенный ниже фрагмент кода, скорее всего, заставит JS-движок сохранить структуру, поскольку функция click имеет замыкание по всей области видимости (глобальной).

function process(data) {
	// do something interesting
}

var someReallyBigData = { .. };  //global scope

process( someReallyBigData );

var btn = document.getElementById( "my_button" );

btn.addEventListener( "click", function click(evt){
	console.log("button clicked");
}, false );

Тем не менее, блочная область видимости может решить эту проблему, делая движок более понятным, что ему не нужно держать someReallyBigData рядом.

function process(data) {
	// do something interesting
}

// anything declared inside this block can go away after!
{
	let someReallyBigData = { .. };

	process( someReallyBigData );
}

var btn = document.getElementById( "my_button" );

btn.addEventListener( "click", function click(evt){
	console.log("button clicked");
}, false );

Подъем

У нас может возникнуть соблазн рассматривать var a = 2; как одно выражение, но движок JavaScript так не воспринимает. Он рассматривает var a и a = 2 как два отдельных оператора: первый задача этапа компиляции, а второй задача этапа выполнения.

Это приводит к тому, что все объявления в области действия, независимо от того, где они появляются, обрабатываются до выполнения самого кода. Вы можете визуализировать это как объявления (переменных и функций), которые «перемещаются» в верхнюю часть соответствующих областей, что мы называем «подъемом».

Закрытие

Закрытие — это когда функция может запомнить и получить доступ к своей лексической области видимости, даже если эта функция выполняется за пределами своей лексической области видимости.

Какое бы средство мы ни использовали для переноса внутренней функции за пределы ее лексической области видимости, она будет поддерживать ссылку на область действиядля где оно было первоначально объявлено, и всякий раз, когда мы их выполняем, это закрытие будет выполняться.

Циклы и замыкание

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

for (var i=1; i<=5; i++) {
	setTimeout( function timer(){
		console.log( i );
	}, i*1000 );
}
Output:
6, 6, 6, 6, 6

Решения:

Каждый IIFE имеет индивидуальную область действия.

for (var i=1; i<=5; i++) {
	(function(j){
		setTimeout( function timer(){
			console.log( j );
		}, j*1000 );
	})( i ); 
}

let превращает блок в область действия, которую setTimeout() может закрыть. Более того, let в заголовке цикла for не только связывает i с телом цикла for, но фактически повторно привязывает его к каждой итерации цикла, обеспечивая повторную присвойте ему значение с конца предыдущей итерации цикла.

for (let i=1; i<=5; i++) {
	setTimeout( function timer(){
		console.log( i );
	}, i*1000 );
}

Модули

Есть два «требования» для использования шаблона модуля:

  1. Должна быть внешняя объемлющая функция, и она должна быть вызвана хотя бы один раз (каждый раз создается новый экземпляр модуля).
  2. Охватывающая функция должна возвращать по крайней мере одну внутреннюю функцию, чтобы эта внутренняя функция закрывала частную область и могла получать доступ и/или изменять это частное состояние.
var MyModules = (function Manager() {
	var modules = {};

	function define(name, deps, impl) {
		for (var i=0; i<deps.length; i++) {
			deps[i] = modules[deps[i]];
		}
		modules[name] = impl.apply( impl, deps );
	}

	function get(name) {
		return modules[name];
	}

	return {
		define: define,
		get: get
	};
})(); //Singleton
MyModules.define( "bar", [], function(){
	function hello(who) {
		return "Let me introduce: " + who;
	}

	return {
		hello: hello
	};
} );

MyModules.define( "foo", ["bar"], function(bar){
	var hungry = "hippo";

	function awesome() {
		console.log( bar.hello( hungry ).toUpperCase() );
	}

	return {
		awesome: awesome
	};
} );

var bar = MyModules.get( "bar" );
var foo = MyModules.get( "foo" );

console.log(
	bar.hello( "hippo" )
); // Let me introduce: hippo

foo.awesome(); // LET ME INTRODUCE: HIPPO