作用域
作用域就是变量与函数的可访问范围
,即作用域控制着变量与函数的可见性和生命周期。
在JavaScript中,变量的作用域有全局作用域和局部作用域两种。
作用域链
函数对象有一个内部属性[[Scope]],包含了函数被创建后的作用域中对象的集合,
这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。
示例:
当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象填充。
function add(num1, num2){
var sum = num1 + num2;
return sum;
}
在函数add创建时,它的作用域链中会填入一个全局对象,该全局对象包含了所有全局变量,如下图所示(注意:图片只列举了全部变量中的一部分):
执行add函数时会创建一个称为“运行期上下文(execution context)”
的内部对象,运行期上下文定义了函数执行时的环境。
每个运行期上下文都有自己的作用域链,用于标识符解析。
当运行期上下文被创建时,而它的作用域链初始化为当前运行函数的[[Scope]]所包含的对象,这些值按照它们出现在函数中的顺序被复制到运行期上下文的作用域链中。
它们共同组成了一个新的对象,叫“活动对象(activation object)”
,该对象包含了函数的所有局部变量、命名参数、参数集合以及this,然后此对象会被推入作用域链的前端,当运行期上下文被销毁,活动对象也随之销毁。新的作用域链如下图所示:在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据。
该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没找到继续搜索作用域链中的下一个对象,如果搜索完所有对象都未找到,则认为该标识符未定义。函数执行过程中,每个标识符都要经历这样的搜索过程。
作用域链和代码优化
从作用域链的结构可以看出,在运行期上下文的作用域链中,标识符所在的位置越深,读写速度就会越慢。
如上图所示,因为全局变量总是存在于运行期上下文作用域链的最末端,因此在标识符解析的时候,查找全局变量是最慢的。所以,在编写代码的时候应尽量少使用全局变量,尽可能使用局部变量。一个好的经验法则是:如果一个跨作用域的对象被引用了一次以上,则先把它存储到局部变量里再使用。
改变作用域链
函数每次执行时对应的运行期上下文都是独一无二的
,所以多次调用同一个函数就会导致创建多个运行期上下文,当函数执行完毕,执行上下文会被销毁。每一个运行期上下文都和一个作用域链关联。一般情况下,在运行期上下文运行的过程中,其作用域链只会被 with 语句和 catch 语句影响。
with语句是对象的快捷应用方式,用来避免书写重复代码。
对with语句来说,会将指定的对象添加到作用域链中,对catch语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。
此时,作用域链中函数的所有局部变量所在的作用域对象会被推后,访问代价变高了。
在实际应用中,应少用with,把catch中的错误委托给一个函数处理。
没有块级作用域
if(true){
var i = 0;
i++;
}
console.log(i); //1
如果在c、c++或java语言中,if语句执行完毕后i会被销毁,而在js中,if语句中的变量声明是添加到了当前函数的执行环境中,所以在if语句之后仍然可以正常访问。
模仿块级作用域
(function(){
//这里是块级作用域
})();
将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式,而紧随其后的另一对圆括号会立即调用这个函数。
小结
作用域就是变量和函数的可访问范围,通常,局部环境中的变量和函数是不能被外部环境访问的;
作用域链决定了哪些数据能够被当前函数访问以及访问的顺序;
函数创建时,会创建一个Global Object,填入它的作用域链;函数执行时,会创建一个运行期上下文的对象,它定义了函数执行时的环境。函数执行环境包含一个活动对象,该对象包含了函数的所有局部变量、命名参数、参数集合以及this,它会被推入作用域链的最前端;
函数执行过程,每遇到一个变量,都会经历一次标识符解析的过程(逐级向上搜索作用域链)以决定从哪里获取和存储数据;
全局变量存在于运行期上下文作用域链的最末端,查找最慢,所以我们应该尽可能少使用全局变量,如果使用,就先用局部变量缓存下来;
在运行期上下文运行的过程中,其作用域链只会被 with 语句和 catch 语句影响,应少用with,把catch中的错误委托给一个函数处理;
js中没有块级作用域,但是我们可以模仿实现它。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。