предисловие
Переменная в JavaScript имеет свободный тип, нет правил, определяющих, какой тип данных она должна содержать, ее значение и тип данных могут меняться во время выполнения.
Такие правила проектирования очень эффективны, но они также вызывают много проблем, таких как область действия и замыкание, которые мы обсудим в этой статье.Заинтересованные разработчики могут прочитать эту статью.
Принципиальный анализ
Прежде чем понять область действия и замыкания, нам необходимо глубоко проанализировать переменные.
Примитивные и эталонные значения переменных
Переменные могут хранить два разных типа данных:Примитивное значение против эталонного значения
- использоватьОсновной тип упаковкиСозданная стоимость является исходной стоимостью
- использоватьтип ссылкиСозданное значение является эталонным значением
Давайте посмотрим, какие основные типы обертки и ссылочные типы имеют:
- Базовая упаковка Тип:Number,String,Boolean,Undefined,Null,Symbol,BigInt
- Тип ссылки:Array,Function,Date,RegExpЖдать
При присвоении значения переменной движок 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
, значение не определено
Результат выполнения следующий:
Примечание ⚠️: Когда мы используем базовый тип оболочки для создания переменной, результирующее значение является объектом, который является ссылочным значением, к которому можно добавлять свойства и методы. Например:
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
Он принадлежит копии исходного значения.Поскольку исходное значение хранится в памяти стека, оно откроет новую область в стеке и скопирует значение возраста в новую область, как показано на следующем рисунке:
Наконец, давайте проанализируем приведенный выше пример.obj
а такжеtomObj
:
-
tomObj = obj
Принадлежит репликации эталонного значения. - Ссылочное значение хранится в куче памяти, поэтому оно копирует указатель.
В приведенном выше примере кода и obj, и tomObj указывают на одно и то же место в памяти кучи, а указатель tomObj указывает на obj.Глубокое понимание цепочки прототипов и наследованияВ статье мы знаем, что у объекта есть цепочка прототипов, поэтому, когда мы добавим атрибут name в obj, tomObj также будет содержать этот атрибут.
Далее мы рисуем картинку для описания вышеуказанных слов:
передача параметров
После того, как у нас есть предвестие первых двух глав, давайте проанализируем, как передаются параметры функции.
В 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
Переменная.
Результаты приведены ниже:
Далее проверим правила передачи параметров по ссылке на примере, следующим образом:
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
Атрибуты.
Результаты приведены ниже:
контекст и область выполнения
Разобравшись с переменными, давайте узнаем о контексте выполнения.
контекст выполнения в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
После выполнения функция извлекается из стека.
Давайте нарисуем схему, чтобы понять вышеописанный процесс:
Объем и цепочка объемов
После того, как мы понимаем контекст, мы можем легко понять масштаб.
При выполнении кода контекста набор переменных, к которым может получить доступ текущий контекст,объем.
Когда код контекста выполняется, он создает переменный объектцепочка прицелов, который определяет порядок, в котором код в различных контекстах обращается к переменным и функциям.
Переменный объект контекста, в котором выполняется код, всегда в начале цепочки областей видимости, если контекст является функцией, егоАктивный объектиспользуется как переменный объект.
Активный объект изначально имеет только одну переменную по умолчанию:arguments
(глобальный контекст не существует), следующий объект переменной в цепочке областей действия находится в содержащем контексте, а следующий объект — в содержащем контексте. И так до глобального контекста.
Объект переменных глобального контекста всегда является последним объектом переменных в цепочке областей видимости.
Разрешение идентификатора во время выполнения кода выполняется путем пошагового поиска имени идентификатора по цепочке областей видимости Процесс поиска всегда начинается в самом начале цепочки областей видимости и работает в обратном направлении, пока идентификатор не будет найден. (Если идентификатор не найден, будет сообщено об ошибке)
Далее, давайте проиллюстрируем приведенное выше утверждение на примере:
var name = "神奇的程序员";
function changeName() {
console.log(arguments);
name = "大白";
}
changeName();
console.log(name); // 大白
В приведенном выше коде:
- функция
changeName
Цепочка областей видимости содержит два объекта контекста: собственный объект контекста функции, объект глобального контекста -
arguments
в своем собственном переменном объекте,name
в переменном объекте в глобальном контексте - Мы можем получить доступ внутри функции
arguments
а такжеname
просто потому, что их можно найти через цепочку областей видимости.
Результат выполнения следующий:
Далее, давайте рассмотрим пример, объясняющий процесс поиска в цепочке областей видимости:
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
Внутри функции мы можем получить доступ к ее собственному объекту контекста и переменным, определенным в объекте глобального контекста. - В глобальном контексте мы можем получить доступ только к тем переменным, которые существуют в глобальном контексте.
Благодаря анализу приведенного выше примера мы знаем, что поиск в цепочке областей видимости происходит изнутри наружу, внутренний может получить доступ к внешним переменным, а внешний не может получить доступ к внутренним переменным.
Затем мы рисуем диаграмму, описывающую цепочку областей действия приведенного выше примера, следующим образом:
Примечание ⚠️: Параметр функции считается переменной в текущем контексте, поэтому он подчиняется тем же правилам доступа, что и другие переменные в этом контексте.
переменная область видимости
Ключевые слова для объявления переменных в 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, а результат выполнения полностью соответствует приведенным выше словам.
Результат выполнения следующий:
использовать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
ключевое слово для объявления, тогда к переменным внутри блока можно будет нормально обращаться вне блока.
Результаты приведены ниже:
использовать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
В приведенном выше коде:
- Мы неоднократно объявляли две переменные с одним и тем же именем, используя let
a
- Мы повторили две переменные с одинаковым именем, используя var
b
Когда мы печатаем 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: "大白" };
В приведенном выше коде:
- Мы объявили две переменные, используя const
name
,obj
- Добавляем атрибут name в obj, мы не переназначали obj, так что его можно добавить нормально
- Далее мы присваиваем новое значение имени, и в это время будет сообщено об ошибке
TypeError: Assignment to constant variable.
- Наконец, мы присваиваем obj новое значение, и будет сообщено о той же ошибке.
Результаты приведены ниже:
В приведенном выше примере используется объявление constobj
Вы можете изменить его свойства.Если вы хотите, чтобы весь объект был неизменяемым, вы можете использоватьObject.freeze()
,Следующим образом:
const obj1 = Object.freeze({ name: "大白" });
obj1.name = "神奇的程序员";
obj1.age = 20;
console.log(obj1.name);
console.log(obj1.age);
Результаты приведены ниже:
Примечание ⚠️: поскольку объявление 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()
После выполнения функция извлекается из стека.
Давайте нарисуем диаграмму, объясняющую описанный выше процесс, следующим образом:
Наконец, давайте проанализируем второй фрагмент кода:
- воплощать в жизнь
changeName()
функцию, создайте контекст выполнения и поместите его в стек контекста -
changeName()
После выполнения функции извлеките стек и вернитесьf()
ссылка на функцию - воплощать в жизнь
f()
функцию, создайте контекст выполнения и поместите его в стек контекста -
f()
После выполнения функция извлекается из стека.
Давайте нарисуем диаграмму, объясняющую описанный выше процесс, следующим образом:
каррирование функций
Каррирование функций — это идея кэширования результата функции, которая представляет собой применение замыканий.
давайте возьмемСуммирование неизвестных параметровПример для объяснения каррирования, код выглядит следующим образом:
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
Массив суммируется и возвращается результат - Наконец, внутри функции return
add
Ссылка на функцию, образующую замыкающую структуру
мы звонимunknownSum
Когда функция вызывается в первый раз()
вернусьadd
Ссылка на функцию, последующие вызовы()
называютсяadd
функция, параметры передаются вadd
После функции из-за замыкания внутренняя функция функцииarr
Переменная не уничтожается, поэтомуadd
Функция будет кэшировать параметры вarr
в переменной.
последний звонокadd
функциональныйtoString
метод, даarr
Параметры в кэше суммируются.
Результат выполнения следующий:
кодовый адрес
Эта статья является третьей в серии "Изучение принципов JS". Пожалуйста, перейдите к полному маршруту этой серии:Изучение принципов JS (1) 》Планирование маршрута обучения
Весь пример кода из этой серии статей можно найти по адресу:js-learning
напиши в конце
На данный момент статья опубликована.
яудивительный программист, фронтенд-разработчик.
Если вы заинтересованы во мне, пожалуйста, перейдите на мойперсональный сайт,Узнать больше о.
- Если в статье есть ошибки, исправьте их в комментариях, если статья вам поможет, ставьте лайк и подписывайтесь 😊
- Эта статья была впервые опубликована на Наггетс, перепечатка без разрешения запрещена 💌