深刻学习JS执行--建立执行上下文(变量对象,做用域链,this)

1、介绍

本篇继上一篇深刻理解js执行--单线程的JS,此次咱们来深刻了解js执行过程当中的执行上下文。html

本篇涉及到的名词:预执行,执行上下文,变量对象,活动对象,做用域链,this等前端

2、预执行

在上一篇说到,在js代码被执行,执行上下文会被压进执行栈中,可是在此以前还有一步工做要作,就是建立好执行上下文,由于建立好才能被压进去啊。git

建立执行上下文就是预执行过程: 接下来讲说建立执行上下文的细节部分。github

3、建立执行上下文

(1)执行上下文组成

执行上下文:也叫一个执行环境,有全局执行环境和函数执行环境两种。每一个执行环境中包含这三部分:变量对象/活动对象做用域链this的值闭包

代码模拟

//能够把执行上下文看做一个对象
exeContext = {
    VO = [...],  //VO表明变量对象,保存变量和函数声明
    scopeChain = [...];  //做用域链
    thisValue = {...};  //this的值
}

建立执行上下文就是建立变量对象,做用域链和this过程app

接下来就分别细说建立变量对象/活动对象,做用域链,this值的过程。函数

(2)变量对象(variable object)

变量对象中存储了在上下文(环境)中定义的变量和函数声明this

建立变量对象(VO)时就是将各类变量和函数声明进行提高的环节:线程

//用下面代码为例子
console.log(a);
console.log(b);
console.log(c);
console.log(d);
var a = 100;
b = 10;
function c(){};
var d = function(){};

上述代码的变量对象:code

//这里用VO表示变量对象
VO = {
    a = undefined; //有a,a使用var声明,值会被赋值为undefined
    //没有b,由于b没用var声明
    c = function c (){}  //有c,c是函数声明,而且c指向该函数
    d = undefined; //有d,d用var声明,值会被赋值为undefined
}

解说:执行上述代码的时候,会建立一个全局执行上下文,上下文中包含上面变量对象,建立完执行上下文后,这个执行上下文才会被压进执行栈中。开始执行后,由于js代码一步一步被执行,后面赋值的代码还没被执行到,因此使用console.log函数打印各个变量的值是变量对象中的值。

在运行到第二行时会报错(报错后就再也不执行了),由于没有b(b is no defined)。把第二行注释掉后,再执行各个结果就是VO里面的对应的值。

讲到这里我想你们对变量对象理解了吧,以及对变量提高和函数提高有个深刻了解。

(3)活动对象(activation object)

活动对象是在函数执行上下文里面的,其实也是变量对象,只是它须要在函数被调用时才被激活,并且初始化arguments,激活后就是看作变量对象执行上面同样的步骤。

//例子
function fn(name){
    var age = 3;
    console.log(name);
}
fn('ry');

当上面的函数fn被调用,就会建立一个执行上下文,同时活动对象被激活

//活动对象
AO = {
    arguments : {0:'ry'},  //arguments的值初始化为传入的参数
    name : ry,  //形参初始化为传进来的值
    age : undefined  //var 声明的age,赋值为undefined
}

活动对象其实也是变量对象,作着一样的工做。其实无论变量仍是活动对象,这里都代表了,全局执行和函数执行时都有一个变量对象来储存着该上下文(环境内)定义的变量和函数。

(4)做用域链(scope chain)

在建立执行上下文时还要建立一个重要的东西,就是做用域链。每一个执行环境的做用域链由当前环境的变量对象及父级环境的做用域链构成。

建立做用域链过程:

//以本段代码为例
function fn(a,b){
    var x = 'string',
}
fn(1,2);

