Глубокое погружение в цепи и замки

glubokoe pogruzhenie v czepi i zamki?v=1656567034

от Кевина Терни

Как работают цепь и закрывание Scope под капотом с примерами

JC6Wko1HdOI-OS-znRe4GntOJVoHvbHE-8zc
Фото Анурага Харичандракара на Unsplash

Понимание области действия и замыканий в JavaScript

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

Прежде чем мы перейдем к закрытию, мы должны иметь понимание масштаба.

Во-первых, если вы знаете что [[scope]](диапазон в двойных скобках), то эта статья не для вас. У вас более расширенные знания и вы можете двигаться дальше.

Что…

Что объем и почему это имеет значение?

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

Другими словами, область действия касается доступа. Имеет ли функция возможность искать переменную для выполнения или манипуляции, какие переменные просматриваются?

Различают два типа: локальный и глобальный. Разрешение области действия или определение того, какие переменные куда принадлежат, начинается с внутреннего контекста и продолжается наружу, пока не будет найден идентификатор. Начнём с малого…

var firstNum = 1;
function number() {  var secondNum = 2;  return firstNum + secondNum;}
number();

Когда, почему и как… контекст исполнения

UxvNw5GMNCWBibNq99xSI1iWoGnY-39EZipV

Когда функция вызывается, она сформировывает новый контекст выполнения. Что такое контекст исполнения? Так же, как у нас есть два типа области действия, у нас есть два типа контекста исполнения. Они представляют собой глобальный контекст выполнения и контекст выполнения функции.

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

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

Итак, теперь у нас сформирован контекст выполнения, что дальше?

Каждый контекст выполнения имеет связанный объект переменной

fUmuSkkuMeZsJVFFqOMP6ViyoAyZxzcPhIbl

Во-первых, an Объект активации (недоступно по коду, но работает в фоновом режиме). Он связан с этим контекстом исполнения. Этот объект содержит все объявлены переменные, функциии параметры передается в этом контексте (его объем или диапазон доступности).

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

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

lPIm2hQe6Qz9ZTByQxWx9S8-qj93KbtRQ66G

Теперь, когда эта функция вызывается, создается «цепочка областей» этих объектов. Почему? Цепь областей – это способ связать или обеспечить систематический доступ ко всем переменным и другим функциям, к которым имеет доступ текущий контекст выполнения (в этом случае функция). [[Scope]]– это скрытый механизм, связывающий эти переменные объекты для поиска идентификаторов. Это скрыто [[Scope]]является свойством функции, созданной во время объявления, а не вызова.

Во главе цепи сферы действия, если это функция, стоит Объект активации. Этот объект активации имеет собственные объявленные переменные, аргументы и другие.

Далее, в цепочке области видимости, есть следующий объект из содержащего контекста. Если это глобальная переменная, это a Сменный объект. Если это функция, то это Объект активации. Это происходит, пока мы не достигнем глобального контекста. Поэтому вы видите, что мы начинаем от внутреннего контекста к внешнему, подумайте о русских матрешках.

Какая разница между объявленной переменной и необъявленной? Если идентификатору предшествует var, let или const, он объявляется явно и пространство памяти выделяется для использования этой переменной. Если идентификатор не объявляется явно, он неявно объявляется в глобальной области, которую мы рассмотрим в ближайшее время. Для целей этой статьи я придерживаюсь var без особой причины.

Я знаю, вышеперечисленное было немного техническим, и, честно говоря, когда я писал это, я узнал об объектах Variable и Activation только сам. Теперь, когда вы получили объяснение глубокого погружения, вот описание высокого угла.

Цепочка объема подобна цепи прототипов. Если переменная или свойство не найдено, она продолжает цепочку вверх до тех пор, пока не будет найдена или не будет выброшена ошибка. Функция создает скрытый [[scope]]свойство. Это свойство связывает внутренние области видимости с отдаленными областями. В этом случае цепочка области действия числа связана с глобальным объектом окна (контекстом, содержащим номер функции). Это то, что позволяет двигателю смотреть за пределы номера функции, чтобы найти firstNum и secondNum.

Например, возьмем тот же номер функции и изменим одну вещь:

// 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
mZ88I9Xu0AfpjMKHwLSdNTEOUMdwg6DYfFgy

Говоря о глобальной области видимости, объявления переменных, объявления неуложенных функций и выражения функций (до сих пор считаются определением переменной) рассматриваются в области глобального объекта окна в браузере. Итак, как мы видим выше, объект окна имеет свойства 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.

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

