关于javascript:JS执行上下文

8次阅读

共计 5093 个字符,预计需要花费 13 分钟才能阅读完成。

一、什么是执行上下文

执行上下文(Execution Context),简称 EC。

网上有很多对于执行上下文定义的形容,简略了解一下,其实就是作用域,也就是运行这段 JavaScript 代码的一个环境。

二、执行上下文的组成和分类

1. 组成

对于每个执行上下文 EC,都有三个重要的属性:

  1. 变量对象 Variable Object(变量申明、函数申明、函数形参)
  2. 作用域链 Scope Chain
  3. this 指针

2. 分类

执行上下文分为 3 类

  1. 全局执行上下文
  2. 函数执行上下文
  3. eval 执行上下文(简直不必,临时不做解释)

全局执行上下文

术语了解

代码开始执行前首先进入的环境。

特点

全局执行上下文有且只有一个。客户端中个别由浏览器创立,也就是 window 对象。

留神点

(1)应用 var 申明的全局变量,都能够在 window 对象中拜访到,能够了解为 windowvar申明对象的载体。

(2)应用 let 申明的全局变量,用 window 对象拜访不到。

函数执行上下文

术语了解

函数被调用时,会创立一个函数执行上下文。

特点

函数执行上下文能够有多个,即便调用本身,也会创立一个新的函数执行上下午呢。

以上是对全局执行上下文和函数执行上下文的区别。

上面再来看看执行上下文的生命周期。

三、执行上下文的生命周期

执行上下文的生命周期能够分为 3 个阶段:

  1. 创立阶段
  2. 执行阶段
  3. 回收阶段

1. 创立阶段

产生在当函数被调用,然而在 未执行外部代码之 前。

创立阶段次要做的事件是:

(1)创立变量对象 Variable Object(创立函数形参、函数申明、变量申明)

(2)创立作用域链 Scope Chain

(3)确定 this 指向 This Binding

咱们先用代码来更直观的了解下创立阶段的过程:

function foo(i){
    var a = 100;
    var b = function(){};
    function c(){}
}
foo(20);

当调用 foo(20) 的时候,执行上下文的创立状态如下:

ExecutionContext:{scopeChain:{ ...},
    this:{...},
    variableObject:{
        arguments:{
            0: 20,
            length: 1
        },
        i: 20,
        c:<function>,
        a:undefined,
        b:undefined
    }
}

2. 执行阶段

创立实现后,程序主动进入执行阶段,执行阶段次要做的事件是:

(1)给变量对象赋值:给 VO 中的变量赋值,给函数表达式赋值。

(2)调用函数

(3)程序执行代码

还是以下面的代码为例,执行阶段给 VO 赋值,用伪代码示意如下:

ExecutionContext:{scopeChain:{ ...},
    this:{...},
    variableObject:{
        arguments:{
            0: 20,
            length: 1
        },
        i: 20,
        c:<function>,
        a:100,
        b:function
    }
}

3. 回收阶段

所有代码执行结束,程序敞开,开释内存。

上下文出栈后,虚拟机进行回收。
全局上下文只有当敞开浏览器时才会出栈。

依据以上内容,咱们理解到执行上下文的创立须要创立变量对象,那变量对象到底是什么呢?

四、变量对象 VO 和 流动对象 AO

1. VO 概念了解

变量对象Variable Object,简称VO。简略了解就是一个对象,这个对象寄存的是:全局执行上下文的变量和函数。

VO === this === Global

VO的两种非凡状况:

(1)未通过 var 申明的变量,不会存在 VO

(2)函数表达式(与函数申明绝对),也不在 VO

2. AO 概念了解

流动对象 Activation Object,也叫激活对象,简称 AO。
激活对象是在进入函数执行上下文时(函数执行的前一刻)被创立的。

函数执行上下文中,VO 是不能间接拜访,所以 AO 表演了 VO 的角色。

VO === AO, 并且增加了形参类数组和形参的值

Arguments Object 是函数上下文 AO 的一个对象,它蕴含的属性有:

(1)callee:指向以后函数的援用

(2)length:真正传递参数的个数

(3)properties-indexes:函数的参数值(依照参数列表从左到右排列)

3. VO 的初始化过程

