一 为什么要有作用域
我们知道,变量对于程序来说是至关重要的,如果没有变量存储和访问值,整个程序会受到限制。那么问题来了,既然程序这么需要变量,那么它到底是怎么样去存储变量和使用变量的呢?存储变量这里暂且不提,到时候会有专门一篇博客来说明这个问题。我们这次说的主要就是如何去使用变量。这就要牵扯到我们今天的主题作用域上面了。
概括的来说,作用域就是一套能让你有序访问变量的规则。(注意有序很重要)javascript中只有函数能封闭作用域(let函数也能绑定一个块级作用域,这里先不做讨论).
二 作用域的有序性
来看下面一个例子
function foo (a) {
var b = a * 2
function bar (c) {
console.log(a, b, c)
}
bar(b * 3)
console.log(c)
}
foo(2)
(1)foo函数内部会形成一个作用域
(2)bar函数内部会形成一个作用域
(3)有一个全局的作用域
我们前面说过,作用域是一套能让你有序访问变量的规则,那么上述代码运行的时候,作用域是怎么样访问变量的呢?来看看下面的示意图。
,一个是函数bar的作用域,一个是函数foo的作用域,一个是全局的作用域。并且这三个作用域是嵌套的。
(1)bar作用域中有一个变量c
(2)foo作用域中有三个变量a,b,bar
(3)全局作用域中有一个变量foo
我们来看看上面代码的运行过程,首先执行最外层的foo(2),foo在调用栈调用bar,bar执行。但是注意bar内部的执行语句为console.log(a,b,c)
我们前面已经说过,bar作用域中只有变量c,那么上述语句是否会出现错误呢,答案是不会。上述代码会正常输出。那么为什么会这样呢?答案就是代码在运行的过程中有一个作用域链能作用域给串起来。如下图
内部的作用域可以访问外部作用域的变量。所以bar函数在执行console.log(a,b,c)时,在当前作用域中如果没有找到a,b变量,它会顺着作用域链往上找,在上层作用域foo中找到了a,b变量,它就会使用上层作用域a,b的值。如果上层作用域还是没有a,b的话,它会顺着作用域继续查找,直到全局变量。如果全局变量仍然没有,程序就会报错。那么既然内部作用域能沿着作用域链访问到外部作用域,那么外部作用域能不能顺着作用域链访问内部作用域呢?不急,继续看下面代码。
执行完bar函数后,bar函数从执行栈中弹出,继续执行foo函数剩余的语句,console.log(c)
由于当前作用域中不存在变量c,但是其子作用域内有变量c的定义,那么程序会不会输出子级作用域的变量c呢?答案是不会。
上级作用域不能通过作用链进入下级作用域。只有下级作用域能通过作用链进入上级作用域。只就是作用域的有序性。有序的访问所有能访问的变量和函数。
三 总结
作用域就像一个一个封闭的空间,不同作用域内的变量是不会相互影响的。但是作用域之间又会有联系。如果是嵌套的作用域的话,这些嵌套作用域会通过作用域链把嵌套作用域联系在一起。内部作用域能通过作用域链访问到上级作用域的变量。即如果当前作用域中没有某个变量,引擎会通过作用域链查找上级作用域看看有没有定义该变量。直到全局作用域。(全局没有则报错)
但是上级作用域没法通过作用域链访问下级作用域。这就是作用域的有序性。通过作用域链能让引擎对执行环境里所有有权访问的变量和函数进行有序访问。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。