成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

形象化模擬作用域鏈,深入理解js作用域、閉包

txgcwm / 1064人閱讀

摘要:至此作用域鏈創(chuàng)建完畢。好了,通過深入理解作用域鏈,我們能跟好的理解的運(yùn)行機(jī)制和閉包的原理。

前言

理解javascript中的作用域和作用域鏈對我們理解js這們語言。這次想深入的聊下關(guān)于js執(zhí)行的內(nèi)部機(jī)制,
主要討論下,作用域,作用域鏈,閉包的概念。為了更好的理解這些東西,我模擬了當(dāng)一個函數(shù)執(zhí)行時,js引擎做了哪些事情--那些我們看不見的動作。

關(guān)鍵詞:

執(zhí)行環(huán)境

作用域

作用域鏈

變量對象

活動對象

閉包

垃圾回收

執(zhí)行環(huán)境與作用域鏈

我們都知道js的執(zhí)行環(huán)境最外層是一個全局環(huán)境Global,在web瀏覽器的宿主環(huán)境下,window對象被認(rèn)為是全局執(zhí)行環(huán)境。在后臺的nodejs環(huán)境global作為全局變量也是我們可以直接訪問到的。
某個執(zhí)行環(huán)境中所有代碼執(zhí)行完畢后,該環(huán)境被銷毀,保存在其中的所有變量和函數(shù)定義也隨之銷毀(全局環(huán)境到應(yīng)用退出--如關(guān)閉網(wǎng)頁或?yàn)g覽器)

每個函數(shù)也有自己的執(zhí)行環(huán)境,當(dāng)執(zhí)行流進(jìn)入函數(shù)時,函數(shù)的環(huán)境被推入一個環(huán)境棧中,函數(shù)執(zhí)行完畢之后,棧將其環(huán)境彈出,把控制權(quán)返回給之前的執(zhí)行環(huán)境。

當(dāng)代碼在一個環(huán)境中執(zhí)行時,會創(chuàng)建創(chuàng)建變量對象的一個作用域鏈。
如果環(huán)境是個函數(shù),則將其活動對象作為變量對象?;顒訉ο笤谧铋_始只包含一個變量,即arguments對象,作用域鏈的下一個變量對象來自下一個包含環(huán)境,一直延續(xù)到全局環(huán)境。

下面我們模擬下這個過程。

var name = "eric";

function say(){
    var name = "xu";
    console.log(name);
}

say();//xu

輸出“xu”,而不是“eric”,這個我們也許都很好理解,因?yàn)楹瘮?shù)內(nèi)部定義了局部同名變量name,而不會使用全局的name。上面的環(huán)境中包含全局變量namesay函數(shù);當(dāng)say執(zhí)行時,js引擎做了些什么。下面我們模擬下引擎“偷偷”為我們做的事。

作用域鏈的產(chǎn)生過程

首先say()執(zhí)行時會創(chuàng)建一個執(zhí)行環(huán)境,為了形象一些,我這里以三個大括號可視化表示一個執(zhí)行環(huán)境。如:say(){{{...}}}

這個執(zhí)行環(huán)境中會自動擁有一個特殊的內(nèi)部屬性[[Scope]](為了更好的理解,可以把它想象成如果是全局環(huán)境的window,全局環(huán)境定義的變量和函數(shù)附著在這個變量上自動成為window的屬性和方法,這樣的一個局部功能“局部內(nèi)全局對象”。但其實(shí)局部的變量和函數(shù)會被附著在其活動對象上,活動對象又是作用域鏈第一個變量對象。)

函數(shù)調(diào)用時與執(zhí)行環(huán)境同時創(chuàng)建的就是相應(yīng)的作用域鏈[[Scope Chain]],并賦值給特殊變量Scope;

//step 1:創(chuàng)建執(zhí)行環(huán)境,為了形象一些,我這里以三個大括號可視化表示一個執(zhí)行環(huán)境

{{{...}}}
//step 2:創(chuàng)建作用域鏈,并賦值給特殊變量Scope,我們用數(shù)組來模擬這個作用域鏈,隨后我會解釋為什么用數(shù)組模擬

var ScopeChain = [
    FirstVariableObject,//函數(shù)內(nèi)的變量對象
    SecondVariableObject //包含這個函數(shù)的外面一層的變量對象,在上面的例子中已經(jīng)是全局環(huán)境了。
]
Scope = ScopeChain;

