JavaScript的scope和context都是不能被我们直接使用的东西,存在于JavaScript的整个执行过程,分为代码编译阶段和代码执行阶段,在代码编译阶段,编译器将代码翻译成可执行代码,此时会确定函数的作用域和作用域链;在代码的执行阶段,引擎执行代码时会创建代码的执行环境。所以首先我们要明白一点,执行环境和作用域不是同一个东西。
作用域
作用域是负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前的执行的代码对这些标识符的访问权限。——《你不知道的JavaScript(上卷)》
JavaScript中作用域可以分为全局作用域和局部作用域。
全局作用域是整个程序在运行时都能访问的;局部作用域中包含了函数作用域和块级作用域。
下图展示了函数作用域和全局作用域之间的关系:
块级作用域在ES3中有with和catch两个,在ES6中引入了let和const
/* with */
var obj = {a: 0}
with(obj) {
a = 1
}
console.log(obj) // {a:1}
console.log(a) // ReferenceError: a is not define
/* catch */
try {
undefined() // 制造一个错误
} catch(err) {
console.log(err) // 输出这个err
}
console.log(err) // ReferenceError: err is not define
利用catch的块作用域,我们可以在不兼容ES6的环境下使用let的兼容写法,可以看下面这个例子:
/* 无块级作用域 */
var i = 0;
for(i; i < 10; i++) {
var j = i
setTimeout(function(){
console.log(j) // 输出10个10
})
}
/* 使用let */
var i = 0
for(i; i < 10; i++) {
let j = i
setTimeout(function(){
console.log(j) // 输出从0到9 10个数字
})
}
/* 使用catch */
var i = 0;
for (i; i < 10; i++) {
try {
throw undefined // 产生错误,值为undefined
} catch (j) {
j = i
setTimeout(function () {
console.log(j) // 输出从0到9 10个数字
})
}
}
词法作用域
JavaScript是用的词法作用域,所以作用域和作用域链是在代码编译阶段就确定的东西,下面看一个简单的例子:
var a = 2;
function fn1() {
console.log(a);
}
function fn2() {
var a = 3;
fn1();
}
fn2(); // 2
在上面的代码中,变量a、fn1和fn2是在全局作用域中声明的,fn1和fn2也产生了各自的函数作用域,但因为作用域和作用域链是在函数定义阶段就确定的,所以在执行阶段,fn1()输出的a就是全局变量中的a。
执行环境
每个函数都有自己的执行环境(execution context)。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。——《JavaScript高级程序设计》
console.log('global');
function func1() {
console.log('func1');
var func2 = function() {
console.log('func2');
}
func2();
}
func1();
- 浏览器开始运行,环境栈中添加global执行环境,输出global
- 执行了func1(),环境栈中添加func1执行环境,输出func1
- 在func1中执行了func2(),环境栈中添加func2执行环境,输出func2
- func2执行完毕,环境栈中推出func2执行环境
- func1执行完毕,环境栈中推出func1执行环境
上面这个例子的环境栈变化如下图所示:
执行环境和作用域的区别
自我理解(未确认):JavaScript的代码在执行前会确定作用域,产生作用域链,然后当执行到某个函数的时候,函数执行环境就被推入环境栈,执行环境上就会添加变量对象、活动对象、作用域链这些东西。变量对象和活动对象是差不多的,只是状态不一样,一个是执行前确定的,一个是执行时用到的,上面保存了函数中定义的变量或者函数声明。作用域是一套规则,用来确定在何处以及如何查找对象,当作用域嵌套的时候,就产生了作用域链,当前作用域没有找到变量的时候,就在上一层作用域查找,直到找到变量或者到达最外层作用域。