Автор оригинала: FreeCodeCamp Community Member.
Кевин Тюреей
Как цепочка и замыкание цепи работает под капотом с примерами.
Понимание охвата и закрытия в JavaScript
Чтобы выкопать глубоко и получить необходимую информацию, подумайте как журналист. Спросите шесть основных вопросов: кто, почему, где, где, когда и как. Если вы можете ответить на все это на конкретном теме, то вы набрали сущность того, что вам нужно знать.
Прежде чем мы доберемся, мы должны иметь понимание охвата.
Во-первых, если вы знаете, что [[Scope]] (двойной кронштейн) есть, то эта статья не для вас. У вас есть более продвинутые знания и можем двигаться дальше.
Что…
Что такое Область И почему это имеет значение?
Поместите другой путь, область охвата доступ. Есть ли функция возможность поиска переменной для выполнения или манипуляции, какие переменные видны?
Есть два типа области: местный и глобальный. Разрешение области или нахождение того, какие переменные принадлежат где, начинается в самых внутренних контексте и выходит наружу, пока идентификатор не будет найден. Давайте начнем маленький …
var firstNum = 1;
function number() { var secondNum = 2; return firstNum + secondNum;}
number();
Когда, почему и как … выполнение контекста
Когда функция вызывается, она образует новый контекст выполнения. Что такое контекст исполнения? Ну, так же, как у нас есть два типа объема, у нас есть два типа контекста выполнения. Они являются глобальным контекстом выполнения и контекст выполнения функций.
Глобальный контекст всегда работает. В случае нарушения браузера она останавливается только при закрытии браузера. Когда мы вызываем функцию, мы помещаем контекст выполнения функции поверх контекста глобального выполнения. Следовательно, терминология мы стек их.
JavaScript – это один резьбовый язык, что означает, что он может сделать только одну вещь за раз. Когда мы вызываем функцию, предыдущий контекст выполнения приостановлен. Выполненная функция находится сверху, и она выполняется. Когда это заканчивается, он выключен с стека, а затем возобновляется старый контекст выполнения. Этот «стек» исполнения – это то, что отслеживает положение исполнения в нашем приложении. Это также важно в поисках идентификаторов.
Итак, теперь у нас есть формированный контекст исполнения, что дальше?
Каждый контекст выполнения имеет связанный объект переменной
Во-первых, Объект активации (Недоступно по коду, но работают на заднем плане). Это связано с этим контекстом выполнения. Этот объект имеет все объявленные переменные , Функции и Параметры прошел в этом контексте (его объем или диапазон доступности).
Параметры к функции неявно определены. Они являются «местными» к этой функции. Эти объявленные переменные «поднимаются», доставленные в верхнюю часть области, к которым они принадлежат.
Прежде чем идти дальше, чтобы избежать путаницы – в глобальном контексте исполнения, а Переменная объект создается, и если это функция, это Объект активации Отказ Они в значительной степени идентичны.
Теперь, когда эта функция вызывается, создается «цепь области применения» этих объектов. Почему? Цепочка охвата является способ связать или предоставить систематический доступ ко всем переменным и другим функциям, которые содержит текущий контекст выполнения (функция в этом случае). [[Scope]] – скрытый механизм, который связывает эти переменные объекты для поиска идентификатора. Это скрытое [[Область]] является свойством функции, создаваемой при декларации, а не вызова.
Во главе цепной цепи прицела, если это функция, это Объект активации Отказ Этот объект активации имеет свои объявленные переменные, аргументы и это.
Далее на цепочке прицела является следующим объектом из содержащего контекста. Если это глобальная переменная, это Переменный объект. Если это функция, это Объект активации Отказ Это происходит, пока мы не достигнем глобального контекста. Вот почему вы можете увидеть, как мы начнем с самого внутреннего контекста до самой внешней, думаю, русские гнездовые куклы.
В чем разница между переменной, которая объявляется, и тот, который не пренебрежен? Если идентификатор предшествует VAR, пусть или const, он объявлен явно и пространство памяти выделяется для использования этой переменной. Если идентификатор не проверяется явно, то он неявно объявлен в глобальном объеме, который мы рассмотрим в ближайшее время. Для целей настоящей статьи я придерживаюсь var, нет особой причины.
Я знаю, что вышеизложенное было немного техническим, и, честно говоря, как я написал это, я только узнал о переменной и объекта активации сами. Теперь, когда у вас было глубокое объяснение погружения, вот высокоугольный описание …
Цепочка области аналогична цепочке прототипа. Если переменная или свойство не найдено, она продолжает в цепочке до тех пор, пока она не найдена, либо ошибка не будет выброшена. Функция создает скрытую свойство [[[SCOPE]]. Эта недвижимость ссылается на самые внутренние призывы к внешнему объемам. В этом случае цепочка объема номера связана с объектом глобального окна (содержащий контекст, который содержит номер функции). Это то, что позволяет двигатель посмотреть вне номера функции, чтобы найти первую и вторующую.
Например, давайте возьмем один и тот же номер функции и измените одну вещь:
// global scope - includes firstNum, secondNum, and the// function number
var firstNum = 1;
function number() { // local scope for number - only thirdNum is local to number() // because it was explicitly declared. secondNum is implicitly declared in the // the global scope.
secondNum = 2; var thirdNum = 3; return firstNum + secondNum; }// what do we have access to in the global scope?number(); // 3firstNum; // 1secondNum; // 2thirdNum; // Reference Error: thirdNum is not defined
Говоря о глобальном объеме, переменные объявления, не вложенные декларации функций и выражения функций (все еще считаются определением переменной), рассматриваются в объеме глобального окна объекта в браузере. Поэтому, как мы видим выше, окно объект имеет свойства FirstNum, Secondnum, и номер добавлен к нему. Если мы продолжим по цепочке области ищет ее, мы продолжаем смотреть, пока мы не достигнем к объекту «Глобального контекста». Если это не там, то мы получаем ссылочную ошибку.
In a new tab, type "about:blank" in the search bar. A blank page will open and hit cmd-option-i to open dev tools.
Type the code above and remember, shift-enter for a new line!
Now type "window" and explore all the properties on the window object.
Look closely and you will see the properties firstNum, secondNum, and number are all available on the window object.
Когда мы пытаемся получить доступ к третьейтуре за пределами того, где он был объявлен, мы получаем ссылочную ошибку. Двигатель, который компилирует код не удалось найти идентификатор в окне Global Scope Object.
Третий доступна только внутри функции, где она была объявлена. Он инкапсулирован или конфиденциально для номера функции
Вопрос, который у вас может быть: «Делает ли глобальный объем доступа ко всему внутри числа?» Опять же, области применения работает только от наизнанку, внутреннего контекста, локального, до внешнего контекста, глобальной.
Начиная с локальной области, мы можем сказать, что данные и переменные, которые обернуты в функцию, доступны только для членов этой функции. Цепочка охвата – это то, что ссылки из первой необходимости номера ().
Когда номер () вызывается, нетехнический разговор идет так …
Это в значительной степени это для понимания Область применения, ключевое вынос:
- Идентификатор идентификатора работает от наизнанку и останавливается на первом матче.
- Есть два вида объема, глобальные и местные
- Цепочка области создается при вызове функций и основана на том, где записываются переменные и/или блоки кода (лексическая среда). Переменные или функции вложены?
- В JavaScript, если идентификатор не выполняется с var, пусть, или const, оно неявно объявлено в глобальном объеме.
- Область применения не проходит 1 для 1 с функцией, она идет от 1 до 1 с вызовом функций. Выполните функцию 3 раза, получи 3 разных области. Почему? Поскольку, если выполнение функции завершено, он выключен с стека выполнения и с ним, его доступ к другим переменным через цепочку объема. Таким образом, новый объем создается каждый раз, когда выполняется функция. Закрытие работает немного по-другому!
Давайте закончим с более сложным примером, прежде чем мы перейдем к закрытиям.
a = 1;var b = 2;
function outer(z) { b = 3; c = 4; var d = 5; e = 6;
function inner() { var e = 0; d = 2 * d; return d; } return inner(); var e;}outer(1);
- Прежде чем мы бежим чем угодно, подъемник начинается снаружи, глобальный уровень. Поэтому мы начинаем с декларации для Переменная B и объявление функции для Функциональный объект внешний Отказ На данный момент ничего не назначена, у нас есть только эти две клавиши, установленные в глобальном объекте переменного объема.
- Далее мы начинаем в а. Это задание, или «запись в» заявление, но для него нет официальной декларации. Так что происходит в глобальном масштабе, а если не в «строгом режиме», в том, что А Будет неявно объявлен как принадлежащий к глобальному объекту переменного объема.
- Мы переходим на следующую строку и посмотрите на идентификатор B Через подъемное поднятие было объяснено, и теперь мы можем назначить значение, 2, к нему.
До сих пор у нас есть …
Глобальный спектр
4. С тех пор как мы построили Функциональный объект внешний , при подъеме, мы затем прыгаем к выполнению, внешнее (1);
5. Помните, что при вызове функций сначала создано контекст выполнения. С этим мы создаем объект активации. Он содержит данные и переменные локальные для этого контекста. Мы также формируем цепочку объема.
6. Параметр z неявно объявлен для этой функции и назначен 1.
Быстрая сторона примечания: в это время контекст выполнения функции создает его « это » связывание. Это также создает Аргументы Array , который является массивом параметров, в этом случае Z. Это Выходит за рамки этой статьи, поэтому позвольте мне взглянуть на него.
7. Теперь мы ищем явные объявления переменных в Функция внешняя Отказ У нас есть D и var e объявлен после Функция внутреннего Отказ
8. Вот какая-то скрытая магия, В это время свойство скрытой [[Scope]] для функции внешних связывает свою цепочку объема переменной объекты. В этом случае он работает как связанный список с свойством родительского типа, подключающий объект внешнего активации функций к объекту переменных контекста глобального выполнения. Здесь вы можете увидеть, что прицел простирается изнутри, чтобы сформировать эту «ссылку». Это ссылка, которая позволяет нам продолжить цепочку охвата поиска.
Область применения для функции внешнего
9. Мы наступаем внутрь внешний и начать в B Отказ Это B заявил? Неа. Так что JavaScript использует скрытый [[Область применения]] Свойство прилагается к функции внешний Чтобы переместить цепь охвата, чтобы найти « B ». Он находит его в глобальном объекте масштаба и, поскольку мы находимся в теле функции внешний Мы назначаем B значение 3.
Глобальный объем снова
10. Следующая строка, C Отказ Так как это запись в идентификатор C был …| C Явно объявлено в функции внешний ? Нет, и поэтому его не найдено по поиску в объекте активации внешнего объекта. Таким образом, он поднимает цепочку охвата и смотрит в глобальный объект объема варианта. Это не там. Поскольку это операция записи/назначения, глобальный объем будет обрабатывать его и поместить его на его переменную объект.
Глобальный объект переменных
11. D Отказ Да, это здесь, поэтому мы присваиваем его 5.
Область применения для функции внешнего
12. е Отказ Помните, что Stragger, var е ? Он все еще был объявлен в теле внешний И поэтому у нас уже было место для этого – поэтому мы назначаем его 6. Если это не было объявлено, как C Мы бы переместили цепочку охвата для поиска. Поскольку это запись, а не операция чтения, а не в «строгом режиме», она была бы помещена в объем глобальной массы.
13. Мы призываемся к функции Внутренний Отказ Мы начинаем все, как мы сделали с функцией внешний: Подъем, настройте объект активации и создайте скрытый [[Область применения]] имущество. В этом случае содержащий контекст функция внешний и внешний «Очки» до глобального масштаба.
Область применения функции
14. Сейчас с е И в целом переменные, которые дают то же имя, работают так. Поскольку поиск идентификатора начинается с самого внутреннего применения до самой внешней области, поиск останавливается при первом обнаружении этого идентификатора. В теле Внутренний, Мы видим var е = 0, сделано, останавливайтесь, не пойти дальше. е В теле функции внешний «недоступно». Термин, который обычно используется, это «затенение» е в функции Внутренний «Тени» или скрывают е в функции внешний Отказ
15. Следующая строка – D * D Отказ Прежде чем назначить значение D Слева мы должны оценить выражение справа, 2 * D Отказ С D не является местным по объему Внутренний Мы поднимаемся вверх по цепочке области, чтобы найти переменную для D И имеет ли это значение, связанное с ним. Мы находим это в внешний Сфера действия функции внешний И именно там изменяется значение.
Область применения для функции внешнего
Важная вещь вот что Внутреннее манипулирование данными в ее внешнем объеме!
16. Функция Внутренний Возвращает значение D 10.
17. Функция внешний Возвращает значение функции Внутренний Отказ
18. Результат 10.
19. После того, как функция внешний полностью закончил исполнение, Коллекция мусора происходит. Коллекция мусора – это освобождение ресурсов, которые больше необходимы. Он начинается в глобальном масштабе и работает, насколько она имеет «достижение».
Глобальный объем в этом примере не имеет ручки для функции внешний или функция Внутренний Так что Уош, ушел. Это важно, когда мы получаем закрытие, потому что там нам нужны данные и некоторые переменные, чтобы прилипать даже после завершения работы функции.
Наконец, давайте получим застежку!
Как мы определим закрытие?
Давайте начнем с нескольких определений, все правильные, некоторые более глубокие, но это добраться до одной той же точки.
1. Closures are functions that have access to variables from another function's scope. This is accomplished by creating a function inside another function.
2. A Closure is a function that returns another function.
3. A Closure is an implicit, permanent link between a function and its scope chain.
Зачем закрыть?
Без возможности использовать правила цепи охвата, асинхронные операции были бы невозможны. Поскольку нет никакой гарантии, что данные все равно будут вокруг, чтобы использовать позже. JavaScript имеет только сферу функции в качестве механизма инкапсуляции.
Закрытие – лучшая форма конфиденциальности для функций и переменных. Это очевидно при использовании многих модульных шаблонов. Шаблон модуля возвращает объект для выставления публичного API. Он также хранит другие методы и переменные частные. Закрытия используются в обработке событий и обратных вызовах.
Пример модуля …
var Toaster = (function(){ var setting = 0; var temperature; var low = 100; var med = 200; var high = 300; // public var turnOn = function(){ return heatSetting(); }; var adjustSetting = function(setting){ if(setting <= 3){ temperature = low; }if (setting >3 && setting <= 6){ temperature = med; }if (setting > 6 && setting <= 10){ temperature = high;
}return temperature; }; // private var heatSetting = function(adjustSetting){ var thermostat = adjustSetting; return thermostat; }; return{ turnOn:turnOn, adjustSetting:adjustSetting };})();
Toaster.adjustSetting(5);Toaster.adjustSetting(8);
Тостер модуля имеет частные местные жители и общедоступный интерфейс и записывается как немедленно вызываемое выражение функции (IIFE). Мы создаем функцию, немедленно вызовуте ее и захватываем возвращаемое значение.
Еще один маленький пример:
function firstName(first){ function fullName(last){ console.log(first + " " + last); } return fullName;}var name = firstName("Mister");name("Smith") // Mister Smithname("Jones"); //Mister Jones
Внутренняя функция Funnname () доступа к переменной, во-первых, во внешнем объеме, первый случай (). Даже после внутренней функции, полное имя, вернулось, у него все еще есть доступ к этой переменной Отказ Как это возможно? Цепочка объема внутренней функции включает в себя объем ее внешнего объема.
Когда вызывается функция, создается контекст выполнения и цепочка объема. Также функция получила скрытую свойство [[[SCOPE]]. Объект активации для функции инициализируется и помещен в цепочку. Затем объект активации внешней функции помещен в цепочку. В этом случае, наконец, глобальный Переменная объект Отказ
В этом примере определяется полное значение. Свойство [[[[SCOPE]] создается. Объект активации функции, содержащий функцию добавляется в цепочку области полномочий FullName. Он также добавлен в глобальный объект переменной. Эта ссылка на объект активации внешней функции позволяет получить доступ ко всем содержащим переменные сюжета. Это не получает сборщик мусора.
Это самое главное. Объект активации внешней функции, FirstName (), не может быть уничтожен после завершения выполнения, поскольку ссылка все еще существует в цепочке области полномочий FullName. После первого пояса () Завершено выполнение, ее цепочка объема для этого контекста выполнения уничтожена. Но объект активации останется в памяти, пока FullName () не разрушен. Мы можем сделать это, установив его ссылку на NULL.
Оживленный наблюдатель отметит, что мы возвращаем ссылку на FullName, а не возвращаемое значение Funnname ()!
Это то, что мы подразумеваем под неверным, постоянным звеном между и функционирующими, и это цепочка объема.
Закрытие всегда получает последнее значение из содержащего функции, поскольку хранятся ссылка на объект переменной.
Например …
var myFunctions= [];function createMyFunction(i) { return function() { console.log("My value: " + i); }; }for (var i = 0; i < 10; i++) {myFunctions[i] = createMyFunction(i);myFunctions[i]();}
My value: 0 My value: 1 My value: 2 My value: 3 My value: 4 My value: 5 My value: 6 My value: 7 My value: 8 My value: 9
Если мы вернемся к нашему оригинальному примеру области и измените одно:
a = 1;var b = 2;
function outer(z) { b = 3; c = 4; var d = 5; e = 6;
function inner() { var e = 0; d = 2 * d; return d; } return inner; // we remove the call operator, now we are returning a reference to function inner. var e;}myG = outer(1); // store a reference to function inner in the global scope (the return value of outer)myG(); // when we execute myG, inner's [[Scope]] property is copied to recreate the scope chain, // and that gives it access to the scopes that contain function inner, outter then global. We got inner and inner's got outter.
Вот несколько дополнительных примеров:
function make_calculator() { var n = 0; // this calculator stores a single number n return { add: function(a) { n += a; return n; }, multiply: function(a) { n *= a; return n; } };}
first_calculator = make_calculator();second_calculator = make_calculator();
first_calculator.add(3); // returns 3second_calculator.add(400); // returns 400
first_calculator.multiply(11); // returns 33second_calculator.multiply(10); // returns 4000
Предположим, мы хотели выполнить массив функций:
function buildList(list) { var result = []; for (var i = 0; i < list.length; i++) { result.push(function number(i) { var item = 'item' + list[i]; console.log(item + ' ' + list[i])} ); } return result;}buildList([1,2,3,4,5]);
function testList() { var fnlist = buildList([1,2,3,4,5]); for (var i = 0; i < fnlist.length; i++) { fnlist[i](i); // another IIFE with i passed as a parameter!! } } testList();
Я надеюсь, что это объяснение охвата и закрытия помогает. Играйте с узорами, которые вы видите здесь, эксперимент. На самом деле запись этой статьи была сложной – я получил гораздо более глубокое понимание, чем я, когда начал.
Ресурсы
Ydkjs.
Дмитрий Сошников, JavaScript: Core
ECMA 262.3
Переполнение стека
Ник Закас