在作用域鏈生成之前,其實(shí)還有步驟,那就是作用域鏈數(shù)組的兩個變量對象的生成。那這兩個變量對象是什么呢?

其實(shí)第一個變量對象就是函數(shù)的活動對象【activation object】,這個活動對象可以理解成這樣一個對象

ActivationObject = {
    arguments: []  //活動對象最開始僅包含arguments(就是函數(shù)內(nèi)隱藏的arguments)
}

然后內(nèi)部this根據(jù)環(huán)境,加入活動對象

ActivationObject = {
    arguments: [],  //活動對象最開始僅包含arguments(就是函數(shù)內(nèi)隱藏的arguments)
    this: window    //這里的this根據(jù)執(zhí)行環(huán)境和調(diào)用對象的不同,會動態(tài)變化,上面的例子因?yàn)槭侨汁h(huán)境執(zhí)行的所以this指向window
}

然后開始尋找var的變量定義,或者函數(shù)聲明(我們都知道的函數(shù)聲明會被提升)。
此時的活動對象變成:

//活動對象,即函數(shù)內(nèi)部所有變量的綜合,會自動成為第一個變量對象
ActivationObject = {
    arguments: [],
    this: window,
    name: undefined //注意引擎此時并不會初始化賦值,只有讀到賦值那一行時才會賦值
}

這樣我們就能很好的理解我們熟悉的經(jīng)典例子,為什么下面的console.log不會報錯,也不是輸出"xu",而是undefined

因?yàn)槲覀兊幕顒訉ο髸詣幼優(yōu)榈谝粋€活動對象,所以第一個變量對象就等于活動對象

FirstVariableObject = ActivationObject;

同理作用域中的第二個變量對象SecondVariableObject,或者我們也可以命名為GlobalVariableObject,因?yàn)樵谏厦娴睦又幸呀?jīng)是全局環(huán)境了

//作用域鏈的第二個,也是最后一個(全局變量對象)
SecondVariableObject = {
    this: window,
    say: function (){...},
    name: "eric"
}

第二個變量對象不包含arguments,因?yàn)樗侨汁h(huán)境,而不是函數(shù)。say函數(shù)聲明被提升作為window的全局方法,還有全局的name屬性。都被掛在第二層的作用域鏈的變量對象上。

至此作用域鏈創(chuàng)建完畢。作用域鏈會成為這樣的好理解的樣子:

//形象的作用域鏈
Scope = ScopeChain = [
    {
        arguments: [],
        this: window,
        name: undefined
    },
    {
        this: window,
        say: function (){...},
        name: "eric"
    }
]
作用域鏈查找在js執(zhí)行過程中的模擬

然后js開始一句一句解析say函數(shù)的代碼,

第一句,var name = "xu"
此時,活動對象的name值才會將undefined變?yōu)?xu";

然后執(zhí)行第二句console.log(name);
這句中有一個變量name,這個時候作用域鏈就該出場了。

js引擎會開始執(zhí)行查找,首先從ActivationObject活動對象中開始找,因?yàn)榻?jīng)過var name = "eric";
此時作用域鏈的第一個,即活動對象已經(jīng)變成

{
    arguments: [],
    this: window,
    name: "xu"
}

所以輸出‘xu’,而不是‘eric’

如果我們將say函數(shù),做下改動如下:

var name = "eric";

function say(){
    var age = 99;
    console.log(name);
}

say();//eric

因?yàn)閮?nèi)部的沒有定義name變量,這個結(jié)果不出意料的我們都知道,但這個過程我把它模擬成以下查找過程:

//從當(dāng)前函數(shù)的活動對象開始,一層一層向上查找,直到頂層全局作用域
//break這句相當(dāng)重要,當(dāng)前這一層找到了,不再向上一層找了。即在這一層環(huán)境中找到了變量name

for (var i=0;i

我覺得這段代碼,可以非常形象的表達(dá)了作用域鏈的查找過程,
即首先查找第一個變量對象,其實(shí)就是函數(shù)內(nèi)部的活動對象,如果找到則不進(jìn)行下一個變量對象的查找,如果內(nèi)部函數(shù)沒有,才會沿著作用域鏈找下一個值,直到頂層的全局環(huán)境。

這就是為什么我用數(shù)組去模擬作用域鏈的原因,因?yàn)?b>作用域鏈可以理解是個有序列表(其實(shí)作用域鏈的本質(zhì)就是指向變量對象的指針列表),查找過程是按順序查找的。

