词法作用域 VS 动态作用域

Javascript的词法作用域

作用域是个语言无关的概念,你要接触过Lisp或者Scheme等语言,应该对这个概念会非常熟悉。我在这篇文章会介绍词法作用域和动态作用域的基本知识,让你刚好对这个概念了解,此外,我们还会讨论下JavaScript的词法作用域。

首先你得明白程序设计中作用域这个概念:通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域

词法作用域,也叫静态作用域,它的作用域是指在词法分析阶段就确定了,不会改变。动态作用域是在运行时根据程序的流程信息来动态确定的,而不是在写代码时进行静态确定的。

我们以下面这段Javascript代码来说明词法作用域和动态作用域,但是你要明白,阐述的概念是与Javascript无关的。

var a = 2;

function foo() {
  console.log(a); // 会输出2还是3?
}

function bar() {
  var a = 3;
  foo();
}

bar();

如果是词法作用域,它会让 foo() 函数引用到全局作用域中的 a,因此会输出 2。我们说过,词法作用域是写代码的时候就静态确定下来的。Javascript中的作用域就是词法作用域(事实上大部分语言都是基于词法作用域的),所以这段代码在浏览器中运行的结果是输出 2

Javascript作用域链

而动态作用域并不关心函数和作用域是如何声明以及在何处声明的,只关心它们从何处调用。换句话说,作用域链是基于调用栈的,而不是代码中的作用域嵌套。因此,如果Javascript具有动态作用域,理论上输出结果是 3

为什么会这样?因为当 foo() 无法找到 a 的变量引用时,会顺着调用栈在调用 foo() 的地方查找 a ,而不是在嵌套的词法作用域链中向上查找。由于 foo() 是在 bar() 中调用的,引擎会检查 bar() 的作用域,并在其中找到值为 3 的变量 a

你可能会觉得很奇怪,但这其实是因为你可能只写过基于词法作用域的代码(或者至少以词法作用域为基础进行了深入的思考),因此对动态作用域感到陌生。如果你只用基于动态作用域的语言写过代码,就会觉得这是很自然的,而词法作用域看上去才怪怪的。

需要明确的是,Javascript并不具有动态作用域,它只有词法作用域,简单明了。但是,它的 eval()withthis机制某种程度上很像动态作用域,使用上要特别注意。

主要区别:词法作用域是在写代码或者定义时确定的,而动态作用域是在运行时确定的(this也是!)。词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用

参考资料


  1. 书籍:《你不知道的JavaScript:上卷》
  2. 百度百科词条:作用域
  3. cnblogs:《浅谈静态作用域和动态作用域

推荐阅读更多精彩内容