(1)依据函数参数,创立并初始化 arguments

变量申明 var、函数形参、函数申明

(2)扫描函数申明

函数申明,是变量对象的一个属性,其属性名和值都是函数对象创立进去的。若变量对象曾经蕴含了雷同名字的属性,则替换它的值。

(3)扫描变量申明

变量申明,即变量对象的一个属性,其属性名即变量名,其值为 undefined。如果变量名和曾经申明的函数名或者函数的参数名雷同,则不影响曾经存在的属性。

注:函数申明优先级高于变量申明优先级

五、示例剖析

1. 如何了解函数申明中“若变量对象曾经蕴含了雷同名字的属性,则替换它的值”

用代码来了解一下:

function fun(a){console.log(a); // function a(){}
    function a(){}
}
fun(100);

咱们调用了 fun(100), 传入a 的值是 100,为什么执行 console 语句后后果却不是 100 呢?别急,咱们接着剖析~

创立阶段:

步骤 1-1:依据形参创立 arguments,用实参赋值给对应的形参,没有实参的赋值为 undefined
AO_Step1:{
    arguments:{
        0: 100,
        length:1
    },
    a: 100
}

步骤 1-2:扫描函数申明,此时发现名称为 a 的函数申明,将其增加到 AO 上,替换掉曾经存在的雷同属性名称 a,也就是替换掉形参为 a 的值。AO_Step2:{
    arguments:{
        0: 100,
        length:1
    },
    a: 指向 function a(){}
}

步骤 1-3:扫描变量申明,未发现有变量。

执行阶段:

步骤 2-1:没有赋值语句,第一行执行 console 命令,而此时 a 指向的是 funciton,所以输入 function a(){}

2. 如何了解变量申明中“如果变量名和曾经申明的函数名或者函数的参数名雷同,则不影响曾经存在的属性”

用代码来了解一下

情景 1:变量与参数名雷同

function fun2(a){console.log(a); // 100
    var a = 10;
    console.log(a) // 10
}

fun2(100);

// 剖析步骤:创立阶段:步骤 1-1:依据 arguments 创立并初始化 AO
AO = {
    arguments:{
        0: 100,
        length:1
    },
    a:100
}

步骤 1-2:扫描函数申明,此时没有额定的函数申明,所以 AO 还是和上次统一
AO = {
    arguments:{
        0: 100,
        length:1
    },
    a:100
}

步骤 1-3:扫描变量申明,发现 AO 中曾经存在了 a 属性,所以不批改已存在的属性。AO = {
    arguments:{
        0: 100,
        length:1
    },
    a:100
}

执行阶段:步骤 2-1:按程序执行 console 语句,此时 AO 中的 a 是 100,所以输入 100.
步骤 2-2:执行到赋值语句,对 AO 中的 a 进行赋值,此时 a 是 10。步骤 2-3:按程序执行,执行 console 语句,此时 a 是 10,所以输入 10。

情景 2:变量与函数名雷同

function fun3(){console.log(a); // function a(){}
    var a = 10;
    function a(){}
    console.log(a) // 10
}
fun3();

// 剖析步骤:创立阶段:步骤 1-1:依据 arguments 创立并初始化 AO
AO={
    arguments:{length:0}
}

步骤 1-2:扫描函数申明, 此时 a 指向函数申明(Function Declaration)
AO={
   arguments:{length:0}, 
   a: FD
}

步骤 1-3:扫描变量申明, 发现 AO 中曾经存在了 a 属性,则跳过,不影响已存在的属性。AO={
   arguments:{length:0}, 
   a: FD
}


执行阶段:步骤 2-1:执行第一行语句 console,此时 a 指向的是函数申明,所以输入函数申明。AO={
   arguments:{length:0}, 
   a: FD
}

步骤 2-2:执行第二句对 AO 中的变量对象进行赋值,所以 a 的值改为 10。AO={
   arguments:{length:0}, 
   a: 10
}

步骤 2-3:执行第三句,是函数申明,在执行阶段不会再将其增加到 AO 中,间接跳过。所以 AO 还是上次的状态。AO={
   arguments:{length:0}, 
   a: 10
}

步骤 2-4:执行第四句,此时 a 的值是 10,所以输入 10。AO={
   arguments:{length:0}, 
   a: 10
}

