何为作用域,查找度娘百科中是这么定义的:

作用域(scope),程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。

1、静态作用域(词法作用域)

通常来说 JS 是静态作用域,函数的作用域在函数定义的时候就已经确定了。
可以举个例子:

1
2
3
4
5
6
7
8
9
var scope = 1;
function foo() {
console.log(scope);
}
function bar() {
var scope = 2;
foo();
}
bar();

最后输出为 1,因为 JS 是静态作用域,所以 foo 函数的作用域在其定义的时候就被确定了,当在 bar 函数中执行 foo()时,没有去查找 bar 中的 scope 变量,而是去查找 foo 函数中的 scope,当 foo 函数中没有找到这个变量的时候他会在全局中查找,所以最后输出 1。

2、动态作用域

函数的作用域在函数调用的时候才决定。
其实大部分的语言都是基于静态作用域的,如果 JS 是动态作用域的话,那上述例子最后的结果应该是 2 而不是 1,因为当 foo()无法找到 scope 的变量引用时,会顺着调用栈在调用 foo()的地方查找 scope,而不是在嵌套的词法作用域链中向上查找。由于 foo()是在 bar()中调用的,引擎会检查 bar()的作用域,并在其中找到值为 2 的变量 scope。
看到这里有没有感觉到这个动态作用域的工作原理像极了 JS 中的 this,关于 this 的机制我们将在后续中进行讲解。

3、JS 的全局作用域、块级作用域、函数作用域

3.1 全局作用域

在 ES6 之前,JS 只有全局作用域和函数作用域,但是当全局作用域用的过多时就会发现一个大问题,就是全局污染,为了这个解决这个问题推出了好多方法:模块化、闭包、命名空间等等等等。
什么是全局作用域?
全局作用域即贯穿整个 JS 文档,在任何地方都能够访问到,JS 中有个全局对象 window,如声明一个全局变量,就相当于在 window 上添加一个属性。

3.2 块级作用域

块级作用域是 ES6 中才出现的新特性,通常使用的 let、const 都是显式声明块级作用域的方法。
在《你不知道的 JavaScript》中对块级作用域的总结是这样的:块作用域指的是变量和函数不仅可以属于所处的作用域,也可以属于某个代码块(通常指 { .. } 内部)。
其实我认为通俗的理解就是,在代码块中定义的变量或者函数,在其代码块外无法访问。

3.3 函数作用域

函数作用域即定义在函数代码块中的变量和函数,外部无法访问。
举个栗子:

1
2
3
4
5
6
7
var a = 1;
function foo() {
var a = 2;
console.log(a);
}
foo();
console.log(a);

运行上述结果可以的出结论,foo 函数运行完后在 foo 函数中输出的为 2,在全局上输出的为 1。但是这样并不是很理想,因为 foo 函数是挂载在全局中的,也会容易造成一个全局污染的问题,其次必须显式调用(foo())才能运行函数代码,还好 JS 给我们提供了一个解决这种问题的方法,即自执行函数。
可以通过自执行函数将上面的例子进行改造:

1
2
3
4
5
6
7
var a = 1;
(function foo() {
var a = 2;
console.log(a);
})();
console.log(a);
console.log(foo); // Uncaught ReferenceError: foo is not defined

执行结果与之前相同,当查看 foo 函数时会报 foo 没有被定义的错误。foo 变量名被隐藏在自身中意味着不会非必要地污染外部作用域。