通過上面的形象化解釋,是不是非常好理解作用域和作用域鏈了呢!??!

垃圾回收

我們都知道在函數(shù)執(zhí)行完畢之后,內(nèi)部的變量和內(nèi)部定義的函數(shù)會隨之銷毀,也就是被垃圾回收機(jī)制所回收,如下:

function talk(){
    var name = "eric";

    function say(){
        console.log(name);
    }

    say();
}
talk();

當(dāng)talk函數(shù)執(zhí)行后,內(nèi)部的變量name和聲明的函數(shù)say會從內(nèi)存中銷毀,但閉包的情況就不會。如:

function createTalk(){
    var name = "eric";
    var age = 99;
    return function (){
        var innerName = name;
        console.log(innerName);
    }

}
var talk = createTalk();
talk();
閉包中沒有釋放局部變量的原因

閉包的本質(zhì)其實(shí)是有權(quán)訪問另一個函數(shù)作用域中變量的函數(shù)

根據(jù)我們上面模擬的作用域鏈模型,上面的例子中當(dāng)talk執(zhí)行時,整個作用域鏈可以形象化為:

ScopeChain = [
    {
        arguments:[],
        this: window,
        innerName: undefined
    },
    {
        arguments:[],
        this: window,
        name: eric,
        age: 99
    },
    {
        this: window,
        createTalk: function (){...},
        talk: function (){...} //內(nèi)部return的匿名函數(shù)
    },
]

這樣當(dāng)createTalk執(zhí)行后,talk變量仍然保持了對函數(shù)內(nèi)部變量和內(nèi)部匿名函數(shù)的引用,因此即使createTalk執(zhí)行完畢,雖然其執(zhí)行環(huán)境被銷毀,但返回的匿名函數(shù)的作用域鏈被初始化為createTalk()函數(shù)的活動對象和全局變量對象,內(nèi)部變量仍然沒有被垃圾回收機(jī)制所回收。雖然返回的匿名函數(shù),僅使用了外一層的name變量,而沒有使用age變量。但其內(nèi)部保存的仍然是整個外層變量對象,即

{
    arguments:[],
    this: window,
    name: eric,
    age: 99
}

而不僅僅是外層的name變量一個值,因?yàn)椴檎疫^程中,使用的是整個的變量對象來查找的。因?yàn)槭遣檎?,所以存在遍歷整個對象的過程,而不是簡單的賦值

這就是為什么閉包會占用更多的內(nèi)存的原因,因?yàn)槠浔4媪苏麄€變量對象。雖然我們的例子可能就幾個,但在實(shí)際應(yīng)用中可能存在非常多。
這也是我們要謹(jǐn)慎使用閉包的原因。

閉包的經(jīng)典實(shí)例

接下來我們看一個經(jīng)典的閉包示例。

var result = [];

for (var i=0;i<10;i++){
    result[i] = function (){
        return i;
    }
}

結(jié)果或許大家都知道了,result數(shù)組的任何一個執(zhí)行,都會返回10。下面我們用上面模擬的作用鏈,形象話的看下,
比如result[9]()函數(shù)執(zhí)行的初始化作用域鏈如下:

ScopeChain = [
    //第一層是內(nèi)部匿名函數(shù)的變量對象
    {
        arguments:[],
        this: window
    },
    //第二層是外部的,也就是全局變量對象
    {
        this: window,
        result: [Array],
        i: 10 //此時全局環(huán)境的i已經(jīng)經(jīng)過for循環(huán)變成了10
    },
]

自然任何一個result的值調(diào)用函數(shù),都會是返回10。
通過變形符合預(yù)期的閉包如下:

var result = [];

for (var i=0;i<10;i++){
    result[i] = function (num){
        return function (){
            return num;
        }
    }(i);
}

上面這個經(jīng)典的閉包返回的就是我們想要的各自的i,為了更好理解,我還是使用形象的作用域鏈。
當(dāng)匿名函數(shù)執(zhí)行時,看下它的初始作用域鏈:

ScopeChain = [
    //第一層為傳入?yún)?shù)i的自執(zhí)行函數(shù)
    {
        arguments:[],
        this: window,
    },
    {
        arguments:[num],
        num: 9, 
        this: window,
    }
    {
        this: window,
        result: [Array],
        i: 10
    }
]