ThirdNum доступен только внутри функции, где он был объявлен. Он является инкапсулированным или частным для номера функции

У вас может возникнуть вопрос: «Имеет ли глобальная область доступа ко всему, что входит в число?» Опять же область действия работает только изнутри, внутреннего контекста, локального, к внешнему контексту, глобальному.

Начиная с локальной области видимости, мы можем сказать, что данные и переменные, завернутые в функцию, доступны только членам этой функции. Цепочка областей – это то, что связывает firstNum с number().

Когда вызывается number(), нетехнический разговор выглядит так…

Двигатель: «Номер, я даю вам новый контекст исполнения. Позвольте мне найти то, что вам нужно для бега»

Двигатель: «Хорошо, я вижу, что thirdNum явно объявлено. Я оставляю место для вас, перейдите в верхнюю часть функционального блока номера и подождите, пока я вам позвоню…

Двигатель: «Номер, я вижу secondNum, он принадлежит тебе?»

Номер: «Нет».

Двигатель: «Ладно, я вижу, что вы связаны с глобальным объектом окна, позвольте мне посмотреть за пределы вас».

Двигатель: «Окно, у меня есть идентификатор по имени secondNum, он принадлежит тебе?»

Окно: «Он явно не заявил о себе в Number из var, let, or
const, и я возьму его и оставлю место».

Двигатель: «Круто. Номер, я вижу firstNum в вашем функциональном блоке, он принадлежит вам?

Номер: «Нет».

Двигатель: «Окно, я вижу, что firstNum используется внутри Number, он ему нужен, он принадлежит тебе?»

Окно: «Да, он был объявлен»

Двигатель: «Все учтены, теперь я назначаю значение переменным»

Двигатель: Номер, я тебя казню, готов, вперед!»

Это почти все для понимания объема. Основные выводы:

  1. Поиск идентификатора работает изнутри и останавливается на первом совпадении.
  2. Существует два типа сферы действия: глобальный и локальный
  3. Цепочка областей создается при вызове функции и базируется на том, где записываются переменные и/или блоки кода (лексическая среда). Являются ли переменные или функции вложенными?
  4. В JavaScript, если идентификатор не обрабатывается var, let или const, он неявно объявляется в глобальной области.
  5. Область действия не меняется 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);
  1. Прежде чем мы что-нибудь запустим, подъемник начинается на внешнем, глобальном уровне. Поэтому мы начинаем с объявления для a переменный би объявления функции для внешний объект функции. На данный момент ничего не предназначено, у нас есть только эти два ключа, настроенные в объекте переменной глобальной области видимости.
  2. Далее мы начинаем с а = 1. Это присвоение или оператор «написать», но для него нет официальной декларации. Итак, что происходит в глобальном масштабе, а если не в «строгом режиме», то это а будет неявно объявлено, что относится к глобальному объекту переменной области видимости.
  3. Переходим к следующей строке и ищем идентификатор бчерез подъем он был учтен, и теперь мы можем назначить ему значение 2.

Пока мы имеем…

Глобальный масштаб

WS9msWmNLLYx50YR5DCjUfXfKMs-GIrr424a

4. Поскольку мы построили внешний объект функцииво время подъема мы переходим к исполнению, outer(1);

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

6. Параметр z неявно объявляется этой функции и назначается 1.

Краткое примечание: в настоящее время контекст выполнения функции создает ее.это” связывание. Это также создает массив аргументовкоторый является массивом передаваемых параметров в этом случае z. Это выходит за рамки этой статьи и позвольте мне просмотреть ее.

7. Теперь мы ищем явные объявления переменных в функция внешняя. Мы имеем dи вар е объявляется после функция внутрь.

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

Область для функции внешняя

cv8nLf3vH0T-8NFBDzxDArKSAL7n5gnLIn7w

9. Заходим внутрь внешний и начать с б = 3. Есть б объявлено? Нет. Таким образом, JavaScript использует скрытые [[scope]] имущество, связанное с функцией внешний чтобы переместиться вверх по цепочке, чтобы найтиб”. Он находит его в объекте глобальной области видимости и, поскольку мы находимся в теле функции внешнийназначаем б значение 3.

Вновь глобальный масштаб

qpQb0nuu7Y1WbN3MJwp9ermPDVShi8R9u9Sj

