进击的 JavaScript(三) 之 函数执行过程

理解js 的执行过程是很重要的,比如,作用域,作用域链,变量提升,闭包啊,要想明白这些,你就得搞懂函数执行时到底发生了什么!

一、执行环境(Execution Context)又称执行上下文

当代码执行时都会产生一个执行环境。JavaScript中的执行环境可以分为三种。

  1. 全局环境:在浏览器中,全局环境被认为是window对象,因此,所有的全局变量和函数都作为window对象的 属性 和 方法 创建的。
  2. 函数环境:当一个函数执行时,就会创建该函数的执行环境,在其中执行代码。
  3. eval(不建议使用,可忽略)

函数内,没有使用var 声明的变量,在非严格模式下为window的属性,即全局变量。

二、函数调用栈(call stack)

js 是根据函数的调用(执行) 来决定 执行顺序的。每当一个函数被调用时,js 会为其创建执行环境,js引擎就会把这个执行环境 放入一个栈中 来处理。

这个栈,我们称之为函数调用栈(call stack)。栈底永远都是全局环境,而栈顶就是当前正在执行函数的环境。当栈顶的执行环境 执行完之后,就会出栈,并把执行权交给之前的执行环境。

看栗子说话:

function A(){
   console.log("this is A");
   function B(){
       console.log("this is B");
   }
   B();
}

A();

那么这段代码执行的情况就是这样了。

  1. 首先 A() ;A 函数执行了,A执行环境入栈。
  2. A 函数执行时,遇到了 B(),B 又执行了,B入栈。
  3. B 中没有可执行的函数了,B执行完 出栈。
  4. 继续执行A, A中没有可执行的函数了,A执行完 出栈。
函数调用栈

再来个不常规的:

function A(){
    
    function B(){
        console.log(666);
    }
 
    return B;
}

var C = A();
C();
//666
  1. 首先 A() ;A 函数执行了,A执行环境入栈。
  2. 继续执行A, A中没有可执行的函数了,A执行完 出栈。
  3. 然后C(), 这时的C 就是 B,A 执行后,把B返回 赋值给了C,B执行环境入栈。
  4. B 中 没有可执行的函数了,B执行完 出栈。
函数调用栈

眼尖的同学,估计看出来了,它怎么像闭包呢?其实,稍微改动下,它就是闭包了。

function A(){
    
    var say = 666
    
    function B(){
        console.log(say);
    }
 
    return B;
}

var C = A();

C();

//666

这就是闭包了,但是这次我们不讲闭包,你就知道,它是的执行是怎么回事就行。

三、执行过程

现在我们已经知道,每当一个函数执行时,一个新的执行环境就会被创建出来。其实,在js引擎内部,这个环境的创建过程可分为两个阶段:

A. 建立阶段(发生在调用(执行)一个函数时,但是在执行函数内部的具体代码之前)
       1.建立活动对象;
       2.构建作用域链;
       3.确定this的值。

B. 代码执行阶段(执行函数内部的具体代码)
       1.变量赋值;
       2.执行其它代码。

需要注意的是,作用域链是创建函数的时候就创建了,此时的链只有全局变量对象,保存在函数的[[Scope]]属性中,然后函数执行时的,只是通过复制该属性中的对象 来 构建作用域链。本文后面还有说明。

看图更清晰!

执行上下文

如果把函数执行环境看成一个对象的话:

executionContextObj = {           //执行上下文对象
            AtiveObject: { },  //活动对象
            scopeChain: { },      //作用域链
            this: {}              //this
}

//下面这段内容,感兴趣的可以看下,不感兴趣,就跳过哈。
也许你在别家看到跟我的不一样,人家写的是建立变量对象。下面我来说说我得想法吧!

之前我按照 首先建立变量对象,其后,变量对象转变为活动对象的规则 去理解,但是呢,通过我分析JavaScript高级程序设计第三版,4.2节 和 7.2节,发现根本就不符合逻辑。

然后,我根据分析,得出了我的结论:变量对象 是执行环境中保存着环境中定义的所有变量和函数 的对象 的统称。而活动对象,是函数执行环境中创建的,它不仅保存着函数执行环境中定义的变量和函数,并且独有一个arguments 属性。因此,活动对象也可称之为变量对象。

这样,很多东西就说的通了。
比如(以下都是来自JavaScript高级程序设计第三版,4.2节 和 7.2节 中原文):

如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始时只包含一个变量,即 arguments 对象(这个对象在全局环境中是不存在的)。

