执行上下文及作用域
执行上下文
执行上下文定义了变量或函数有权访问的其它数据,决定了它们各自的行为。每个执行上下文中都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。
- 全局执行上下文:最外围的执行上下文。根据ECMAScript实现所在的宿主不同表示执行环境的对象也不同。在浏览器中,全局执行环境被认为是window对象,因此全局变量和函数都是作为window对象的属性和方法创建的。
- 销毁:某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。
- 全局执行环境直到应用程序退出(关闭网页或浏览器)时才被销毁。
- 每个函数都有自己的执行上下文。
作用域链
当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链是保证对执行环境有权访问的所有变量和函数的有序访问。
每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用域链。
- 作用域链的前端始终是当前执行的代码所在环境的变量对象。下一个变量对象来自外部包含环境,再下一个变量对象则来自下一个包含环境,这样一直延续到全局执行环境;全局执行环境的变量对象总是作用域链中的最后一个对象。
- 如果当前环境是函数,则将其活动对象作为变量对象。(活动对象一开始只包含arguments对象这一个变量)
- 标识符解析是沿着作用域链一级一级地搜索标识符的过程,从作用域链前端开始,逐级向后回溯,直至找到标识符。(若没找到通常导致错误)
p74 图4-3
即:内部环境可以通过作用域链访问所有外部环境,但外部环境不能访问内部环境中的任何变量和函数。每个环境都可以向上搜索作用域链,但不能向下搜索而进入另一个执行环境。
延长作用域链
有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。当执行流进入下列任何一个语句时,作用域链就会得到加长:
- try-catch语句的catch块
- with语句
这两个语句都会在作用域链的前端添加一个变量对象。
- with:会将指定的对象添加到作用域链的前端
- catch:会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。
没有块级作用域
一般情况下,js不具有其他语言所具有的{ }块级作用域。
- 使用var声明的变量会自动被添加到最接近的环境中。
- 函数内部:最接近的环境是函数的局部环境
- with语句:最接近的环境的函数环境
- 没有使用var声明的变量会自动被添加到全局环境。
严格模式下,初始化未声明的变量会导致错误。
由于搜索标识符是从作用域链的前端开始向上逐级查询的,因此如果局部环境存在着同名标识符,就不会使用位于父环境中的标识符。
变量查询是有代价的,访问局部变量要比访问全局变量更快,因为不用向上搜索作用域链。
let
let关键字是ES6新引入的,提供了除var之外的另一种变量声明方式。
let关键字可以将变量绑定到所在的任意作用域中(通常是{……}内部),换句话说,let为其声明的遍历隐式地绑定在所在的块级作用域。
var foo = true;
if(foo){
let bar = foo*2;
bar = something(bar);
console.log(bar);
}
console.log(bar); //ReferenceError
let隐式地将bar附加在了if后面的{ }块作用域中。
为块作用域显式地创建块可以解决隐式绑定块作用域可能导致的混乱问题,使变量的附属关系变得更加清晰:
var foo = true;
if(foo){
{ //显式的块
let bar = foo*2;
bar = something(bar);
console.log(bar);
}
}
console.log(bar); //ReferenceError
只要声明是有效的,在声明中的任何位置都可以使用{……}括号来为let创建一个用于绑定的块。
在这个例子中,我们在if声明内部显式地创建了一个块,如果需要对其进行重构,整个块都可以方便地移动而不会对外部if声明的位置和语义产生任何影响。
但是,使用let进行的声明不会在块作用域中进行提升。
let循环
一个let可以发挥优势的典型例子就是for循环。
for(let i=0;i<10;i++){
console.log(i);
}
console.log(i); //Reference Error
可以看见,for循环头部的let不仅将i绑定到了for循环的块中,事实上它将其重新绑定到了循环的每一个迭代中,确保使用上一个循环迭代结束时的值重新进行赋值。
有关重新绑定的原因在闭包那里再进行探究。
const
除了let外,ES6还引入了const,同样可以用来创建块作用域变量,但其值是固定的(常量)。之后任何试图修改值的操作都会引起错误。
var foo = true;
if(foo){
var a = 2;
const b = 3; //包含在if中的块作用域常量
a = 3; //正常
b = 4; //错误!!
}
console.log(a); //3
console.log(b); //Reference Error
垃圾收集
- js具有自动垃圾收集机制。
- js开发人员不用关心内存问题,所需内存的分配和无用内存的回收完全实现了自动管理。
两种垃圾收集机制:
- 标记清楚
- 引用计数
优化内存占用:
解除引用:将其值设置为null来释放引用。适用于大多数全局变量和全局对象的属性,而局部变量会在它们离开执行环境时自动被解除引用。