Область видимости — очень простая, но важная концепция в JS, и она часто упоминается в интервью. В этой статье подробно объясняется эта концепция и другие связанные с ней концепции, включая продвижение объявления, область действия на уровне блоков, цепочку областей действия и цепочку областей действия, расширение и т. д.
что такое объем
Первая проблема заключается в том, что нам нужно выяснить, что такое область действия, которая не является уникальной концепцией для JS, а является концепцией, обычно используемой в области программирования. В качестве примера возьмем следующее утверждение:
let x = 1;
Это простое утверждение на самом деле содержит несколько основных понятий:
- Переменная(переменная): Здесь x — переменная, символ, используемый для обозначения значения.
- стоимость(значение): это конкретные данные, которые могут быть числами, строками, объектами и т. д. здесь
1является ценностью.- привязка переменных(привязка имени): заключается в установлении соответствия между переменными и значениями,
x = 1переменнаяxа также1связался.- объем(область): Область действияпривязка переменныхДопустимый диапазон (привязка имени). То есть в этом объеме этопривязка переменныхдействителен, вне этой областипривязка переменныхявляется недействительным.
Что касается всей области программирования, область действия делится на две категории: статическая область действия и динамическая область действия.
статическая область
Статическая область также называется лексической областью, JS — это статическая область, например следующий код:
let x = 10;
function f() {
return x;
}
function g() {
let x = 20;
return f();
}
console.log(g()); // 10
В приведенном выше коде функцияfвернутьxопределяется внешним слоемx, то есть,10, мы называемgкогда, хотяgТакже есть переменнаяx, но мы не используем его здесь, мы используемfвнутриx. То есть, когда мы вызываем функцию, если переменные этой функции не определены в функции,Просто перейдите в место, где определена функция, чтобы найти, эта взаимосвязь поиска фактически определяется при написании нашего кода, поэтому она называется статической областью видимости. Это очень простой фрагмент кода, все знают, что вывод10, можно ли еще вывести20? Там действительно выход 20, это динамический диапазон!
динамическая область
Динамическая область видимости, используемая языком Perl, по-прежнему представляет собой описанную выше логику кода, а язык Perl выглядит следующим образом:
$x = 10;
sub f
{
return $x;
}
sub g
{
local $x = 20;
return f();
}
print g();
Вывод приведенного выше кода20,Вы можете использовать Perl, чтобы запустить и посмотреть, который является динамической областью. Так называемая динамическая область видимости заключается в том, что когда мы вызываем функцию, если переменные этой функции не определены в функции,Просто перейдите в место, где вызывается функция, чтобы найти. Поскольку функция может вызываться в нескольких местах, значение переменной может быть разным при каждом вызове, поэтому она называется динамической областью действия. Значение переменной динамической области трудно определить перед запуском, и сложность выше, поэтому в настоящее время основным направлением является статическая область, такая как JS, C, C++, Java, которые все являются статическими областями.
заявление заранее
раннее объявление переменной
До ES6 мы объявляли, что используются переменные.var,использоватьvarОбъявленные переменныеобъем функции, которая видна в теле функции, одна из проблем, которую это принесет, — это объявление аванса.
var x = 1;
function f() {
console.log(x);
var x = 2;
}
f();
Вывод приведенного выше кодаundefined, так как функцияfпеременные внутриxиспользоватьvarобъявить, так что он на самом деле во всей функцииfВидно, то есть его заявление эквивалентно приходу заранееf, но задание все еще выполняетсяx = 2время, так вvar x = 2;печатать вышеxто естьundefinedПриведенный выше код на самом деле эквивалентен:
var x = 1;
function f() {
var x
console.log(x);
x = 2;
}
f();
Предварительное объявление функции
Посмотрите на следующий код:
function f() {
x();
function x() {
console.log(1);
}
}
f();
приведенный выше кодx()Вызов может быть успешным, потому что объявление функции также будет выдвинуто вперед текущей функции, то есть вышеприведенной функции.xприбудет раноfВерхнее выполнение приведенного выше кода эквивалентно:
function f() {
function x() {
console.log(1);
}
x();
}
f();
Но есть одна вещь, чтобы отметить, вышеxЕсли функцию заменить функциональным выражением, она не будет работать:
function f() {
x();
var x = function() {
console.log(1);
}
}
f();
Это даст ошибкуUncaught TypeError: x is not a function. потому что здесьxПо сути, это обычная переменная, но ее значение — функция, хотя она и будет объявлена в начале текущей функции заранее, как было сказано ранее, ее значение равноundefined,БудуundefinedКак вызов функции, он должен бытьTypeError.
Предварительный приоритет объявлений переменных и объявлений функций
Поскольку будут расширены как объявления переменных, так и объявления функций, какое из них имеет более высокий приоритет? Ответ заключается в том, что **объявления функций имеют более высокий приоритет! ** См. следующий код:
var x = 1;
function x() {}
console.log(typeof x); // number
В приведенном выше коде мы объявляем переменнуюxи функцияx, у них одинаковое имя. окончательный результатtypeofдаnumber, указывая, что объявление функции имеет более высокий приоритет,xПеременные сначала объявляются как функция, а затем, когда мы используемvarобъявитьxкогда этоvarбудет игнорироваться, ноx=1Оператор присваивания будет запущен, и, наконец,x1, типnumber.
область действия блока
Предыдущее утверждение заранее не соответствует нормальным привычкам мышления людей.Если новички, не знакомые с JS, не понимают этого механизма, они могут часто сталкиваться с различнымиTypeError, написанный код также может содержать различные ошибки. Чтобы решить эту проблему, в ES6 была введена область видимости на уровне блоков. Область действия на уровне блока означает, что доступ к переменным возможен только в пределах указанного блока кода, то есть пары{}можно получить доступ внутрь, но не снаружи. чтобы отличить предыдущийvar, блочное использованиеletа такжеconstутверждение,letобъявить переменные,constОбъявите константы. См. следующий код:
function f() {
let y = 1;
if(true) {
var x = 2;
let y = 2;
}
console.log(x); // 2
console.log(y); // 1
}
f();
Приведенный выше код мы используем в теле функцииletобъявилy, в это время его областью действия является вся функция, а дальше идет другаяif,этоifиспользуется внутриvarобъявилx,использоватьletобъявил еще одинy,потому чтоvarявляется областью действия функции, поэтому вifВы также можете получить доступ к этому извнеx, он выводит 2,ifтот внутриyпотому чтоletУтверждено, значит он блочный прицел, то есть только вifВступает в силу внутри, если напечатано снаружиy, получит первыйy, что равно 1.
Дублирование деклараций не допускается.
Области на уровне блока не могут быть объявлены повторно в одном и том же блоке, например:
var a = 1;
let a = 2;
Это напрямую сообщит об ошибкеUncaught SyntaxError: Identifier 'a' has already been declared.
Но если вы используетеvarПодтверждение не сообщит об ошибке:
var a = 1;
var a = 2;
Нет переменного продвижения?
Я часто вижу статьи, в которых говорится:letа такжеconstОбъявленные переменные не поднимаются. На самом деле это утверждение не является точным, например следующий код:
var x = 1;
if(true) {
console.log(x);
let x = 2;
}
Приведенный выше код выдаст ошибкуUncaught ReferenceError: Cannot access 'x' before initialization. еслиletобъявленxПеременного продвижения нет, значит мы перед нимconsoleдолжен получиться внешний слойvarОпределенныйxВот так. Но теперь он сообщает об ошибке, указывая на то, что исполнитель находится вifЭтот блок на самом деле заранее знает, что существуетletобъявленx, поэтому было бы неверным сказать, что переменная вообще не продвигается. Просто повышенное поведение такое же, какvarРазные,varчитаетundefined,** и поведение при подъеме области на уровне блоков создаст временную мертвую зону (TDZ). **Феномен временной мертвой зоны заключается в том, что если переменная официально объявлена наверху блочного уровня для доступа к переменной, то сразу будет сообщено об ошибке, что предусмотрено спецификацией ES6.
Приложение в операторе цикла
Мы часто сталкиваемся со следующими проблемами: при вызове асинхронной функции в цикле ожидается, что при каждом вызове будет получена соответствующая переменная цикла, но в итоге получается конечная переменная цикла:
for(var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i)
})
}
Приведенный выше код, который мы ожидаем, является выходом0,1,2, но конечный результат равен трем3,Это потому чтоsetTimeoutэто асинхронный код, который будет выполняться в следующем цикле событий, иi++Но это синхронный код, и все выполнение завершено, подождите, покаsetTimeoutПри выполнении,i++было выполнено, в это времяiУже 3. Чтобы решить эту проблему в прошлом, мы обычно использовали самовыполняющиеся функции:
for(var i = 0; i < 3; i++) {
(function(i) {
setTimeout(() => {
console.log(i)
})
})(i)
}
теперь естьletмы будем напрямуюvarизменить наletПросто хорошо:
for(let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i)
})
}
Это обозначение относится и кfor...inа такжеfor...ofцикл:
let obj = {
x: 1,
y: 2,
z: 3
}
for(let k in obj){
setTimeout(() => {
console.log(obj[k])
})
}
Можно ли это использоватьconstКак насчет объявления переменных цикла? дляfor(const i = 0; i < 3; i++)Сказать,const i = 0это хорошо, ноi++Это должна быть ошибка, поэтому этот цикл будет выполнен один раз, а затем будет сообщено об ошибке. дляfor...inа такжеfor...ofперерабатыватьconstЗаявления в порядке.
let obj = {
x: 1,
y: 2,
z: 3
}
for(const k in obj){
setTimeout(() => {
console.log(obj[k])
})
}
Не влияет на глобальный объект
Использовать на самом внешнем уровне (глобальная область)varОбъявите переменную, и переменная станет свойством глобального объекта.Если глобальный объект имеет свойство с таким же именем, оно будет перезаписано.
var JSON = 'json';
console.log(window.JSON); // JSON被覆盖了,输出'json'
при использованииletОбъявление переменных не имеет этой проблемы:
let JSON = 'json';
console.log(window.JSON); // JSON没有被覆盖,还是之前那个对象
Так много из вышеперечисленного на самом делеletа такжеconstк предыдущемуvarВнесены улучшения, если наша среда разработки поддерживает ES6, мы должны использоватьletа такжеconst, вместоvar.
цепочка прицелов
Цепочка областей видимости на самом деле является очень простой концепцией. Когда мы используем переменную, мы сначала ищем ее в текущей области видимости. Если мы не находим ее, мы переходим к ее внешней области видимости, чтобы найти ее. Если нет, мы продолжаем поиск. смотрим наружу, пока не найдем глобальную область видимости, если в итоге область видимости не будет найдена, будет сообщено об ошибке. Например следующий код:
let x = 1;
function f() {
function f1() {
console.log(x);
}
f1();
}
f();
Этот код находится вf1вывод вx, так он и будетf1Ищите эту переменную в , естественно не найдёте, затем переходите кfЯ искал посередине, но так и не нашел, потом поднялся в глобальную область видимости, чтобы найти, и нашел здесь. Эта цепочка поиска является цепочкой области видимости.
Расширение цепочки областей
На самом деле, есть три объекта на цепочке объема предыдущего примера:
f1作用域 -> f作用域 -> 全局作用域
Большинство случаев подобны этому, как долго доменная цепь действий в основном, главным образом видеть его текущий слой вложенности, но некоторые операторы могут увеличить переменное объект в переднем конце цепочки области применения, этот объект переменной удален после выполнения кода после выполнения кода после выполнения кода после выполнения кода после выполнения кода после выполнения кода после выполнения кода после выполнения кода после выполнения кода после выполнения кода после выполнения кода после выполнения кода после выполнения кода , Это то, что объем продлен. Есть два утверждения, которые могут привести к длительному объему:try...catchизcatchблокировать иwithутверждение.
try...catch
На самом деле это особый случай, который мы использовали:
let x = 1;
try {
x = x + y;
} catch(e) {
console.log(e);
}
приведенный выше кодtryВнутри мы используем необъявленную переменнуюy, поэтому он сообщит об ошибке, а затем перейдет кcatch,catchдобавит переменную в начало цепочки областей видимостиe, это текущий объект ошибки, мы можем получить доступ к объекту ошибки через эту переменную, что фактически эквивалентно расширению цепочки областей видимости. эта переменнаяeБудет вcatchБлок уничтожается после выполнения.
with
withОператор может работать с цепочкой областей видимости, и вы можете вручную добавить объект в начало цепочки областей видимости.При поиске переменных объект сначала ищется.withПосле выполнения блока цепочка областей действия возвращается в нормальное состояние.
function f(obj, x) {
with(obj) {
console.log(x); // 1
}
console.log(x); // 2
}
f({x: 1}, 2);
Вышеприведенный код,withвнутри выходаxИди первымobjНайти, что эквивалентно ручному добавлению его в начало цепочки областей видимости.objэтот объект, поэтому выводxэто 1.withСнаружи по-прежнему обычная цепочка областей видимости, поэтому выводxеще 2.должен быть в курсеwithЦепочка областей действия в инструкции может быть определена только при ее выполнении, и движок не может ее оптимизировать, поэтому ее запрещено использовать в строгом режиме.withиз.
Суммировать
- Область действия фактически является эффективной областью привязки переменной.
- JS использует статическую область видимости, то есть если используемая функцией переменная не находится сама по себе, она будет искать ее в том месте, где она определена, а не в том месте, где она вызывается. Место для перехода к вызову - найти динамическую область.
-
varПеременная будет объявлена заранее, и к ней можно будет получить доступ до присваивания.undefined. - Объявления функций также расширены и имеют более высокий приоритет, чем
varвысоко. - использовать
varФункциональное выражение фактически представляет собойvarпеременная, вызываемая перед присваиванием, эквивалентнаundefined(). Ошибка будет сообщена напрямую. -
letа такжеconstобласть на уровне блока, эффективная область — это пара{}. - Одна и та же область на уровне блока не может быть объявлена повторно, и будет сообщено об ошибке.
- Область блока также имеет «подъем переменной», но поведение такое же, как
varВ отличие от этого, «продвижение переменной» в области блочного уровня сформирует «временную мертвую зону», и сообщение об ошибке будет сообщено непосредственно перед объявлением. - использовать
letа такжеconstОчень удобно решать проблему, что параметры асинхронного вызова в цикле неверны. -
letа такжеconstПеременные, объявленные в глобальной области видимости, не станут свойствами глобального объекта,varМогу. - При доступе к переменной, если текущая область видимости не существует, она будет искать уровень за уровнем, пока не достигнет глобальной области видимости, которая является цепочкой областей видимости.
-
try...catchизcatchБлоки расширяют цепочку областей видимости, добавляя объект ошибки на передний план. -
withОператоры могут вручную добавлять объект в начало цепочки областей видимости, но они недоступны в строгом режиме. - Если ваша среда разработки поддерживает ES6, вам следует использовать
letа такжеconst, Не используйте егоvar.
В конце статьи спасибо, что потратили свое драгоценное время на чтение этой статьи. Если эта статья немного поможет вам или вдохновит, пожалуйста, не скупитесь на лайки и звезды GitHub. Ваша поддержка является движущей силой для автор продолжать творить.
Добро пожаловать, чтобы обратить внимание на мой общедоступный номербольшой фронт атакиПолучите высококачественные оригиналы впервые~
Цикл статей "Передовые передовые знания":nuggets.capable/post/684490…
Адрес GitHub с исходным кодом из серии статей «Advanced Front-end Knowledge»:GitHub.com/Денис — см....