10. Следующая строка, c = 4. Поскольку это запись в идентификатор cбыл c явно объявлен в функции внешний? Нет, поэтому он не обнаружен поиском во внешнем объекте активации. Таким образом, он двигается вверх по цепочке областей и смотрит в глобальный объект переменной области видимости. Его там нет. Поскольку это операция записи/присвоения, глобальная область обработает ее и поместит в свой объект переменной.

Объект переменной глобальной сферы действия

kLw4Ge1eryBuP1xrBetBMr8EX9KtPsCmmjBk

11. d = 5. Да, это здесь, поэтому мы назначаем ему 5.

Область применения функции

rCC-QbTSWgdHODOvdaCuvVt39-kqwO2qCnpF

12. e = 6. Вспомните, что отстающий, вар e? Это все еще было объявлено в теле внешний и поэтому мы уже имели для него место – поэтому назначаем ему 6. Если это не было объявлено, как c, мы будем двигаться вверх по цепочке диапазона для поиска. Поскольку это операция записи, а не чтения, а не «строгого режима», она была бы помещена в глобальную область.

13. Мы приступаем к вызову функции внутренний. Мы начинаем все сначала, как и с функцией внешний: подъема, настройте объект активации и создайте скрытый [[scope]] собственность. В этом случае контекстом, содержащим, является функция внешнийи внешний «указывает» на глобальный масштаб.

Область применения внутрь

uM9gVd9l86C9w6eMypi55J1Xxi0f0CaThFSv

14. Теперь с e и вообще, переменные, имеющие одинаковые имена, работают следующим образом. Поскольку поиск идентификатора начинается от внутренней области до крайней наружной, поиск прекращается при первом нахождении этого идентификатора. В организме внутренний, мы видим вар e= 0, готово, остановись, не идти дальше. The e в теле функции внешний является «недоступным». Обычно используется термин «тенение» e в функции внутренний «затеняет» или скрывает e в функции внешний.

15. Следующая строка d = 2* d. Прежде чем присвоить значение d слева, мы должны оценить выражение справа, 2* d. Так как d не является локальным по объему внутренниймы двигаемся вверх по цепочке областей, чтобы найти переменную для d и имеет ли оно связанное с ним значение. Мы находим его в внешний сфера в функции внешний и именно там меняется значение.

Область применения функции

UfIUjuTpQ3t3JGOHDPpY0rxS6ZvpCHz0Eleo

Здесь главное то inner манипулирует данными в своей внешней области!

16. Функция внутренний возвращает значение d10

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);

Модуль Toaster имеет приватные локальные команды и открытый интерфейс и записан как выражение функции немедленного вызова (IIFE). Мы создаем функцию, немедленно вызываем ее и получаем возвращаемое значение.

Еще один небольшой пример:

function firstName(first){    function fullName(last){        console.log(first + " " + last);    }    return fullName;}var name = firstName("Mister");name("Smith") // Mister Smithname("Jones"); //Mister Jones

Внутренняя функция fullName() обращается к переменной, во-первых, в её наружной области, firstName(). Даже после возвращения внутренней функции fullName она все еще имеет доступ к этой переменной. Как это возможно? Цепь внутренней функции включает в себя область ее наружной области.

Когда функция вызывается, создается контекст выполнения и цепочка областей. Также функция get скрыта [[Scope]]свойство. Объект активации для функции инициализируется и размещается в цепочке. Затем объект активации внешней функции помещается в цепочку. В этом случае, наконец, глобальное Сменный объект.

В этом примере определено полное имя. А [[Scope]]создано свойство. Содержащий объект активации добавляется в цепочку области действия fullName. Он также добавляется к объекту глобальной переменной. Эта ссылка на объект активации внешней функции позволяет получить доступ ко всем переменным областям, содержащим. Он не собирает мусор.

Это самое важное. Объект активации внешней функции firstName() не может быть уничтожен после завершения выполнения, поскольку ссылка все еще существует в цепочке действия fullName. После имени( )
исполнение завершается, его цепочка областей для этого контекста исполнения уничтожается. Но объект активации останется в памяти, пока fullName() не будет уничтожен. Мы можем сделать это, установив его ссылку на null.

Вопросительный наблюдатель заметит, что мы возвращаем ссылку на fullName, а не на значение fullName()!

Это то, что мы подразумеваем под неявной, постоянной связью между функцией и ее цепью действия.

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

Например …

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

Переполнение стека

Ник Закас

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *