【重学JS之路】作用域

344 阅读3分钟

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

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

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

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

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。

动态作用域

函数的作用域在函数调用的时候才决定。

其实大部分的语言都是基于静态作用域的,如果JS是动态作用域的话,那上述例子最后的结果应该是2而不是1,因为当foo()无法找到scope的变量引用时,会顺着调用栈在调用foo()的地方查找scope,而不是在嵌套的词法作用域链中向上查找。由于foo()是在bar()中调用的,引擎会检查bar()的作用域,并在其中找到值为2的变量scope。

看到这里有没有感觉到这个动态作用域的工作原理像极了JS中的this,关于this的机制我们将在后续中进行讲解。

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

全局作用域

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

块级作用域

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

函数作用域

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

var a = 1;
function foo() {
  var a = 2;
  console.log(a);
}
foo();
console.log(a);

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

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变量名被隐藏在自身中意味着不会非必要地污染外部作用域。