首頁  >  文章  >  web前端  >  js中對執行上下文以及變數物件的解析

js中對執行上下文以及變數物件的解析

不言
不言原創
2018-08-14 10:02:021174瀏覽

這篇文章帶給大家的內容是關於js中對執行上下文以及變數物件的解析 ,有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。

執行上下文(Execution Context)

JavaScript程式碼執行的過程,包括編譯和執行兩個階段,編譯就是透過詞法分析,建構抽象抽象語法樹,並且編譯成機器識別的指令,在JavaScript程式碼編譯階段,作用域規則就已經確定了;在程式碼執行階段,或者函數一旦調用,便會創建執行上下文(Execution Context),也叫執行環境

在ECMA-262中有下列一段定義

當控制器轉入ECMA 腳本的可執行程式碼時,控制器會進入一個執行環境。目前活動的多個執行環境在邏輯上形成一個堆疊結構。此邏輯棧的最頂層的執行環境稱為目前運行的執行環境。任何時候,當控制器從目前執行的執行環境相關的可執行程式碼轉入與該執行環境無關的可執行程式碼時,會建立一個新的執行環境。新建的這個執行環境會被推入堆疊中,成為目前運行的執行環境.

這也是一個抽象的概念,在一段JavaScript程式碼中,會建立多個執行上下文,執行上下文定義了變數或函數有權存取的其他資料, ,透過閱讀規範及相關文檔,了解到執行上下文(簡稱EC)主要包括三個點,用偽代碼表示如下:

EC = {
    this: // 绑定this指向为当前执行上下文, 如果函数属于全局函数,则this指向window
    scopeChain: [] // 创建当前执行环境的作用域链,
    VO: {} // 当前环境的变量对象(Variable Object),每个环境都有一个与之关联的变量对象
}

看下面這段程式碼:

var a = 1;
function foo() {
    var b = 2;
    function bar() {
        console.log(b)
    }
    bar()
    console.log(a);
}

foo()
  • 1.執行這段程式碼,首先會建立全域上下文globleEC,並推入執行上下文堆疊中;

  • ##2.當調用foo()時便會建立foo的上下文fooEC,並推入執行上下文堆疊中;

  • #3.當呼叫bar()時便會建立bar的上下文barEC,並推入執行上下文堆疊中;

  • 4.當bar函數執行完,barEC就會從執行上下文堆疊中彈出;

  • 5.當foo函數執行完,fooEC便會從執行上下文堆疊中彈出;

  • #6.在瀏覽器視窗關閉後,全域上下文globleEC會從執行上下文堆疊中彈出;

總結: 堆疊底部永遠都是全域上下文,而堆疊頂部就是目前正在執行的上下文,只有當瀏覽器關閉時,全域上下文才會從執行上下文堆疊中彈出

變數物件(Variable Object):

每一個執行環境都有一個與之關聯的變數對象,是一個抽象的概念,環境中定義的所有變數和函數都保存在這個物件中。雖然我們寫的程式碼無法存取這個對象,但單解析器在處理資料時會在後台使用它們。
當瀏覽器第一次載入js腳本程式的時候, 預設進入全域執行環境, 這次的全域環境變數物件為window, 在程式碼中可以存取。

如果環境是函數, 則將此活動物件做為當前上下文的變數物件(VO = AO), 此時變數物件是不可透過程式碼來存取的,下面主要對活動物件進行講解。

活動物件(Activation Object)

1、初始化活動物件(下文縮寫為AO)

當函數一調用,立刻建立當前上下文的活動對象, 並將活動對像作為變數對象,透過arguments屬性初始化,值為arguments對象(傳入的實參集合,與形參無關,形參做為局部環境的局部變數被定義)

AO = {
  arguments: <ArgO>
};
arguments物件有以下屬性:

  • length: 真正傳遞參數的數量;

  • callee: 指向目前函數的參考,也就是被呼叫的函數;

  • #'類別index': 字串型別的整數, 值就是arguments物件中物件下標的值,arguments物件應和陣列加以區別, 它就是arguments物件,只是能和陣列具有相同的length屬性,和可以透過下標來存取值

  • #
function show (a, b, c) {
    // 通过Object.prototype.toString.call()精准判断类型, 证明arguments不同于数组类型
    var arr = [1, 2, 3];
    console.log(Object.prototype.toString.call(arr)); // [object Array]

    console.log(Object.prototype.toString.call(arguments)); // [object Arguments]

    console.log(arguments.length) // 2  传递进来实参的个数

    console.log(arguments.callee === show) // true 就是被调用的函数show自身

    //参数共享

    console.log(a === arguments[0]) // true

    a = 15;

    console.log(arguments[0]) // 15

    arguments[0] = 25;

    console.log(a)  // 25;

    但是,对于没有传进来的参数c, 和arguments的第三个索引是不共享的

    c = 25;

    console.log(arguments[2]) // undefined

    argument[2] = 35;

    console.log(c) // 25

}

