Глубокое понимание области видимости и замыканий

JavaScript Примечания
Глубокое понимание области видимости и замыканий

предисловие

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

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

Принципиальный анализ

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

Примитивные и эталонные значения переменных

Переменные могут хранить два разных типа данных:Примитивное значение против эталонного значения

  • использоватьОсновной тип упаковкиСозданная стоимость является исходной стоимостью
  • использоватьтип ссылкиСозданное значение является эталонным значением

Давайте посмотрим, какие основные типы обертки и ссылочные типы имеют:

При присвоении значения переменной движок JavaScript должен определить, является ли значениеИсходное значениеещеисходная величина:

  • спастиИсходное значениеДоступ к переменной осуществляется по значению, которое хранится в памяти стека.
  • спастиисходная величинаДоступ к переменной осуществляется по ссылке, она хранится в куче памяти.

Ссылочное значение — это объект, хранящийся в памяти.JavaScript не допускает прямого доступа к памяти, поэтому пространство памяти, в котором находится объект, находится напрямую.

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

Эксплуатация свойств

Примитивные значения и ссылочные значения определяются аналогичным образом, создавая переменную и затем присваивая ей значение.

Однако то, что можно сделать со значением после того, как переменная удерживает значение, имеет большое значение.

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

Далее, давайте возьмем пример для проверки:

let person = {};
person.name = "神奇的程序员";
console.log(person.name); // 神奇的程序员

let person1 = "";
person1.name = "神奇的程序员";
console.log(person1.name); // undefined

В приведенном выше коде:

  • Мы создалиpersonпустой объект, который является эталонным значением
  • Затем дайтеpersonДобавьте атрибут имени и назначьте его
  • Затем распечатайтеperson.name, как и ожидалось, даст правильный результат
  • Далее мы создалиperson1пустая строка, которая является исходным значением
  • Затем мы даемperson1Добавьте атрибут имени и назначьте его
  • Наконец, распечатайтеperson1.name, значение не определено

Результат выполнения следующий:

image-20210318235346264

Примечание ⚠️: Когда мы используем базовый тип оболочки для создания переменной, результирующее значение является объектом, который является ссылочным значением, к которому можно добавлять свойства и методы. Например:

let person2 = new String("");
person2.name = "神奇的程序员";
console.log(person2.name);

копия значения

Когда мы копируем значение одной переменной в другую переменную, движок JS обрабатывает исходное значение и опорное значение по-разному, поэтому давайте проанализируем это подробно.

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

Поясним это на примере:

let age = 20;
let tomAge = age;

let obj = {};
let tomObj = obj;
obj.name = "tom";
console.log(tomObj.name); // tom

В приведенном выше коде:

  • Мы создали переменную с именемage, присвоено значение 20, которое является примитивным значением
  • Впоследствии мы создалиtomAgeПеременная, назначьте его возраста.
  • Далее мы создаем пустой объект с именемobj.
  • Впоследствии мы создалиtomObjобъект, назначьте его объекту.
  • Затем мы даемobjДобавленnameимущество, закрепленное заtom.
  • Наконец, мы печатаемtomObj.name, установлено, что значение равноtom.

Давайте сначала проанализируем приведенный выше примерageа такжеtomAge,tomAge = ageОн принадлежит копии исходного значения.Поскольку исходное значение хранится в памяти стека, оно откроет новую область в стеке и скопирует значение возраста в новую область, как показано на следующем рисунке:

image-20210319152045841

Наконец, давайте проанализируем приведенный выше пример.objа такжеtomObj:

  • tomObj = objПринадлежит репликации эталонного значения.
  • Ссылочное значение хранится в куче памяти, поэтому оно копирует указатель.

В приведенном выше примере кода и obj, и tomObj указывают на одно и то же место в памяти кучи, а указатель tomObj указывает на obj.Глубокое понимание цепочки прототипов и наследованияВ статье мы знаем, что у объекта есть цепочка прототипов, поэтому, когда мы добавим атрибут name в obj, tomObj также будет содержать этот атрибут.

Далее мы рисуем картинку для описания вышеуказанных слов:

image-20210319204337610

передача параметров

После того, как у нас есть предвестие первых двух глав, давайте проанализируем, как передаются параметры функции.

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

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

Давайте используем пример для проверки правил передачи параметров по значению, как показано ниже:

function add(num) {
  num++;
  return num;
}

let count = 10;
const result = add(count);
console.log(result); // 11
console.log(count); // 10

В приведенном выше коде:

  • Во-первых, мы создаемaddфункция, которая принимает один параметрnum
  • Внутри функции параметр автоматически увеличивается, а затем возвращается.
  • Далее мы объявляемcountпеременной и присвойте ей значение 10.
  • передачаaddфункция, декларацияresultпеременная для получения возвращаемого значения функции.
  • Наконец, распечатайте результат и подсчитайте, результаты следующие:11,10

мы звонимaddфункция, прошлаcountвходят параметры, и при обработке внутри функции он поставитcountСкопируйте копию значения в локальную переменную. Когда оно изменяется внутренне, оно изменяет скопированное значение, поэтому мы увеличиваем его внутренне.numНе влияет на внешнюю часть функцииcountПеременная.

Результаты приведены ниже:

image-20210319235807949

Далее проверим правила передачи параметров по ссылке на примере, следующим образом:

function setAge(obj) {
  obj.age = 10;
  obj = {};
  obj.name = "神奇的程序员";
  return obj;
}
let tom = {};
const result1 = setAge(tom);
console.log("tom.age", tom.age); // 10
console.log("tom.name", tom.name); // undefined
console.log("result1.age", result1.age); // undefined
console.log("result1.name", result1.name); // 神奇的程序员

В приведенном выше коде:

  • Мы создалиsetAgeфункция, которая принимает объект
  • Внутри функции добавлен новый объект параметраageсвойство, присвойте ему значение 10
  • Затем мы присваиваем объект параметра пустому объекту, добавляем атрибут имени и присваиваем его.
  • Наконец, верните объект параметра.
  • Далее мы создаемtomпустой объект
  • Затем передайте объект tom в качестве параметраsetAgeметод и вызов, объявитьresult1переменная для получения возвращаемого значения
  • Наконец, мы печатаемtomобъект сresult1Свойства объекта, результат выполнения соответствует правилам передачи параметров по ссылке

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

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

Наконец, объект параметра, который мы возвращаем внутри функции, который указывает на новый адрес, естественно, имеет только атрибут имени.

так,tomобъект толькоageАтрибуты,result1объект толькоnameАтрибуты.

Результаты приведены ниже:

image-20210320001519276

контекст и область выполнения

Разобравшись с переменными, давайте узнаем о контексте выполнения.

контекст выполнения вJavaScriptСреда – относительно важная концепция. В качестве структуры данных она использует стек. Для удобства в этой статье она сокращена до контекста. Ее правила таковы:

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

глобальный контекст

Глобальный контекст относится к самому внешнему контексту, который определяется в соответствии с хост-средой.Конкретные правила таковы:

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

контекст функции

Каждая функция имеет свой собственный контекст.Далее давайте посмотрим на правила контекста выполнения функций:

  • Когда функция начинает выполняться, ее контекст помещается в стек контекста.
  • После выполнения функции стек контекста извлекает контекст функции.
  • Вернуть управление предыдущему контексту выполнения
  • Поток выполнения программы JS контролируется этим стеком контекста.

Давайте возьмем пример, чтобы проиллюстрировать стек контекста:

function fun3() {
    console.log('fun3')
}

function fun2() {
    fun3();
}

function fun1() {
    fun2();
}

fun1(); 

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

Когда функция выполняется, контекст выполнения создается и помещается в стек контекста выполнения.Когда функция выполняется, контекст выполнения функции извлекается из стека.

Зная приведенные выше концепции, мы возвращаемся к приведенному выше коду:

  • воплощать в жизньfun1()функция, контекст создается и помещается в стек контекста выполнения
  • fun1Функция вызывается сноваfun2функция, поэтому создайтеfun2Контекст функции, который помещается в стек контекста.
  • fun2Функция вызывается сноваfun3функция, поэтому создайтеfun3Контекст функции, который помещается в стек контекста.
  • fun3После выполнения функция извлекается из стека.
  • fun2После выполнения функция извлекается из стека.
  • fun1После выполнения функция извлекается из стека.

Давайте нарисуем схему, чтобы понять вышеописанный процесс:

image-20210322100055908

Объем и цепочка объемов

После того, как мы понимаем контекст, мы можем легко понять масштаб.

При выполнении кода контекста набор переменных, к которым может получить доступ текущий контекст,объем.

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

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

Активный объект изначально имеет только одну переменную по умолчанию:arguments(глобальный контекст не существует), следующий объект переменной в цепочке областей действия находится в содержащем контексте, а следующий объект — в содержащем контексте. И так до глобального контекста.

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

Разрешение идентификатора во время выполнения кода выполняется путем пошагового поиска имени идентификатора по цепочке областей видимости Процесс поиска всегда начинается в самом начале цепочки областей видимости и работает в обратном направлении, пока идентификатор не будет найден. (Если идентификатор не найден, будет сообщено об ошибке)

Далее, давайте проиллюстрируем приведенное выше утверждение на примере:

var name = "神奇的程序员";

function changeName() {
  console.log(arguments);
  name = "大白";
}

changeName();
console.log(name); // 大白

В приведенном выше коде:

  • функцияchangeNameЦепочка областей видимости содержит два объекта контекста: собственный объект контекста функции, объект глобального контекста
  • argumentsв своем собственном переменном объекте,nameв переменном объекте в глобальном контексте
  • Мы можем получить доступ внутри функцииargumentsа такжеnameпросто потому, что их можно найти через цепочку областей видимости.

Результат выполнения следующий:

image-20210320171253397

Далее, давайте рассмотрим пример, объясняющий процесс поиска в цепочке областей видимости:

var name = "神奇的程序员";

function changeName() {
  let insideName = "大白";

  function swapName() {
    let tempName = insideName;
    insideName = name;
    name = tempName;

    // 可以访问tempName、insideName、name
  }
  // 可以访问insideName、name
  swapName();
}
// 可以访问name
changeName();
console.log(name);

Приведенный выше код:

  • В цепочке областей видимости есть три объекта контекста:swapNameобъект контекста функции,changeNameОбъект контекста функции, объект глобального контекста
  • существуетswapNameВнутри функции у нас есть доступ ко всем переменным, определенным в трех объектах контекста.
  • существуетchangeNameВнутри функции мы можем получить доступ к ее собственному объекту контекста и переменным, определенным в объекте глобального контекста.
  • В глобальном контексте мы можем получить доступ только к тем переменным, которые существуют в глобальном контексте.

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

Затем мы рисуем диаграмму, описывающую цепочку областей действия приведенного выше примера, следующим образом:

image-20210320181607527

Примечание ⚠️: Параметр функции считается переменной в текущем контексте, поэтому он подчиняется тем же правилам доступа, что и другие переменные в этом контексте.

переменная область видимости

Ключевые слова для объявления переменных в JavaScript:var,let,const, области видимости переменных, объявленных разными ключевыми словами, сильно различаются, поэтому давайте шаг за шагом проанализируем их области видимости.

объем функции

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

Если переменная не объявлена ​​для инициализации напрямую, то она автоматически добавляется в глобальный контекст.

Давайте возьмем пример, чтобы проверить приведенное выше утверждение:

function getResult(readingVolume, likes) {
  var total = readingVolume + likes;
  globalResult = total;
  return total;
}

let result = getResult(200, 2);
console.log("globalResult = ", globalResult); // 202
console.log(total); // ReferenceError: total is not defined

В приведенном выше коде:

  • Мы объявляемgetResultфункция, которая принимает два аргумента
  • используется внутри функцииvarобъявляетtotalпеременной и присвоить ее как сумму двух параметров.
  • Внутри функции мы также напрямую инициализируем функцию с именемglobalResultпеременная и назначьте ее какtotalпеременное значение
  • Наконец, верните значение total.

мы называемgetResultфункция, передача параметров200а также2, а затем распечататьglobalResultа такжеtotalзначение , находимglobalResultЗначение распечатывается нормально,totalОн сообщит об ошибке undefined, а результат выполнения полностью соответствует приведенным выше словам.

Результат выполнения следующий:

image-20210320204933307

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

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

console.log(name);// undefined
var name = "神奇的程序员";
function getName() {
  console.log(name); // undefined
  var name = "大白";
  return name;
}
getName();

Приведенный выше код:

  • Сначала мы печатаем переменную имени, затем используемvarКлючевое слово объявлено, а напечатанное значениеundefined
  • Впоследствии мы объявилиgetNameФункция функции, переменная имени сначала разрешена внутри функции, а затем объявлена, допустимое значениеgetName
  • Наконец, вызовите метод getName.

Будь то в глобальном контексте или в контексте функции, мы вызываем переменную, значение которойundefined, доказано, что ошибки нетvarОбъявление переменной вызывает подъем переменной.

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

использоватьletПеременная, объявленная с помощью ключевого слова, будет иметь свой собственный блок области видимости на уровне блока, а область действия на уровне блока определяется ближайшей парой фигурных скобок.{}задавать. То есть,if,while,for,functionПеременные, объявленные с помощью let внутри блока, имеют область действия в{}Внутри даже одного блока, в котором переменная объявлена ​​с помощью let, ее область видимости также определяется в{}внутренний.

Возьмем пример для проверки:

let result = true;
if (result) {
  let a;
}
console.log(a); // ReferenceError: a is not defined

while (result) {
  let b;
  result = false;
}
console.log(b); // ReferenceError: b is not defined

function foo() {
  let c;
}
console.log(c); // ReferenceError: c is not defined

{
  let d;
}
console.log(d); // ReferenceError: a is not defined

В приведенном выше коде мы объявили переменные в функциях if, while, и отдельный {}, и при вызове переменной внутри блока вне блока будет сообщено об ошибкеReferenceError: xx is not defined, помимо функции, если использовать внутри блокаvarключевое слово для объявления, тогда к переменным внутри блока можно будет нормально обращаться вне блока.

Результаты приведены ниже:

image-20210320214600031

использоватьletПри объявлении переменной ее нельзя объявить повторно в одной и той же области видимости, если она повторяется, она будет выброшенаSyntaxErrorошибка.

Возьмем пример для проверки:

let a = 10;
let a = 11;
console.log(a); // SyntaxError: Identifier 'a' has already been declared

var b = 10;
var b = 11;
console.log(b); // 11

В приведенном выше коде:

  • Мы неоднократно объявляли две переменные с одним и тем же именем, используя leta
  • Мы повторили две переменные с одинаковым именем, используя varb

Когда мы печатаем a, мы сообщим об ошибкеSyntaxError: Identifier 'a' has already been declared

Когда мы печатаем b, повторное объявление var будет проигнорировано, в зависимости от того, что будет после, результат будет таким, поэтому значение равно11

Примечание ⚠️: Строго говоря, объявленные let переменные также поднимаются во время выполнения, но вы не можете фактически использовать переменную let до объявления из-за «временной мертвой зоны». Поэтому изJavaScriptС точки зрения кода, let's hoisting отличается от var.

объявление константы

использоватьconstПеременной, объявленной с помощью ключевого слова, должно быть присвоено начальное значение. После объявления ей нельзя повторно присвоить новое значение в любой момент ее жизненного цикла.

Возьмем пример для проверки:

const name = "神奇的程序员";
const obj = {};
obj.name = "神奇的程序员";
name = "大白";
obj = { name: "大白" };

В приведенном выше коде:

  • Мы объявили две переменные, используя constname,obj
  • Добавляем атрибут name в obj, мы не переназначали obj, так что его можно добавить нормально
  • Далее мы присваиваем новое значение имени, и в это время будет сообщено об ошибкеTypeError: Assignment to constant variable.
  • Наконец, мы присваиваем obj новое значение, и будет сообщено о той же ошибке.

Результаты приведены ниже:

image-20210320222904217

В приведенном выше примере используется объявление constobjВы можете изменить его свойства.Если вы хотите, чтобы весь объект был неизменяемым, вы можете использоватьObject.freeze(),Следующим образом:

const obj1 = Object.freeze({ name: "大白" });
obj1.name = "神奇的程序员";
obj1.age = 20;
console.log(obj1.name);
console.log(obj1.age);

Результаты приведены ниже:

image-20210320223429928

Примечание ⚠️: поскольку объявление const подразумевает, что значение переменной имеет один тип и не может быть изменено, компилятор среды выполнения JavaScript может заменить все ее экземпляры фактическим значением, не выполняя поиск переменной в таблице поиска (V8 двигатель делает эту оптимизацию).

переменное время жизни

Далее рассмотрим жизненный цикл переменных.

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

Давайте возьмем пример для иллюстрации:

var a = 10;
function getName() {
  var name = "神奇的程序员";
}

В приведенном выше коде:

  • ПеременнаяaВ глобальном контексте его жизненный цикл постоянен.
  • Переменнаяnameв контексте функции, когдаgetNameПосле завершения выполнения переменная имени будет уничтожена.

Понимание замыканий

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

Поясним это на примере:

var selfAdd = function() {
  var a = 1;
  return function() {
    a++;
    console.log(a);
  };
};

const addFn = selfAdd();
addFn(); // 打印2
addFn(); // 打印3
addFn(); // 打印4
addFn(); // 打印5