依据以上的示例,咱们曾经大抵明确了 EC 以及 EC 的生命周期。

同时,咱们晓得函数每次调用都会产生一个新的函数执行上下文。

那么,如果有若干个执行上下文呢,JavaScript 是怎么执行的?

这就波及到 执行上下文栈 的相干常识。

六、执行上下文栈

1. 术语了解

执行上下文栈(Execution context stack,ECS),简称 ECS。

简略了解就是若干个执行上下文组成了执行上下文栈。也称为执行栈、调用栈。

2. 作用

用来存储代码执行期间的所有上下文。

3. 特点

咱们晓得栈的特点是先进后出。能够了解为瓶子,先进来的货色永远在最底部。

所以

执行上下文栈的特点就是 LIFO(Last In First Out)
也就是后进先出。

4. 存储机制

  1. JS 首次执行时,会将全局执行上下文存入栈底,所以全局执行上下文永远在最底部。
  2. 当有函数调用时,会创立一个新的函数执行上下文存入执行栈。
  3. 永远是栈顶处于以后正在执行状态,执行实现后出栈,开始执行下一个。

    5. 示例剖析

    咱们用代码简略了解一下

示例 1:

function f1(){f2();
    console.log(1)
}
function f2(){f3();
    console.log(2)
}
function f3(){console.log(3)
}
f1(); // 3 2 1

依据执行栈的特点进行剖析:

(1)咱们假如执行上下文栈是数组ECStack,则ECStack=[globalContext],存入全局执行上下文(咱们暂且叫它globalStack

(2)调用 f1() 函数,进入 f1 函数开始执行,创立 f1 的函数执行上下文,存入执行栈,即ECStack.push('f1 context')

(3)f1函数外部调用了 f2() 函数,则创立 f2 的函数执行上下文,存入执行栈,即 ECStack.push('f2 context')f2 执行实现之前,f1无奈执行 console 语句

(4)f2函数外部调用了 f3() 函数,则创立 f3 的函数执行上下文,存入执行栈,即 ECStack.push('f3 context')f3 执行实现之前,f2无奈执行 console 语句

(5)f3执行实现,输入 3,并出栈,ECStack.pop()

(6)f2执行实现,输入 2,并出栈ECStack.pop()

(7)f1执行实现,输入 1,并出栈ECStack.pop()

(8)最初 ECStack 只剩 [globalContext] 全局执行上下文

示例 2:

function foo(i){if(i == 3){return}
    foo(i+1);
    console.log(i) 
}
foo(0); // 2,1,0

剖析:

(1)调用 foo 函数,创立 foo 函数的函数执行上下文,存入 EC,传0i=0if 条件不满足不执行,

(2)执行到 foo(1),再次调用foo 函数,创立一个新的函数执行上下文,存入 EC,此时传入的i1if条件不满足不执行,

(3)又执行到 foo(2),又创立新的函数执行上下文,存入EC,此时i2if条件不满足不执行

(3)又执行到 foo(3), 再次创立新的函数执行上下文,存入EC,此时i3if满足间接退出,EC弹出foo(3)

(4)EC弹出 foo(3) 后执行 foo(2) 剩下的代码,输入 2foo(2) 执行实现,EC弹出foo(2)

(5)EC弹出 foo(2) 后执行 foo(1) 剩下的代码,输入 1foo(1) 执行实现,EC弹出foo(1)

(6)EC弹出 foo(1) 后执行 foo(0) 剩下的代码,输入 0foo(0) 执行实现,EC弹出 foo(0), 此时EC 只剩下全局执行上下文。

七、总结

  1. 全局执行上下文只有一个,并且在栈底。
  2. 当浏览器敞开时,全局执行上下文才会出栈。
  3. 函数执行上下文能够有多个,并且函数每调用执行一次(即便是调用本身),就会生成一个新的函数执行上下文。
  4. Js 是单线程,所以是同步执行,执行上下文栈中,永远是处于栈顶的是执行状态。
  5. VO 或是 AO 只有一个,创立过程的程序是:参数申明 > 函数申明 > 变量申明
  6. 每个 EC 能够形象为一个对象,这个对象蕴含三个属性:作用域链、VO/AO、this
正文完
 0