执行上下文和作用域链
来源:互联网 发布:淘宝哪家耳机好 编辑:程序博客网 时间:2024/05/29 19:55
执行上下文(Execution context)
执行上下文可以认为是 代码的执行环境。
1 当代码被载入的时候,js解释器 创建一个 全局的执行上下文。
2 当执行函数时,会创建一个 函数的执行上下文。
3 当执行 eval()的时候,创建 一个 eval 执行上下文。
当js解释器开始工作的时候:
1 首先创建一个 执行上下文栈(后进先出)
2 接着创建一个 全局的执行上下文,并放入执行上下文栈中。
3 当在全局上下文中调用一个函数的时候.
为函数创建一个 执行上下文,
压入执行上下文栈中。
4 当函数执行结束的时候,位于栈顶的 执行上下文 被弹出(并销毁)。继续执行 新的位于栈顶的执行上下文。
一个执行上下文可以激活另一个上下文,就好比一个函数调用了另一个函数(或者全局的上下文调用了一个全局函数),然后一层一层调用下去。逻辑上来说,这种实现方式是栈,我们可以称之为上下文堆栈。
激活其它上下文的某个上下文被称为 调用者(caller) 。被激活的上下文被称为被调用者(callee) 。被调用者同时也可能是调用者(比如一个在全局上下文中被调用的函数调用某些自身的内部方法)。
当一个caller激活了一个callee,那么这个caller就会暂停它自身的执行,然后将控制权交给这个callee. 于是这个callee被放入堆栈,称为进行中的上下文[running/active execution context]. 当这个callee的上下文结束之后,会把控制权再次交给它的caller,然后caller会在刚才暂停的地方继续执行。在这个caller结束之后,会继续触发其他的上下文。一个callee可以用返回(return)或者抛出异常(exception)来结束自身的上下文。
如下图,所有的ECMAScript的程序执行都可以看做是一个执行上下文堆栈[execution context (EC) stack]。堆栈的顶部就是处于激活状态的上下文。
图 4. 执行上下文栈
当一段程序开始时,会先进入全局执行上下文环境[global execution context], 这个也是堆栈中最底部的元素。此全局程序会开始初始化,初始化生成必要的对象[objects]和函数[functions]. 在此全局上下文执行的过程中,它可能会激活一些方法(当然是已经初始化过的),然后进入他们的上下文环境,然后将新的元素压入堆栈。在这些初始化都结束之后,这个系统会等待一些事件(例如用户的鼠标点击等),会触发一些方法,然后进入一个新的上下文环境。
见图5,有一个函数上下文“EC1″和一个全局上下文“Global EC”,下图展现了从“Global EC”进入和退出“EC1″时栈的变化:
图 5. 执行上下文栈的变化
ECMAScript运行时系统就是这样管理代码的执行。
详细了解了这个过程之后,我们就可以对执行上下文总结一些结论了。
- 单线程
- 同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待
- 全局上下文只有唯一的一个,它在浏览器关闭时出栈
- 函数的执行上下文的个数没有限制
- 每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。
执行上下文大概形式是这样的
ExecutionContext = { variableObject: {//或activation object }, scopeChain: { ... }, this: { ... }}
下面我们分VO,scopeChain这三部分来说执行上下文里是什么。
创建VO/AO
全局上下文中的变量对象
首先,我们要给全局对象一个明确的定义:
全局对象(Global object) 是在进入任何执行上下文之前就已经创建了的对象;
这个对象只存在一份,它的属性在程序中任何地方都可以访问,全局对象的生命周期终止于程序退出那一刻。
全局对象初始创建阶段将Math、String、Date、parseInt作为自身属性,等属性初始化,同样也可以有额外创建的其它对象作为属性(其可以指向到全局对象自身)。例如,在DOM中,全局对象的window属性就可以引用全局对象自身(当然,并不是所有的具体实现都是这样):
在下列例子中
让我们看一个例子:
function test(a, b) { var c = 10; function d() {} var e = function _e() {}; (function x() {});}test(10); // call
全局对象表现为
global = { Math: <...>, String: <...> ... ... test:<reference to FunctionDeclaration "test"> window: global //引用自身};
在函数声明时,还会创建一个[[scope]]属性,指向一个链表,在这里test.[[scope]]=[global]。
全局上下文中的变量对象——在这里,变量对象就是全局对象自己:
VO(globalContext) === global;
函数上下文中的变量对象
变量对象和活动对象
在函数执行上下文中,未进入执行阶段之前,变量对象(VO)中的属性都不能访问!但是进入执行阶段之后,变量对象转变为了活动对象(AO),里面的属性都能被访问了,然后开始进行执行阶段的操作。变量对象和活动对象其实都是同一个对象,只是处于执行上下文的不同生命周期。不过只有处于函数调用栈栈顶的执行上下文中的变量对象,才会变成活动对象。
VO(functionContext) === AO;
活动对象是在进入函数上下文时刻被创建的,它通过函数的arguments属性初始化(全局上下文中不存在)。arguments属性的值是Arguments对象:
AO = {
arguments:
};
之后会添加其他属性,最终,会包含这些属性函数的所有形参、所有函数声明、所有变量声明。
变量对象的创建,依次经历了以下几个过程。
建立arguments对象。检查当前上下文中的参数,建立该对象下的属性与属性值。
检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。
检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。
变量提升
让我们看一个例子:
function test(a, b) { console.log(c);//function var c = 10; function c() {} console.log(c);//10}test(10); // call
当进入带有参数10的test函数上下文时,AO表现为如下:
第一步通过arguments新建
AO(test) = { a: 10, b: undefined};
第二步检查函数声明
AO(test) = { a: 10, b: undefined, c: <reference to FunctionDeclaration "c">};
第三步检查变量声明
AO(test) = { a: 10, b: undefined, c: <reference to FunctionDeclaration "c">//同名属性直接跳过};
当进入执行阶段
AO(test) = { a: 10, b: undefined, c: 10//执行到c=10修改};
也就是说实际的执行顺序类似于下面
function test(a, b) { function c() {} var c;//为防止已有属性被修改为undefined,不执行。 console.log(c);//function c=10; console.log(c);//10}
同样在函数声明时,也会创建一个[[scope]]属性,指向一个链表,在这里d.[[scope]]=[AO(test),global]
创建scopeChain
创建全局执行上下文时,scopeChain指向一个链表,包含global的VO。
创建函数执行上下文时,scopeChain初始化为AO+该函数的[[scope]]
scopeChain的形式类似于下图
定义this
this的指向,是在函数被调用的时候确定的,也就是执行上下文被创建时确定的。
在一个函数上下文中,this由调用者提供,由调用函数的方式来决定。如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。如果函数独立调用,那么该函数内部的this,则指向undefined。但是在非严格模式中,当this指向undefined时,它会被自动指向全局对象。
举例
var a = 20;function fn() { function foo() { console.log(this.a);//20 } foo();}fn();
这里函数独立调用,那么该函数内部的this,则指向undefined。但是在非严格模式中,当this指向undefined时,它会被自动指向全局对象。
var a = 20;var obj = { a: 10, c: this.a + 20, fn: function () { return this.a; }}console.log(obj.c);//40console.log(obj.fn());//10
这里obj仍然在全局变量对象中,this=window,而fn是由obj这个对象调用的,因此this=obj;
var o = { a:10, b:{ a:12, fn:function(){ console.log(this.a); //undefined console.log(this); //window } }}var j = o.b.fn;//只赋值未执行j();//执行,赋值this
这里j是window的一个变量,window调用了j(),因此this=window。
function foo() { console.log(this);}foo(); // globalalert(foo === foo.prototype.constructor); // true// 但是同一个function的不同的调用表达式,this是不同的foo.prototype.constructor(); // foo.prototype
这里foo是通过foo.prototype这个对象来调用的,因此this=foo.prototype
使用call,apply显示指定this
JavaScript内部提供了一种机制,让我们可以自行手动设置this的指向。它们就是call与apply。所有的函数都具有着两个方法。它们除了参数略有不同,其功能完全一样。它们的第一个参数都为this将要指向的对象。
如下例子所示。fn并非属于对象obj的方法,但是通过call,我们将fn内部的this绑定为obj,因此就可以使用this.a访问obj的a属性了。这就是call/apply的用法。
function fn() { console.log(this.a);}var obj = { a: 20}fn.call(obj);//20
new操作符会改变函数this的指向问题
function fn(num){ this.num = num;}var a = new fn(1);console.log(a.num); //1
为什么this会指向a?首先new关键字会创建一个空的对象,然后会自动调用一个函数apply方法,将this指向这个空对象,这样的话函数内部的this就会被这个空的对象替代。
也就是
var a={};fn.apply(a,[num]);
寻找标识符
标识符可以理解为变量名称、函数声明和普通参数。例如,当一个函数在自身函数体内需要引用一个变量,但是这个变量并没有在函数内部声明(或者也不是某个参数名),那么这个变量就可以称为自由变量[free variable]。那么我们搜寻这些自由变量就需要用到作用域链。
作用域链会从两个维度来搜寻。
1. 首先在原本的作用域链2. 每一个链接点的作用域的链(如果这个链接点是有prototype的话)
我们再看下面这个例子:
Object.prototype.x = 10;var w = 20;var y = 30;// 在SpiderMonkey全局对象里// 例如,全局上下文的变量对象是从"Object.prototype"继承到的// 所以我们可以得到“没有声明的全局变量”// 因为可以从原型链中获取console.log(x); // 10(function foo() { // "foo" 是局部变量 var w = 40; var x = 100; // "x" 可以从"Object.prototype"得到,注意值是10哦 // 因为{z: 50}是从它那里继承的 with ({z: 50}) { console.log(w, x, y , z); // 40, 10, 30, 50 } // 在"with"对象从作用域链删除之后 // x又可以从foo的上下文中得到了,注意这次值又回到了100哦 // "w" 也是局部变量 console.log(x, w); // 100, 40 // 在浏览器里 // 我们可以通过如下语句来得到全局的w值 console.log(window.w); // 20})();
我们就会有如下结构图示。这表示,在我们去搜寻parent之前,首先会去proto的链接中。
图 10. with增大的作用域链
总结
作用域链实际是一个指向变量对象的指针链表,它只引用而不实际包含变量对象。
参考资料:http://blog.csdn.net/ymjring/article/details/41804973
http://zhangzhaoaaa.iteye.com/blog/2251426
http://www.cnblogs.com/wangfupeng1988/p/3989357.html
https://www.cnblogs.com/lin-js/p/5293418.html
https://www.cnblogs.com/TomXu/archive/2012/01/16/2309728.html
http://www.cnblogs.com/TomXu/archive/2012/01/12/2308594.html
http://www.jianshu.com/p/a6d37c77e8db
http://www.jianshu.com/p/d647aa6d1ae6
http://www.cnblogs.com/pssp/p/5216085.html
- 执行上下文和作用域链
- 理解js作用域原型链和执行上下文
- js中的执行上下文和作用域
- 弄清楚作用域、执行上下文、变量对象、作用域链
- 深入理解javascript(13):作用域和执行上下文
- js中的作用域和执行上下文的区别
- 深入理解JS—作用域和执行上下文
- javascript作用域和执行上下文的区别
- 作用域和上下文
- JavaScript作用域、上下文、执行期上下文、作用域链、闭包
- 作用域链(执行上下文) 原型链(对象)
- 执行上下文(栈)/作用域(链)/with
- js 执行环境(上下文)、作用域链
- 执行上下文、变量对象、作用域链、this
- 作用域,作用域链,活动对象,执行上下文,静态作用域等
- 作用域学习------执行上下文环境
- JS---[[scope]]/执行上下文/作用域/this
- JS运行过程,作用域和上下文的作用,自执行函数
- RocketMQ源码简析
- 感知机的对偶形式
- oracle 多个实例互相切换实例
- Python学习笔记(2)- 字符串和数字
- spring bean加载--从缓存中获取bean
- 执行上下文和作用域链
- Linux下MySQL数据库常用基本操作 一
- Python时间模块之time
- Java-11.4/11.5作业
- jsp页面通过EL表达式取不到值解决办法
- (十)关于SeekBar的几点介绍
- centos7.2 编码编译安装nginx,实现tcp反向代理
- BZOJ-3191 卡牌游戏JLOI2013 概率DP
- 工具链的区别