在这篇文章中,我假设您了解执行上下文的基本知识:不过,我很快也会就此写一篇文章😃
让我们看看下面的代码:
const name = "Lydia"
const age = 21
const city = "San Francisco"
function getPersonInfo() {
const name = "Sarah"
const age = 22
return `${name} is ${age} and lives in ${city}`
}
console.log(getPersonInfo())
我们正在调用getPersonInfo函数,该函数返回一个字符串,其中包含name、age和city变量的值:
Sarah 22 San Francisco。但是,getPersonInfo函数不包含名为city🤨?它怎么知道city的值?
首先,为不同的上下文设置内存空间。我们有默认的全局上下文(浏览器中的窗口、节点中的全局)和已被调用的getPersonInfo函数的局部上下文。每个上下文也有一个作用域链。
对于getPersonInfo函数,作用域链看起来像这样(不用担心,它现在还不一定有意义):
作用域链基本上是对对象的“引用链”,这些对象包含对在该执行上下文中可引用的值(和其他作用域)的引用。(⛓):“嘿,这些都是您可以在此上下文中引用的值”。)创建执行上下文时将创建作用域链,这意味着它是在运行时创建的!
不过,在这篇文章中,我一般不会讨论激活对象或执行上下文,我们只关注作用域吧!在下面的示例中,执行上下文中的键/值对表示作用域链对变量的引用。
全局执行上下文的作用域链引用了3个变量:值为Lydia的name、值为21的age和值为San Francisco的city。在局部上下文中,我们引用了两个变量:name和Sarah值,age和22值。
当我们试图访问getPersonInfo函数中的变量时,引擎首先检查局部作用域链。
局部作用域链引用了name和age!name有Sarah的值,age有22的值。但现在,当它试图进入city时会发生什么呢?
为了找到city的值,引擎“沿着作用域链向下”。这基本上只是意味着引擎不会那么容易放弃:它很难让你看到它是否能在外部作用域中找到一个变量city的值,局部作用域引用了这个全局对象。
在全局上下文中,我们用San Francisco的值来声明变量city,从而对变量city有一个引用。现在我们有了变量的值,函数getPersonInfo可以返回字符串Sarah is 22 and lives in San Francisco 🎉
我们可以沿着范围链往下走,但不能沿着范围链往上走。(好吧,这可能会让人困惑,因为有些人会说“向上”而不是“向下”,所以我要重新表述一下:你可以到外部的范围,但不能到更内部的范围。。。(内部…)范围。我喜欢把它想象成瀑布:
甚至更深:
我们以这段代码为例。
这几乎是一样的,但是有一个很大的区别:我们现在只在getPersonInfo函数中声明了city,而不是在全局范围内。我们没有调用getPersonInfo函数,因此也没有创建本地上下文。然而,我们试图在全局范围内获取name、age和city的值。
它抛出一个引用错误!它在全局范围内找不到对名为city的变量的引用,并且没有要查找的外部范围,也无法向上扩展作用域链。
这样,您可以使用作用域作为“保护”变量和重用变量名的方法。
除了全局和局部作用域外,还有一个块作用域。用let或const关键字声明的变量的作用域是最近的花括号({})。
const age = 21
function checkAge() {
if (age < 21) {
const message = "You cannot drink!"
return message
} else {
const message = "You can drink!"
return message
}
}
可以将作用域可视化为:
在ES5中我们只有全局作用域和函数作用域,ES6新增了块级作用域,现在我们有一个全局作用域、一个函数作用域和两个块作用域。我们能够声明变量消息两次,因为变量的作用域是花括号。
快速回顾:
- 您可以将“scope chain”看作是在当前上下文中可以访问的值的作用域链。
- 作用域还可以重用作用域链下游(外部)定义的变量名,因为它只能向下延申(外部)作用域链,而不能向上延申(内部)作用域链。