1.执行环境
执行环境(execution context
)是JavaScript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据。
全局执行环境是最外围的一个执行环境。根据ECMAScript实现所在的宿主环境不同,表示执行环境的对象也不一样。在Web浏览器中,全局执行环境被认为是window对象
,因此所有全局变量和函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出----例如关闭网页或浏览器时才会被销毁)。
每个函数都有自己的执行环境。当执行流进入到一个函数时,函数的执行环境就会被推入一个执行环境栈中并获取执行权。在该函数执行完后,栈将其执行环境弹出,把控制权返还给之前的执行环境。
我们来看一个执行环境栈运作的例子
<script>
var scope = "global";
function fn1(){
return scope;
}
function fn2(){
return scope;
}
fn1();
fn2();
</script>
如下图示意,当 JavaScript 代码执行时,第一个进入的总是默认的全局执行环境(在 Web 浏览器中也就是 window 对象)。 当执行fn1函数时,该函数的执行环境被推入栈中,执行完毕后被弹出,fn2亦如此。
对于每个执行环境都有三个重要的属性,变量对象VO
、作用域链
和 this
。这三个属性和代码运行的行为有很重要的关系。
2.变量对象(variable object )
每一个执行环境都有一个与之关联的变量对象(variable object
一般简写为 VO
),执行环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。
从上面的定义,我们可以知道一般OV包含以下信息:
- 变量声明(var,Variable Declaration)
- 函数声明(FD,Function Declaration)
- 函数的形参 (formal parameters)
举个例子:
<script type="text/javascript">
var a = "global";
function fn(x1) {
var x2 = "local";
}
</script>
在这个程序中,全局执行环境中的 VO 就有两个部分:
- 通过变量声明的变量
a
- 通过变量声明的变量
- 通过函数声明的函数
fn
- 通过函数声明的函数
变量对象是一个抽象的概念,在进入具体的执行上下文时,变量对象在具体实现上也会有相应地差别。在global全局上下文中,变量对象也是全局对象自身,在函数上下文中,变量对象被表示为活动对象AO。
2.1全局上下文中的变量对象
全局对象是一个在进入任何执行上下文前就创建出来的对象;此对象以单例形式存在;它的属性在程序任何地方都可以直接访问,其生命周期随着程序的结束而终止。
全局对象的属性在任何地方都可以被访问到,可以通过 this 或者 DOM 中的 Window 对象来访问。全局对象中的变量对象就是全局对象本身,理解这一点很重要,正是因为这个原因才使得可以通过全局对象的属性来访问在全局上下文中声明的变量。
2.2函数上下文中的变量对象
当函数被调用时,一个特殊的对象——活动对象就随之创建了。它包含普通参数与特殊参数对象(具有索引属性的参数映射表arguments)。活动对象在函数上下文中作为变量对象使用。
在函数执行上下文中,VO是不能直接访问的,此时由活动对象(activation object,缩写为AO)扮演VO的角色
。
VO(functionContext) === AO;
在上面 VO 例子中,当开始执行到 foo 的时候,就会有一个 foo 的 AO 被创建,这个活动对象由两个部分组成:
- 初始化生成的
arguments 对象
- 通过变量声明的变量
x2
3.作用域链
了解了变量对象之后,我们再来讲讲作用域链。
在JavaScript中,函数也是对象,实际上,JavaScript里一切都是对象。函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是[[Scope]]
,该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。
var a='123';
function testFn(b){
var c='abc';
function testFn2(){
var d='efg';
alert(a);
}
testFn2();
}
testFn(10);
estFn2内未声明变量a,为什么testFn2能调用全局变量a?整个过程是怎么发生的呢?请看下图。
当解析器进入全局执行环境时,调用变量和函数时只在Global对象中查找。
当解析器进入testFn函数执行环境时,函数内部属性[[scope]]中首先填入Global对象,然后将testFn活动对象添加到Global对象之前,形成一个作用域链。
当解析器进入testFn2函数执行环境时,函数内部属性[[scope]]首先填入父级的作用域链,然后再将当前的testFn2活动对象添加到作用域链的前端,形成一个新的作用域链。
testFn2调用变量a时,首先在当前的testFn2活动对象中查找,如果没有找到就顺着作用域链向上,在testFn活动对象中查找变量a,如果没有找到再顺着作用域链向上查找,直到在最后Global对象中找到为止,否则报错。所以函数内部可以调用外部环境的变量,外部环境不能调用函数内部的变量,这就是作用域特性的原理。
4.延长作用域链
虽然执行环境的类型就只有两种————全局和局部,但还是有其他办法来延长作用域链。
用with语句和try catch 都可以延长作用域链。
语法形如:
with(object)
statement
这个with语句,将object添加到作用域链的头部,然后执行statement,最后把作用域链恢复到原始状态
function buildUrl(){
var qs = "?debug=true";
with(location){
var url = href + qs;
}
return url;
}
当使用with语句时,相当于在现在的作用域顶部上添加了传递给with的对象。这个例子中with语句接受的是location对象,因此其变量对象中就包含了location对象所有的属性和方法,当在with语句中引用变量href时(实际上是引入location.href),可以在当前执行环境中找到。with语句内部,定义了一个名为url的变量,因而url就成了函数执行环境的一部分,所以可以作为函数的值被返回.
再看下面代码
person={name:"yhb",age:22,height:175,wife:{name:"lwy",age:21}};
with(person.wife){
console.log(name);
}
with语句将person.wife添加到当前作用域链的头部,所以输出的就是:“lwy".
with语句结束后,作用域链恢复正常。
5.提升
提升分为变量提升和函数提升,下面我们依次介绍
5.1变量提升(variable hoisting)
var name="haha";
function changeName(){
console.log(name);
var name="xixi";
}
changeName();
console.log(name);
大家认为第6行和第7行代码输出的结果应该是什么?答案是:输出结果结果分别是 undefined 和 haha。为什么是undefined?
那我们先来分析一下代码 函数changeName() 的作用域链: 自己的变量对象 -----> 全局变量对象。解析器在函数执行环境中发现变量 name,因此不会再向全局环境的变量对象中寻找。但是大家要注意的是,解析器在解析第3句代码时,还不知道变量name的值,也就是说只知道有变量name,但是不知道它具体的值(因为还没有执行第4句代码),因此输出是 undefined。javascript解析器执行原理不懂的童鞋可移步这里。
上述代码其实等价于下面的形式:
var name="haha";
function changeName(){
var name;
console.log(name);
name="xixi";
}
changeName();
console.log(name);
这个现象就是变量提升!
变量提升,就是把变量提升到函数的顶部,需要注意的是,变量提升只是提升变量的声明,不会把变量的值也提升上来!
5.2函数提升
在JavaScript中函数的创建方式有三种:函数声明(静态的)、函数表达式(函数字面量)、函数构造法(动态的,匿名的)。
函数表达式的形式如下:
var func1 = function(n1,n2){
//function body;
};
函数构造法构造函数的形式如下:
var func2 = new Function("para1","para2",...,"function body");
在这里需要说明的是:只有函数声明形式才能被提升!例如:
//函数声明
function myTest1(){
func();
function func(){
console.log("我可以被提升");
}
}
myTest1();
这几个概念真的是绕,自己梳理了一遍可能还没记住,还需要多加记忆,以上若有错误,请指正。