В приведенном выше коде:

  • Мы объявляемselfAddФункция
  • Переменная определена внутри функцииa
  • Впоследствии внутри функции возвращается ссылка на анонимную функцию.
  • Внутри анонимной функции он имеет доступ кselfAddПеременные в контексте функции
  • мы звонимselfAdd()функция, она возвращает ссылку на анонимную функцию
  • Поскольку на анонимную функцию продолжают ссылаться в глобальном контексте, у нее есть причина не уничтожаться.
  • Поэтому здесь генерируется замыкающая структура,selfAddПродлен срок жизни переменной в контексте функции.

Далее мы используем пример, чтобы объяснить роль замыканий:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>学习闭包</title>
  <script type="text/javascript" src="js/index.js"></script>
</head>
<body>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
</body>
</html>
window.onload = function() {
  const divs = document.getElementsByTagName("div");
  for (var i = 0; i < divs.length; i++) {
    divs[i].onclick = function() {
        alert(i);
      };
  }
};

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

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

window.onload = function() {
  const divs = document.getElementsByTagName("div");
  for (var i = 0; i < divs.length; i++) {
    (function(i) {
      divs[i].onclick = function() {
        alert(i);
      };
    })(i);
  }
};

В приведенном выше коде:

  • Внутри цикла for мы используем самовыполняющуюся функцию, чтобы заключить значение i каждого цикла.
  • При поиске переменной i по цепочке областей видимости в функции события сначала будет найдено значение i, заключенное в окружение замыкания.
  • В коде 5 разделов, поэтому i здесь0, 1, 2, 3, 4, что соответствует нашим ожиданиям

Используйте блочную область разумно

В выражении цикла for приведенного выше кода используйтеvarпеременная определенаi,мы вобъем функцииглавы, использоватьvarПри объявлении переменной переменная автоматически добавляется в ближайший контекст, здесь переменнаяiбыл повышен доwindow.onload функция, поэтому каждый раз, когда мы выполняем цикл for,iЗначение будет перезаписано.После выполнения синхронного кода при выполнении асинхронного кода полученное значение является перезаписанным значением.

В дополнение к использованию замыканий для решения вышеуказанных проблем, мы также можем использовать let для их решения.Код выглядит следующим образом:

window.onload = function() {
  const divs = document.getElementsByTagName("div");
  for (let i = 0; i < divs.length; i++) {
    // let的隐藏作用域,可以理解成
    // {let i = 0}
        // {let i = 1}
    // {let i = 2}
    // {let i = 3}
    // {let i = 4}
    divs[i].onclick = function() {
      alert(i);
    };
  }
};

В выражении цикла for приведенного выше кода мы используемletпеременная объявленаi,мы вобласть действия блокаглавы, использоватьletПеременная, объявленная ключевым словом, будет иметь свой собственный блок области видимости, поэтому вforиспользуется в выражениях циклаletЭквивалентно использованию let в блоке кода, поэтому:

  • for (let i = 0; i < divs.length; i++) Между скобками этого кода есть скрытая область
  • for (let i = 0; i < divs.length; i++) {循环体}Прежде чем каждый цикл выполнит тело цикла, движок JSiпереобъявлен и инициализирован один раз в контексте тела цикла

Поскольку let имеет собственную область действия в блоке кода, каждое значение let, используемое в выражении цикла for, будет существовать в отдельной области действия и не будет перезаписано.

нанесение на поверхность

Далее мы используем несколько примеров, чтобы закрепить сказанное ранее.

повышение масштаба

Код показан ниже, мы объявили функцию внутри блокаfoo(), который инициализируетfooпеременная, которой присвоено значение 1. объявить сноваfoo()функция, снова измените переменнуюfooценность .

{
  function foo() {
    console.log(1111);
  }
  foo(); // 2222
  foo = 1;
  // 报错:此时foo的值已经是1了,而并非一个函数
  // console.log(foo());
  function foo() {
    console.log(2222);
  }
  foo = 2;
  console.log(foo); // 2
}
console.log(foo); // 1

В приведенном выше коде:

  • Внутри блока функцияfoo()Он объявляется дважды, потому что функция поведения JS-движка по умолчанию будет продвинута, поэтому функция, объявленная последней, наконец, будет выполнена.
  • foo = 1Принадлежность к поведению прямой инициализации автоматически добавляется в глобальный контекст.
  • Поскольку в области блокаfooэто функция, которая выполняетfoo = 1начнет искать цепочку областей видимости, найденную в области видимости блокаfoo, поэтому присвойте ему значение 1.
  • такой же,foo = 2также начнет искать цепочку областей видимости, найденную в области видимости блока.foo, поэтому присвойте ему значение 2.

