【学习笔记】JavaScript中函数执行的简要原理

293 阅读4分钟

函数执行流程

定义阶段:

确定函数的作用域[[scope]]-> 就是把函数的所有父变量都放进去

执行阶段:

  1. 创建函数的执行上下文,并将其压入上下文执行栈中。开始初始化工作
    1. 复制 [[scope]] 到作用域链
    2. 使用 Arguments 函数创建活动对象 (AO)
    3. 将 AO 压入作用域链
  2. 开始执行函数
  3. 函数执行完毕,将函数执行上下文弹出

执行上下文

1. 执行上下文栈

在JavaScript引擎执行脚本的时候,会使用执行上下文栈的东西管理函数执行的顺序极其作用域等。

在这个栈中,每一个栈帧都是一个执行上下文,最底层的是全局上下文。

2.执行上下文

执行上下文可以简单的理解为JS代码的运行环境,每一段JS代码在执行时都是在对应的上下文中进行。

上下文的分类

一共可以分成3类:

  1. 全局执行上下文

    这个上下文有且只能有一个,在上下文栈中处于最底层,在脚本执行开始时被加载

  2. 函数执行上下文

    在函数执行时创建,执行完毕时销毁。可以有多个

  3. eval执行上下文

    不常用,不在此赘述

生命周期

创建(初始化)-> 执行 -> 回收

上下文的重要组成部分*(重要!!!)*

  1. this指针
  2. 变量对象
  3. 作用域链

全局上下文

全局上下文是执行栈中最底层的执行环境,可以理解为最外围的执行环境。这个上下文有且只有一个。在浏览器中,一般是 window对象。

函数上下文

在函数上下文中,一般用活动对象来表示变量对象。因为JS环境是单线程的,因此同时只能有一个上下文在执行,而这个上下文叫做活动对象。只有活动对象才能被访问到,其余的变量对象都不能被访问。

变量对象

在创建上下文时,变量对象也会被同时创建,是上下文中的数据区域,用来存储上下文中声明的变量和函数声明。

与上下文一样,变量对象也分为两种:全局变量对象函数变量对象,分别对应全局上下文和函数上下文

全局变量对象

就是全局变量对象,浏览器中是 window

函数变量对象

上文提到过,此变量一般称为活动变量 (AO)

变量对象的初始化

在最初创建时只有 Argument

进入执行上下文(开始执行)

此时,函数AO将会将下面所有的东西都包含进去

  1. 函数的所有形参 (arguments)
  2. 声明的变量:由名称和对应值(undefind)构成,若变量对象已经存在相同名称的属性,则完全替换这个属性
  3. 声明的函数:由名称和对应函数 (function object)构成,如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性

例如:

function func(a) {
    var b = function () {
        /* codes */
    }
    function c() {
        /* codes */
    }
    var d = 0
    e = 0
}

func(1);

AO = {
    arguments : {
        0: 1,
        length: 1
    },
    a: 1,
    b: undefind,
    c: [reference to function c(){}],
    d: undefind,
    e: undefind
}

代码执行

在代码执行时,会根据代码一步一步的将AO中的变量赋值

AO = {
    arguments: {
        0: 1,
        lenght: 1
    },
    a: 1,
    b: [reference to FunctionExpression],
    c: [reference to Function],
    d: 0,
    e: 0
}

总结一下

函数变量对象的周期

  1. 创建函数上下文:创建AO。此阶段AO只有 arguments 属性
  2. 进入执行上下文:将形参,变量声明,函数声明等加入 AO
  3. 执行代码:通过代码修改 AO 中具体的属性值

作用域链

作用域

作用域就是规定变量起作用的区域,规定了JavaScript如何查找变量,也规定了其访问权限。

JavaScript使用的是静态作用域,会在函数创建的时候,确定并复制给函数的 [[scope]] 属性。

[[scope]] 中的变量就是所有父变量的副本

例如

function foo () {
    // ...
    function bar () {
        /* codes */
    }
    // ...
}

那么,这两个函数的作用域 [[ scope ]] 为:

foo.[[scope]] = [
    globalContext.VO  // 全局环境
]

bar.[[scope]] = [
    fooContext.AO,
    globalContext.VO
]

作用域的类型

静态:在函数创建之初就已经确定

动态:在函数执行时才确定

作用域链

作用域链是上下文中重要的组成部分,它规定了 JavaScript 如何查找变量。

在上下文创建时,作用域链即被创建。顺序正如文中开头提到的:

  1. 将函数的 [[scope]] 复制进去。

  2. 然后初始化AO

  3. 最后将AO复制进去。

完成后如下:

functionContext = {
    Scope: [AO, [[scope]]],
    AO:{ ... }
}

在查找变量时会按照 Scope 链中的顺序向下查找

参考资料:

ECMAScript5.1

ECMAScript5.1中文版

聊一聊javascript执行上下文

JavaScript深入之执行上下文栈

JavaScript深入之词法作用域和动态作用域

前端面试(80% 应聘者不及格系列):从闭包说起

我从来不理解JavaScript闭包,直到有人这样向我解释它...