作用域貌似简单,实则复杂,由于作用域与this机制非常容易混淆,使得理解作用域的原理更为重要
大多数时候,我们对作用域产生混乱的主要原因是分不清楚应该按照函数位置的嵌套顺序,还是按照函数的调用顺序进行变量查找。再加上this机制的干扰,使得变量查找极易出错。这实际上是由两种作用域工作模型导致的,作用域分为词法作用域和动态作用域,分清这两种作用域模型就能够对变量查找过程有清晰的认识。
词法作用域
简单地说,词法作用域就是定义在词法阶段的作用域,是由写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变
无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定
如图所示,有三个逐级嵌套的作用域。为了帮助理解,可以将它们想象成几个逐级包含的气泡
作用域气泡由其对应的作用域块代码写在哪里决定,它们是逐级包含的
- 气泡1包含着整个全局作用域,其中只有一个标识符:foo
- 气泡2包含着foo所创建的作用域,其中有三个标识符:a、bar和b
- 气泡3包含着bar所创建的作用域,其中只有一个标识符:c
作用域气泡的结构和互相之间的位置关系给引擎提供了足够的位置信息,引擎用这些信息来查找标识符的位置
在代码片段中,引擎执行console.log(…)声明,并查找a、b和c三个变量的引用。它首先从最内部的作用域,也就是bar(…)函数 的作用域开始查找。引擎无法在这里找到a,因此会去上一级到所嵌套的foo(…)的作用域中继续查找。在这里找到了a,因此引擎使用 了这个引用。对b来讲也一样。而对c来说,引擎在bar(…)中找到了它
其实查找变量的过程就像顺着一根链条一直找到window顶层作用域为止(即:作用域链)
[注意]词法作用域查找只会查找一级标识符,如果代码引用了foo.bar.baz,词法作用域查找只会试图查找foo标识符,找到这个变量后,对象属性访问规则分别接管对bar和baz属性的访问
foo = {
bar:{
baz: 1
}
};
console.log(foo.bar.baz);//1
动态作用域
javascript使用的是词法作用域,它最重要的特征是它的定义过程发生在代码的书写阶段
那为什么要介绍动态作用域呢?实际上动态作用域是javascript另一个重要机制this的表亲。作用域混乱多数是因为词法作用域 和this机制相混淆,傻傻分不清楚
var a = 2;
functionfoo(){
console.log( a );
}
functionbar(){
var a = 3;
foo();
}
bar();
【1】如果处于词法作用域,也就是现在的javascript环境。变量a首先在foo()函数中查找,没有找到。于是顺着作用域链到全局作用域中查找,找到并赋值为2。所以控制台输出2
【2】如果处于动态作用域,同样地,变量a首先在foo()中查找,没有找到。这里会顺着调用栈在调用foo()函数的地方,也就是bar() 函数中查找,找到并赋值为3。所以控制台输出3
两种作用域的区别,简而言之,词法作用域是在定义时确定的,而动态作用域是在运行时确定的
参考链接:https://www.xiaohuochai.site/JS/ECMA/scope/lexicalAndDynamicScope.html