发一下牢骚和主题无关:
读《JavaScript高级程序设计》第4、7章有感。
一、基本观点
1.什么是执行环境?(execution context)
执行环境定义了变量或函数有权拜访的其他数据,决定了它们各自的行为。
个人感悟:
所谓执行环境,说的是某一段特别的代码(比如函数),它在这个“执行环境”下执行,通过这个执行环境的限制,决定了这段代码(此处是函数)允许拜访的数据是什么。
2.什么是变量对象?(variable object)
每一个执行环境都有一个与之关联的变量对象,环境中定义的全部变量和函数都保存在这个对象中。(虽然我们编写的代码无法拜访这个对象,但解析器在处置数据时会在后台应用它)
个人感悟:
我认为吧,执行环境是一个很抽象的观点,而在JS代码实现中,须要具体化这个抽象观点,于是,就用一个与执行环境关联的对象,用于表示这个环境的具象存在。
3.什么是作用域链?(scope chain)
当代码在一个环境中执行时,会创建由变量对象构成的一个作用域链。作用域链的用处,是保证对执行环境有权拜访的全部变量和函数的有序拜访。
个人感悟:
前面说道,JavaScript通过执行环境的限制,决定变量或函数允许拜访的数据。那么,现在就通过一条链表存放全部可以拜访的变量对象(变量对象中存放可以拜访的数据),以便我们有序地拜访这些数据。(为什么要这样搞,下文会说)
另外,作用域链本质上是一个指向变量对象的指针列表,它只引用但不现实包含变量对象。
4.什么是活动对象?(activation object)
如果这个环境是函数,则将其活动对象作为变量对象。函数的活动对象在最开始时只包含一个变量,即arguments对象。
个人感悟:
按我目前狭窄地知识贮备,我所知道的就是凡是函数均会将自己的活动对象作为变量对象,然后插入作用域链的最前端。
二、执行流程
1.关键性知识
先说说一些关键性的货色,再讲流程。
(1)首先,全局执行环境是最外围的一个执行环境。在Web浏览器中,全局执行环境被认为是window对象,因此全部全局变量和函数都是作为window对象的属性和方法创建的。
(2)其次,某个执行环境中的全部代码执行完毕后,该环境就会被销毁,保存在其中的全部变量和函数定义也随之销毁。像函数这样的局部环境的变量对象只在函数执行的进程中存在,而全局执行环境直到应用程序退出时才会被销毁。
(3)然后,每一个函数在第一次被调用时都会创建自己的执行环境及相应的作用域链,并把作用域链赋值给一个特别的内部属性[[Scope]],然后,应用this、arguments和其他定名参数的值来初始化函数的活动对象。(函数均会将自己的活动对象作为变量对象,用以表示自己的执行环境)
(4)最后,关于上一点还有一些关于创建作用域链的细节。以函数为例,在创建函数时,会创建一个事后包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中。当调用函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链。此后,又有一个活动对象(在此作为变量对象应用)被创建并被推入执行 环境作用域链的前端。
2.执行进程
有上面的铺垫,上面开始讲流程。
(1)开始执行代码。 // 初始环境是在全局执行环境中
(2)构建一条作用域链,链接到全局对象中 // 为window对象创建作用域链,此时链中元素只有一个对象,这就是全局执行对象
执行流进入一个函数时:
{
(3)创建函数自己的执行环境。 // 即应用this、arguments和其他定名参数的值来初始化自己的活动对象
// 并将活动对象作为变量对象,用以表示自己的执行环境
(4)构建一条作用域链,将链头指针赋值到函数的一个特别的内部属性[[Scope]]中。
// 作用域链的前端,一直都是当前执行的代码地点环境的变量对象
// 活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)
// 作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。
这样,一直延续到全局执行环境,全局执行环境的变量对象一直都是作用域链中的最后一个对象
(5)将函数的环境推入一个环境栈中。
(6)执行函数代码。
// 如果中途须要搜索标识符,则此时就是沿着作用域链一级一级地搜索,这就是作用域链的用处
(7)函数执行完毕,栈将其环境弹出,把控制权返回会之前的执行环境。
// 也就是作用域链中函数执行环境的上一个执行环境
(8)销毁该函数的环境。
//即销毁该函数的变量对象,保存在其中的全部变量和函数定义也随之销毁
}
(9)执行其他代码,退出程序时销毁全局执行环境。
好吧我否认我写得太抽象了,来个例子理解一下吧:
var color = "blue"; function changeColor(){ var anotherColor = "red"; function swapColors(){ var tempColor = anotherColor; anotherColor = color; color = tempColor; // 这里可以拜访color、anotherColor和tempColor
} // 这里可以拜访color和anotherColor,但不能拜访tempColor swapColors();
} changeColor(); // 这里不能拜访anotherColor和tempColor,但可以拜访color alert("Color is now " + color);
上图说的就是,当执行流进入swapColors函数的时候,在函数内部维护着这么一条指针链表,也即之前说的:作用域链。
通过这条作用域链,函数在搜索变量或函数的时候,有了一条有序而准确的路径。当函数须要读写某一变量的时候,首先从链表最前端即swapColors Scope(变量对象)中寻觅是不是有该变量,如有,则搜索停止,不再向上查找,若无,则沿着作用域链向上查找。
通过上图我们可以看出:
这些环境之间的联系是线性的、有顺序的。每一个环境都可以向上搜索作用域链,以查询变量和函数名;但任何环境都不能通过向下搜索作用域链而进入另一个执行环境。
三、总结
作用域链的作用体现在两个很重要的地方。第一个是标识符的解析,第二个就是闭包。
标识符解析是沿着作用域链一级一级地搜索标识符的进程。搜索进程一直从作用域链的前端开始,然后逐级地向后回溯,直至找到标识符为止,找不到则报错。
而闭包,同样须要利用到这个作用域链。关于闭包,下一篇文章再细讲哈。
专心看一大篇货色不容易,谢一个~
共勉。
文章结束给大家分享下程序员的一些笑话语录: N多年前,JohnHein博士的一项研究表明:Mac用户平均IQ要比PC用户低15%。超过6000多的参加者接受了测试,结果清晰的显示IQ比较低的人会倾向于使用Mac。Mac用户只答对了基础问题的75%,而PC用户却高达83%。