说明
最近在复看红皮书的部分内容,发现之前对词法作用域理解的有点片面,所以今天单独成文整理下,方便以后查阅理解。
相关概念
- 执行环境(分类:全局/函数/Eval,浏览器下全局是window对象)
- 变量对象:作用:存储对应执行环境的变量和函数
- 执行环境:执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。
- 作用域链:当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。用途:保证对执行环境有权访问的所有变量和函数的有序访问。
任何js代码片段在执行前都要进行编译,通常就在执行前几微秒,甚至更短。即:(编译器)先编译,(JS引擎)再执行。
js 编译的三个阶段:
- 词法阶段:字符串分解成词法单元;生成词法作用域;
- 语法阶段:把词法单元流(数组)转换成AST;
- 生成阶段:把 AST 转换成可执行的代码。
作用域
作用域是指程序源代码中定义变量的区域。
作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
JavaScript 采用词法作用域,也就是静态作用域。
词法作用域【必须理解!】
概念:定义在词法阶段的作用域。
Javascript 采用的就是词法作用域,所以变量和函数的作用域在定义的时候就决定了。
【理解】词法作用域是由你在写代码时将变量和块作用域 写在哪里 来决定的,因此当词法分析器处理代码时会保持作用域不变(欺骗词法除外),不会因为函数调用的位置发生改变。
【牢记】内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。每个环境都可以向上搜索作用域链,以查询变量和函数名,但不能向下!
典例:
function foo() {
console.log(a);
}
function bar() {
var a = 2;
foo();
}
var a = 1;
bar()
图解作用域链:
window
|—— a
|—— foo()
|—— bar()
|-a
结果为:1
分析:
- 假设javascript采用静态作用域,让我们分析下执行过程:
...,开始执行foo函数,先从foo函数内部查找是否有局部变量a => 没有。再根据代码书写的位置,向上查找作用域链,就找到了window.a,也就是value等于1,所以结果会打印1。与foo函数在哪里执行没有任何关系。
- 假设javascript采用动态作用域,让我们分析下执行过程:
执行foo函数,依然是从foo函数内部查找是否有局部变量value,如果没有,就从调用函数的作用域,也就是bar函数内部查找value变量,所以结果会打印 2。
由于javascript采用的是词法作用域,所以这个例子的结果是1。
动态作用域
与词法作用域相对的是动态作用域,它的函数的作用域是在函数调用的时候才决定的。
理解即可,具体内容请查看《你不知道的js》上 中 P59 的介绍。
思考题
来自《javascript权威指南》中的例子:
eg1:
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f()
}
checkscope()
eg2:
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()();
结果:两段代码都会打印:local scope
分析:因为javascript采用的是词法作用域,函数的作用域基于函数创建的位置。
引用《javascript权威指南》的回答是:
javascript 函数的执行用到了作用域链,这个作用域链是在函数定义的时候创建的。嵌套的函数f()定义在这个作用域链里,其中的变量 scope 一定是局部变量,不管何时何地执行函数 f(),这种绑定在执行f()时依然有效。
参考
《你不知道的JS》上-词法作用域
参考文献《JavaScript深入之词法作用域和动态作用域》
我的相关文章《JS:4.2 执行环境及作用域》