我們可以理解為多了一層作用域鏈的變量對象,使其能保留對num副本的引用,而不是對i的引用。

好了,通過深入理解作用域鏈,我們能跟好的理解js的運(yùn)行機(jī)制和閉包的原理。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/90025.html

相關(guān)文章

  • javascript系列--javascript深入理解--作用作用閉包的面試題解

    摘要:一概要作用域和作用域鏈?zhǔn)侵蟹浅V匾奶匦?,關(guān)系到理解整個體系,閉包是對作用域的延伸,其他語言也有閉包的特性。作用域鏈的作用他保證了變量對象的有序訪問。 一、概要 作用域和作用域鏈?zhǔn)莏s中非常重要的特性,關(guān)系到理解整個js體系,閉包是對作用域的延伸,其他語言也有閉包的特性。 那什么是作用域?作用域指的是一個變量和函數(shù)的作用范圍。 1、js中函數(shù)內(nèi)聲明的所有變量在函數(shù)體內(nèi)始終是可見的; 2...

    anRui 評論0 收藏0
  • 還是不明白JavaScript - 執(zhí)行環(huán)境、作用、作用、閉包嗎?

    摘要:所以,全局執(zhí)行環(huán)境的變量對象始終都是作用域鏈中的最后一個對象。講到這里,可能你已經(jīng)對執(zhí)行環(huán)境執(zhí)行環(huán)境對象變量對象作用域作用域鏈的理解已經(jīng)他們之間的關(guān)系有了一個較清晰的認(rèn)識。 JavaScript中的執(zhí)行環(huán)境、作用域、作用域鏈、閉包一直是一個非常有意思的話題,很多博主和大神都分享過相關(guān)的文章。這些知識點(diǎn)不僅比較抽象,不易理解,更重要的是與這些知識點(diǎn)相關(guān)的問題在面試中高頻出現(xiàn)。之前我也看過...

    jlanglang 評論0 收藏0
  • 【進(jìn)階2-3期】JavaScript深入閉包面試題解

    摘要:閉包面試題解由于作用域鏈機(jī)制的影響,閉包只能取得內(nèi)部函數(shù)的最后一個值,這引起的一個副作用就是如果內(nèi)部函數(shù)在一個循環(huán)中,那么變量的值始終為最后一個值。 (關(guān)注福利,關(guān)注本公眾號回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開始前端進(jìn)階的第二期,本周的主題是作用域閉包,今天是第8天。 本計劃一共28期,每期重點(diǎn)攻克一個面試重難點(diǎn),如果你還不了...

    alanoddsoff 評論0 收藏0
  • “動靜結(jié)合” 小白初探靜態(tài)(詞法)作用,作用與執(zhí)行環(huán)境(EC)

    摘要:圖片中的作用域鏈,是全局執(zhí)行環(huán)境中的作用域鏈。然后此活動對象被推入作用域鏈的最前端。在最后調(diào)用的時候,創(chuàng)建先構(gòu)建作用域鏈,再創(chuàng)建執(zhí)行環(huán)境,再創(chuàng)建執(zhí)行環(huán)境的時候發(fā)現(xiàn)了一個變量標(biāo)識符。 從圖書館翻過各種JS的書之后,對作用域/執(zhí)行環(huán)境/閉包這些概念有了一個比較清晰的認(rèn)識。 栗子說明一切 第一個栗子 來看一個來自ECMA-262的栗子: var x = 10; (function foo(...

    Drummor 評論0 收藏0
  • 講清楚之javascript作用

    摘要:并且作用域鏈也確定了在當(dāng)前上下文中查找標(biāo)識符后返回的值。為了具象化分析問題,我們可以假設(shè)作用域鏈?zhǔn)且粋€數(shù)組,數(shù)組成員有一系列變量對象組成。注意,所有作用域鏈的最末端都為全局變量對象。所以作用域作用域鏈都是在當(dāng)前運(yùn)行環(huán)境內(nèi)代碼執(zhí)行前就確定了。 什么是作用域(Scope)? 作用域產(chǎn)生于程序源代碼中定義變量的區(qū)域,在程序編碼階段就確定了。javascript 中分為全局作用域(Global...

    whidy 評論0 收藏0

發(fā)表評論

0條評論

txgcwm

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<