Объединив вышеперечисленное, в блоке датьfooПри назначении он сначала находит переменный объект в области блока и не изменяет переменный объект в глобальном контексте.foo, так что вне блокаconsole.log(foo)Значение по-прежнему остается значением, которое оно имело, когда переменная была продвинута, когда она была впервые инициализирована внутри блока.

стек контекста выполнения

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

var name = "神奇的程序员";
function changeName() {
  var name = "大白";
  function f() {
    return name;
  }
  return f();
}
const result = changeName();
console.log(result);// 大白

var name = "神奇的程序员";
function changeName() {
  var name = "大白";
  function f() {
    return name;
  }
  return f;
}
const result = changeName()();
console.log(result); // 大白

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

  • Первый кусок кода,changeName()Вызывается внутри функцииf()функцию и вернуть результат ее выполнения
  • Второй кусок кода,changeName()Функция возвращает напрямуюfСсылка на функцию образует замыкающую структуру.

они встек контекста выполненияПорядок хранения в файле тоже сильно отличается, разберем первый кусок кода:

  • воплощать в жизньchangeName()функцию, создайте контекст выполнения и поместите его в стек контекста
  • changeName()Вызывается внутри функцииf()функция, которая создает контекст выполнения и помещает его в стек контекста
  • f()После выполнения функция извлекается из стека.
  • changeName()После выполнения функция извлекается из стека.

Давайте нарисуем диаграмму, объясняющую описанный выше процесс, следующим образом:

image-20210322104014150

Наконец, давайте проанализируем второй фрагмент кода:

  • воплощать в жизньchangeName()функцию, создайте контекст выполнения и поместите его в стек контекста
  • changeName()После выполнения функции извлеките стек и вернитесьf()ссылка на функцию
  • воплощать в жизньf()функцию, создайте контекст выполнения и поместите его в стек контекста
  • f()После выполнения функция извлекается из стека.

Давайте нарисуем диаграмму, объясняющую описанный выше процесс, следующим образом:

image-20210322105200831

каррирование функций

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

давайте возьмемСуммирование неизвестных параметровПример для объяснения каррирования, код выглядит следующим образом:

function unknownSum() {
  // 存储每次函数调用时的参数
  let arr = [];
  const add = (...params) => {
    // 拼接新参数
    arr = arr.concat(params);
    return add;
  };

  // 对参数进行求和
  add.toString = function() {
    let result = 0;
    // 对arr中的元素进行求和
    for (let i = 0; i < arr.length; i++) {
      result += arr[i];
    }
    return result + "";
  };

  return add;
}
const result1 = unknownSum()(1, 6, 7, 8)(2)(3)(4);
console.log("result1 =", result1.toString());

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

В приведенном выше коде:

  • Мы объявили имяunknownSum()Функция
  • объявлен внутри функцииarrМассив, используемый для сохранения параметров, передаваемых каждый раз
  • Функция реализуетaddфункция, которая используется для соединения входящего массива параметров сarrмножество
  • Функция переписана внутриaddфункциональныйtoString()метод, даarrМассив суммируется и возвращается результат
  • Наконец, внутри функции returnaddСсылка на функцию, образующую замыкающую структуру

мы звонимunknownSumКогда функция вызывается в первый раз()вернусьaddСсылка на функцию, последующие вызовы()называютсяaddфункция, параметры передаются вaddПосле функции из-за замыкания внутренняя функция функцииarrПеременная не уничтожается, поэтомуaddФункция будет кэшировать параметры вarrв переменной.

последний звонокaddфункциональныйtoStringметод, даarrПараметры в кэше суммируются.

Результат выполнения следующий:

image-20210322112033471

кодовый адрес

Эта статья является третьей в серии "Изучение принципов JS". Пожалуйста, перейдите к полному маршруту этой серии:Изучение принципов JS (1) 》Планирование маршрута обучения

Весь пример кода из этой серии статей можно найти по адресу:js-learning

напиши в конце

На данный момент статья опубликована.

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

Если вы заинтересованы во мне, пожалуйста, перейдите на мойперсональный сайт,Узнать больше о.

  • Если в статье есть ошибки, исправьте их в комментариях, если статья вам поможет, ставьте лайк и подписывайтесь 😊
  • Эта статья была впервые опубликована на Наггетс, перепечатка без разрешения запрещена 💌