当某个函数被调用时,会创建一个执行环境(execution context)及相应的作用域链。
然后,使用 arguments 和其他命名参数的值来初始化函数的活动对象。

每个执行环境都有一个表示变量的对象——变量对象。

此后,又有一个活动对象(在此作为变量对象使用)被创建并被推入执
行环境作用域链的前端。对于这个例子中 compare() 函数的执行环境而言,其作用域链中包含两个变量对象:本地活动对象和全局变量对象。

有兴趣的可以去看看这本书上说的,有不同的想法可以积极留言,咱们好好探讨,哈哈。

(一)建立阶段

1、建立活动对象(AO)

A. 建立arguments对象,检查当前上下文中的参数,建立该对象下的属性以及属性值 。

B. 检查当前环境中的函数声明(使用function 声明的)。每找到一个函数声明,就在活动对象下面用函数名建立一个属性,属性值就是指向该函数在内存中的地址的一个引用,如果上述函数名已经存在于活动对象下,那么则会被新的函数引用所覆盖。

C. 检查当前上下文中的变量声明(使用 var 声明的)。每找到一个变量声明,就在活动对象下面用变量名建立一个属性,该属性值为undefined。如果该属性名已存在,则忽略新的声明。

function test(){
    function a(){};
    var b;
}
test();

test 函数 的活动对象:

testAO: {    //test变量对象
    arguments: { ... };
    a:function(){};
    b:undefined
}  

变量作用域
javaScript 中,只有两种变量作用域,一种是局部变量作用域,又称函数作用域。另一个则是全局作用域。

什么变量提升问题的根本原因就在建立阶段了。

console.log(A);

function A(){};

console.log(B);

var A = 666;
var B = 566;

console.log(A);
console.log(B);

//function A
//undefined
//666
//566

上面的实际顺序就是这样的了

function A(){};
//var A; 这个var 声明的 同名 A,会被忽略

var B = undefined;

console.log(A);

console.log(B);

A = 666;   //给A 重新赋值
B = 566;   //给B 赋值

console.log(A);
console.log(B);

注意第三点,使用var 声明时,如果VO对象下,该属性已存在,忽略新的var 声明。
因为A 使用 function 声明,VO对象下,创建A属性,然后 var 声明时,检索发现已经有该属性了,就会忽略 var A 的声明,不会把A 设置为 undefined。

2、构建作用域链
作用域链的最前端,始终都是当前执行的代码所在函数的活动对象。下一个AO(活动对象)为包含本函数的外部函数的AO,以此类推。最末端,为全局环境的变量对象。

注意:虽然作用域链是在函数调用时构建的,但是,它跟调用顺序(进入调用栈的顺序)无关,因为它只跟 包含关系(函数 包含 函数 的嵌套关系) 有关。

可能比较绕口,还是来个小栗子,再来个图

function fa(){
    var va = "this is fa";
    
    function fb(){
        var vb = "this is fb";
    
        console.log(vb);
        
        console.log(va);
    }
    return fb;
}
var fc = fa();
fc();

//"this is fb"
//"this is fa"

函数调用栈的情况就是这样:

函数调用栈

那么把函数 fb 的执行环境比作对象(建立阶段):

fbEC = {           //执行上下文对象

            fbAO: {   //活动对象 AO
            
                  arguments: { ... };   //arguments 对象

                  vb: undefined   //变量声明建立的属性,设置为undefined
            },
            
            scopeChain: [ AO(fa), AO(fb), VO(window) ],      //作用域链
            
            this: { ... }              //this
}

fb作用域的展开就是这样的:

作用域链

fb 函数 被 fa 函数 包含, fa 函数 被 window 全局环境包含。作用域链只跟包含关系有关!

注意:作用域链是单向的,因此,函数内的可以访问函数外 和 全局的变量,函数,但是反过来,函数外,全局内 不能访问函数内的变量,函数。

3、确定 this 指向
所以说 this 的指向,是在函数执行时确定的。

(二)执行阶段

1、变量赋值
根据代码顺序执行,遇到变量赋值时, 给对应的变量赋值。

function getColor(){
    console.log(color);
    
    var color;
    console.log(color);
    
    color = "red";
    console.log(color);
}
getColor();
//undefined
//undefined
//"red";

3、执行其他代码。

当函数执行完毕后,局部活动对象就会被销毁(也就是说,局部的变量,函数,arguments 等都会被销毁),内存中仅保存全局作用域(全局执行环境的变量对象)。

这句话对理解闭包很重要,随后,我会出一个闭包的文章,敬请期待!

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

推荐阅读更多精彩内容