1.函数被调用前,初始化function fn,fn有个私有属性[[scope]],它会被初始化为当前全局的做用域,fn.[[scope]="globalScope"。

2.调用函数fn(1,2),开始建立fn执行上下文,同时建立做用域链fn.scopeChain = [fn.[[scope]]],此时做用域链中有全局做用域。

3.fn活动对象AO被初始化后,把活动对象做为变量对象推到做用域链前端,此时fn.scopeChain = [fn.AO,fn.[[scope]]],构建完成,此时做用域链中有两个值,一个当前活动对象,一个全局做用域。

fn的做用域链构建完成,做用域链中有两个值,第一个是fn函数自身的活动对象,能访问自身的变量,还有一个是全局做用域,因此fn能访问外部的变量。这里就说明了为何函数中可以访问函数外部的变量,由于有做用域链,在自身找不到就顺着做用域链往上找。

(5)this的值

上面说过执行上下文有两种,一个全局执行上下文,一个函数执行上下,下面分别说说这两种上下文的this。

a.全局执行上下文的this

指向window全局对象

b.函数执行上下文的this(主要讲函数的this)

在《JavaScript权威指南》中有这么几句话:
1.this是关键字,不是变量,不是属性名,js语法不容许给this赋值。
2.关键字this没有做用域限制,嵌套的函数不会从调用它的函数中继承this。
3.若是嵌套函数做为方法调用,其this指向调用它的对象。
4.若是嵌套函数做为函数调用,其this值是window(非严格模式),或undefined(严格模式下)。

解读一下: 上面说的归纳了this两种值的状况:
1.函数直接做为某对象的方法被调用则函数的this指向该对象。
2.函数做为函数直接独立调用(不是某对象的方法),或是函数中的函数,其this指向window。

咱们看几个栗子即可理解:
栗子1:(这个例子我相信都能理解)当函数被独立运行时,其this的值指向window对象。

function a(){
    console.log(this);
}
//独立运行
a();  //window

栗子2:(函数中函数,这里嵌套了个外围函数)这里也是指向window对象,也至关于函数做为函数调用,就是独立运行。其实这个例子也说明闭包的this指向Window。

//外围函数
function a(){
    //b函数在里面
    function b(){
        console.log(this);
    }
    //虽然在函数中,但b函数独立运行,不是那个对象的方法
    b();
}
a();  //window

栗子3:(再写复杂点的话)x函数即便在对象里面,但它是函数中的函数,也是做为函数运行,不是Object的方法。getName才是objcet的方法,因此getName的this指向object(在下个栗子有)。

//一个对象
var object = {
    //getName是Object的方法
    getName : function(){
        //x是getName里面的函数,它是做为函数调用的,this就是window啦
        function x(){
            console.log(this);
        }
        x();
    }
}
object.getName();  //window

以上三个都是输出window,下面是this指向某个对象的状况。

栗子4:函数做为某个对象的方法被调用。

//一个对象
var object = {
    name : "object",
    //getName是Object的方法
    getName : function(){
        console.log(this === object);
    }
}
object.getName(); //true , 说明this指向了object

这里的getName中的this是指向objct对象的,由于getName是object的一个方法,它做为对象方法被调用。

栗子5:再来个栗子。

var name = "window";
var obj = {
    name : "obj"
};
function fn (){
    console.log(this.name);
}

//将fn经过call或bind或apply直接绑定给obj,从而成为obj的方法。
fn.call(obj);  //obj

再总结一下this的值

全局执行上下文:this的值是window
函数执行上下文:this的值两种:
1.函数中this指向某对象,由于函数做为对象的方法:怎么看函数是对象的方法,一种是直接写在对象里面(不是嵌套在对象方法中的函数,不懂再看看栗子3),另外一种是经过call等方法直接绑定在对象中。

2.函数中this指向window:函数独立运行,不是对象的方法,函数中的函数(闭包),其this指向window。

4、总结整个js代码执行过程

(1)JS执行过程

js代码执行分红了两部分:预执行和执行

  1. 预执行:建立好执行上下文,有两种,一种是开始执行js代码就建立全局的执行上下文,一种是当某个函数被调用时建立它本身的函数执行上下文。这里也就是本节主要讲的东西,建立执行上下文的三个重要成分。
  2. 执行:在执行栈中执行,栈顶的执行上下文得到执行权,并按顺序执行当前上下文中的代码,执行完后弹栈销毁上下文,执行权交给下一个栈顶执行上下文。

(2)放上图示

某个执行上下文生命周期:

5、后话

整个js的执行过程就这样了,一开始可能有点难理解,但看多几遍就慢慢领会了。但愿你们可以理解。若是以为写得好,记得点赞,关注哦。

本文出自博客园:http://www.cnblogs.com/Ry-yuan/
做者:Ry(渊源远愿)
欢迎访问个人我的首页:个人首页
欢迎访问个人github:https://github.com/Ry-yuan/demoFiles 欢迎转载,转载请标明出处,保留该字段。

相关文章
相关标签/搜索