深入学习JS执行--创建执行上下文(变量对象,作用域链,this)

一、介绍

这次我们来深入了解js执行过程中的执行上下文。

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

二、预执行

在上一篇说到,在js代码被执行,执行上下文会被压进执行栈中,但是在此之前还有一步工作要做,就是创建好执行上下文,因为创建好才能被压进去啊。

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

三、创建执行上下文

(1)执行上下文组成

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

代码模拟

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

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

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

(2)变量对象(variable object)

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

创建变量对象(VO)时就是将各种变量和函数声明进行提升的环节:

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

上述代码的变量对象:

//这里用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。

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

(1)JS执行过程

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

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

(2)放上图示

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

image

五、后话

整个js的执行过程就这样了,一开始可能有点难理解,但看多几遍就慢慢领会了。希望大家能够理解。如果觉得写得好,记得点赞,关注哦。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,113评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,644评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,340评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,449评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,445评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,166评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,442评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,105评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,601评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,066评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,161评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,792评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,351评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,352评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,584评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,618评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,916评论 2 344

推荐阅读更多精彩内容