show(10, 20);
接著往下走,這才是關鍵的地方,執行環境的程式碼被分成兩個階段來處理:

  1. #進入執行環境

  2. 執行函數的程式碼

2、進入執行環境

函數如果被呼叫, 進入執行環境(上下文),並立即建立活動物件, 透過arguments屬性初始化, 同時會掃描執行環境中的所有形參、所有函數宣告、所有變數宣告, 新增到活動物件(AO), 並確定this的值,然後會開始執行程式碼。

在進入執行環境這個階段:

所有形參宣告:

形參牌名稱作為活動物件屬性被創建, 如果傳遞實參, 值就為實參值, 如果沒有傳遞參數, 值就為undefined

#所有函數聲明:

函數名稱作為活動對象的屬性被創建,值是一個指標在記憶體中, 指向這個函數,如果變數物件已經存在相同名稱的屬性, 則完全替換。

所有變數宣告:

所有变量名称作为活动对象的属性被创建, 值为undefined,但是和函数声明不同的是, 如果变量名称跟已经存在的属性(形式参数和函数)相同、则不会覆盖
function foo(a, b) {
    var c = 10;
    function d() {
        console.log('d');
    }
    var e = function () {
        console.log('e');
    };
    (function f() {})
    if (true) {
        var g = 20;
    } else {
        var h = 30;
    }
}

foo(10);

此时在进入foo函数执行上下文时,foo的活动对象fooAO为:

fooAO = {
    arguments: {
        0: 10,
        length: 1
    },
    a: 10,
    b: undefined,
    c: fundefined,
    d: <d reference>  //指向d函数的指针,
    e: undefined,
    g: undefined,
    h: undefined  // 虽然else中的代码永远不会执行,但是h仍然是活动对象中的属性
}

这个例子做如下几点说明:

  • 1.关于函数,只会创建函数声明作为活动对象的属性, 而f函数作为函数表达式并不会出现在活动对象(AO)中

  • 2.e虽然值是一个函数, 但是作为变量属性被活动对象创建

3、代码执行阶段

在进入执行上下文阶段,活动对象拥有了属性,但是很多属性值为undefined, 到代码执行阶段就开始为这些属性赋值了

还是上面的代码例子, 此时活动对象如下:

fooAO = {
    arguments: {
        0: 10,
        length: 1
    },
    a: 10,
    b: undefined,
    c: 10, // 赋值为undefined
    d: <d reference>  //指向d函数的指针,
    e: <d reference>  // 指向e函数的指针
    g: 20,
    h: undefined  // 声明h变量,但是没有赋值
}

变量对象包括:{ arguments对象+函数形参+内部变量+函数声明(但不包含表达式) }

这时这个活动对象, 即作为当前执行环境的变量对象会被推到此执行环境作用域链的最前端(作用域链本篇不做介绍,会在下一篇文章中单独讲解作用域和作用域链), 假定执行环境为一个对象,则整个执行环境可以访问到的属性如下:

伪代码如下:

fooExecutionContext = {
    scopeChain: [], //fooAO +所有父执行环境的活动对象,
    fooAO: {
        arguments: {
            0: 10,
            length: 1
        },
        a: 10,
        b: undefined,
        c: 10, // 赋值为undefined
        d: <d reference>  //指向d函数的指针,
        e: <d reference>  // 指向e函数的指针
        g: 20,
        h: undefined
    },
    this: 当前执行环境的上下文指针
}

补充:

下面的例子为了说明一下变量声明的顺序及变量同名不会影响函数声明

console.log(foo); //  foo的函数体
var foo = 10;
console.log(foo) // 10
function foo() {};
foo = 20;
console.log(foo); // 20

在代码执行之前, 就会读取函数声明,变量声明的顺序在函数声明和形参声明之后, 整个流程如下:

进入执行环境阶段:

1. var VO = {}
2. VO[foo] = 'foo函数指针'
3. 扫描到var foo = 10,

 // 但是foo做为function已经声明,所以变量声明不会影响同名的函数声明,如果代码中没有foo函数声明的话,则foo为undefined

代码执行阶段:

1. VO[foo] = 10;
2. VO[foo] = 20;

解析代码完成。

相关推荐:

js对象是什么?js对象的介绍(附代码)

Js中前端模块化的详细分析及其区别对比

js中字符方法以及字符串操作方法的总结(附代码)

以上是js中對執行上下文以及變數物件的解析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn