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

資訊專欄INFORMATION COLUMN

講清楚之javascript作用域

whidy / 1869人閱讀

摘要:并且作用域鏈也確定了在當(dāng)前上下文中查找標(biāo)識(shí)符后返回的值。為了具象化分析問(wèn)題,我們可以假設(shè)作用域鏈?zhǔn)且粋€(gè)數(shù)組,數(shù)組成員有一系列變量對(duì)象組成。注意,所有作用域鏈的最末端都為全局變量對(duì)象。所以作用域作用域鏈都是在當(dāng)前運(yùn)行環(huán)境內(nèi)代碼執(zhí)行前就確定了。

什么是作用域(Scope)?

作用域產(chǎn)生于程序源代碼中定義變量的區(qū)域,在程序編碼階段就確定了。javascript 中分為全局作用域(Global context: window/global )和局部作用域(Local Scope , 又稱為函數(shù)作用域 Function context)。簡(jiǎn)單講作用域就是當(dāng)前函數(shù)的生成環(huán)境或者上下文,包含了當(dāng)前函數(shù)內(nèi)定義的變量以及對(duì)外層作用域的引用。

作用域:

作用域(Scope) -
window/global Scope 全局作用域
function Scope 函數(shù)作用域
Block Scope 塊作用域(ES6)
eval Scope eval作用域

作用域定義了一套規(guī)則,這套規(guī)則定義了引擎如何在當(dāng)前作用域或嵌套作用域根,據(jù)標(biāo)識(shí)符來(lái)查詢變量。反過(guò)來(lái)說(shuō)N個(gè)作用域組成的作用域鏈決定了函數(shù)作用域內(nèi)標(biāo)識(shí)符查找后返回的值。

所以作用域確定了當(dāng)前上下文內(nèi)定義的變量的可見(jiàn)性,即子作用域可以訪問(wèn)到當(dāng)前作用域內(nèi)屬性、函數(shù)。并且作用域鏈(Scope Chain)也確定了在當(dāng)前上下文中查找標(biāo)識(shí)符后返回的值。

Scope分為L(zhǎng)exical Scope和Dynamic Scope。Lexical Scope正如字面意思,即詞法階段定義的Scope。換種說(shuō)法,作用域是根據(jù)源代碼中變量和塊的位置,在詞法分析器(lexer)處理源代碼時(shí)設(shè)置。javascript 采用的就是詞法作用域。
作用域規(guī)則

作用域限制了函數(shù)內(nèi)變量、函數(shù)的可訪問(wèn)性。在函數(shù)內(nèi)部申明的屬性、函數(shù)屬于該函數(shù)的私有屬性,不對(duì)函數(shù)外部代碼暴露,同時(shí)函數(shù)內(nèi)部申明的嵌套函數(shù)繼承了對(duì)當(dāng)前函數(shù)內(nèi)屬性、函數(shù)的訪問(wèn)權(quán)。具體規(guī)則如下:

如果變量 a 在函數(shù)內(nèi)部定義, 則函數(shù)內(nèi)部其他變量具有訪問(wèn)變量 a 的權(quán)限,但是函數(shù)外部代碼沒(méi)有訪問(wèn)變量 a 的權(quán)限。所以同一作用域內(nèi)變量可以相互訪問(wèn),即 a、b、c 在同一個(gè)作用域他們就可以相互訪問(wèn)。這就像雞媽媽有寶寶,雞寶寶可以相互打鬧,其他雞就不能跟他們打鬧了,為什么? 因?yàn)殡u媽媽不容許~ o(^?^)o 。

let a = 1
function foo () {
    let b = 1 + a
    let c = 2
    console.log(b) // 2
}
console.log(c) // error 全局作用無(wú)法訪問(wèn)到 c
foo()

如果變量 a 在全局作用域下定義(window/global),則全局作用域下的局部作用域內(nèi)的執(zhí)行代碼或者說(shuō)是表達(dá)式都可以訪問(wèn)到變量 a 的值。局部變量里的同名變量(a)會(huì)截?cái)鄬?duì)全局變量 a 的訪問(wèn)。(這里的變量 a 就相當(dāng)于是飼養(yǎng)員,候飼養(yǎng)員會(huì)在合適的時(shí)候給雞兒們投食。但是農(nóng)場(chǎng)主為了節(jié)約成本,規(guī)定飼養(yǎng)員要就近給雞投食,當(dāng)飼養(yǎng)員1離雞寶寶更近時(shí)其他飼養(yǎng)員就不能千里迢迢跨過(guò)鴨綠江去喂雞了。)

let a = 1
let b = 2
function foo () {
    let b = 3
    function too () {
        console.log(a) // 1
        console.log(b) // 3
    }
    too()
}
foo()

再次強(qiáng)調(diào) javascript 作用域會(huì)嚴(yán)格限制變量的可訪問(wèn)范圍: 即根據(jù)源代碼中代碼和塊的位置,嵌套作用域擁有對(duì)被嵌套作用域(外層作用域)的訪問(wèn)權(quán)限。(這一條規(guī)則說(shuō)明整個(gè)農(nóng)場(chǎng)是有規(guī)則的,不能反向的投食。)

作用域鏈(Scope Chain)

作用域鏈,是由當(dāng)前環(huán)境與上層環(huán)境的一系列作用域共同組成,它保證了當(dāng)前執(zhí)行環(huán)境對(duì)符合訪問(wèn)權(quán)限的變量和函數(shù)的有序訪問(wèn)。

上面解釋的稍微有些晦澀,對(duì)于我這樣大腦不好使的就需要在大腦里重復(fù)的"讀"幾次才能明白。那么作用域鏈?zhǔn)歉陕锏模?簡(jiǎn)單的說(shuō)作用域鏈就是管理函數(shù)申明是形成的作用域嵌套(依賴)關(guān)系,并在函數(shù)運(yùn)行階段解析函數(shù)訪問(wèn)標(biāo)識(shí)符的。

再簡(jiǎn)單點(diǎn)解釋作用域鏈?zhǔn)歉陕锏模鹤饔糜蜴溇褪怯脕?lái)查找變量的,作用域鏈?zhǔn)怯梢幌盗凶饔糜虼?lián)起來(lái)的。

作用域鏈的訪問(wèn)

在函數(shù)執(zhí)行過(guò)程中,每遇到一個(gè)變量,都會(huì)經(jīng)歷一次標(biāo)識(shí)符解析過(guò)程,以決定從哪里獲取和存儲(chǔ)數(shù)據(jù)。該過(guò)程從作用域鏈頭部,也就是當(dāng)前執(zhí)行函數(shù)的作用域開(kāi)始(下圖中從左向右),查找同名的標(biāo)識(shí)符,如果找到了就返回這個(gè)標(biāo)識(shí)符對(duì)應(yīng)的值,如果沒(méi)找到繼續(xù)搜索作用域鏈中的下一個(gè)作用域,如果搜索完所有作用域都未找到,則認(rèn)為該標(biāo)識(shí)符未定義。函數(shù)執(zhí)行過(guò)程中,每個(gè)標(biāo)識(shí)符值得解析都要經(jīng)歷這樣的搜索過(guò)程。


為了具象化分析問(wèn)題,我們可以假設(shè)作用域鏈?zhǔn)且粋€(gè)數(shù)組(Scope Array),數(shù)組成員有一系列變量對(duì)象組成。我們可以在數(shù)組這個(gè)單向通道中,也就是上圖模擬從左向右查詢變量對(duì)象中的標(biāo)識(shí)符,這樣就可以訪問(wèn)到上一層作用域中的變量了。直到最頂層(全局作用域),并且一旦找到,即停止查找。所以內(nèi)層的變量可以屏蔽外層的同名變量。想象一下如果變量不是按從內(nèi)向外的查找,那整個(gè)語(yǔ)言設(shè)計(jì)會(huì)變得N復(fù)雜了(我們需要設(shè)計(jì)一套復(fù)雜的雞寶寶找食物的規(guī)則)

還是上面的栗子:

let a = 1
let b = 2
function foo () {
    let b = 3
    function too () {
        console.log(a) // 1
        console.log(b) // 3
    }
    too()
}
foo()

作用域嵌套結(jié)構(gòu)是這樣的:

栗子中,當(dāng) javascript 引擎執(zhí)行到函數(shù) too 時(shí), 全局、函數(shù) foo、函數(shù) too 的上下文分別會(huì)被創(chuàng)建。上下文內(nèi)包含它們各自的變量對(duì)象和作用域鏈(注意: 作用域鏈包含可訪問(wèn)到的上層作用域的變量對(duì)象,在上下文創(chuàng)建階段根據(jù)作用域規(guī)則被收集起來(lái)形成一個(gè)可訪問(wèn)鏈),我們?cè)O(shè)定他們的變量對(duì)象分別為VO(global),VO(foo), VO(too)。而 too 的作用域鏈,則同時(shí)包含了這三個(gè)變量對(duì)象,所以 too 的執(zhí)行上下文可如下表示:

too = {
    VO: {...},  // 變量對(duì)象
    scopeChain: [VO(too), VO(foo), VO(global)], // 作用域鏈
}

我們直接用scopeChain來(lái)表示作用域鏈數(shù)組,數(shù)組的第一項(xiàng)scopeChain[0]為作用域鏈的最前端(當(dāng)前函數(shù)的變量對(duì)象),而數(shù)組的最后一項(xiàng),為作用域鏈的最末端(全局變量對(duì)象 window )。注意,所有作用域鏈的最末端都為全局變量對(duì)象。

再舉個(gè)栗子:

let a = 1
function foo() {
    console.log(a)
}
function too() {
    let a = 2
    foo()
}
too() // 1

這個(gè)栗子如果對(duì)作用域的特點(diǎn)理解不透徹很容易以為輸出是2。但其實(shí)最終輸出的是 1。 foo() 在執(zhí)行的時(shí)候先在當(dāng)前作用域內(nèi)查找變量 a 。然后根據(jù)函數(shù)定義時(shí)的作用域關(guān)系會(huì)在當(dāng)前作用域的上層作用域里查找變量標(biāo)識(shí)符 a,所以最后查到的是全局作用域的 a 而不是 foo函數(shù)里面的 a 。

變量對(duì)象、執(zhí)行上下文會(huì)在后面介紹。
閉包

JavaScript中,函數(shù)和函數(shù)聲明時(shí)的詞法作用域形成閉包?;蛘吒ㄋ椎睦斫鉃殚]包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù),這里把閉包理解為函數(shù)內(nèi)部定義的函數(shù)。

我們來(lái)看個(gè)閉包的例子

let a = 1
function foo() {
  let a = 2
  function too() {
    console.log(a)
  }
  return too
}
foo()() // 2

這是一個(gè)閉包的栗子,一個(gè)函數(shù)執(zhí)行后返回另一個(gè)可執(zhí)行函數(shù),被返回的函數(shù)保留有對(duì)它定義時(shí)外層函數(shù)作用域的訪問(wèn)權(quán)。foo()() 調(diào)用時(shí)依次執(zhí)行了 foo、too 函數(shù)。too 雖然是在全局作用域里執(zhí)行的,但是too定義在 foo 作用域里面,根據(jù)作用域鏈規(guī)則取最近的嵌套作用域的屬性 a = 2。

再拿農(nóng)場(chǎng)的故事做比如。農(nóng)場(chǎng)主發(fā)現(xiàn)還有一種方法會(huì)更節(jié)約成本,就是讓每個(gè)雞媽媽作為家庭成員的‘飼養(yǎng)員’, 從而改變了之前的‘飼養(yǎng)結(jié)構(gòu)’。

從作用域鏈的結(jié)構(gòu)可以發(fā)現(xiàn),javascript引擎在查找變量標(biāo)識(shí)符時(shí)是依據(jù)作用域鏈依次向上查找的。當(dāng)標(biāo)識(shí)符所在的作用域位于作用域鏈的更深的位置,讀寫(xiě)的時(shí)候相對(duì)就慢一些。所以在編寫(xiě)代碼的時(shí)候應(yīng)盡量少使用全局代碼,盡可能的將全局的變量緩存在局部作用域中。

不加強(qiáng)記憶很容記錯(cuò)作用域與執(zhí)行上下文的區(qū)別。代碼的執(zhí)行過(guò)程分為編譯階段和解釋執(zhí)行階段。始終應(yīng)該記住javascript作用域在源代碼的編碼階段就確定了,而作用域鏈?zhǔn)窃诰幾g階段被收集到執(zhí)行上下文的變量對(duì)象里的。所以作用域、作用域鏈都是在當(dāng)前運(yùn)行環(huán)境內(nèi)代碼執(zhí)行前就確定了。這里暫且不過(guò)多的展開(kāi)執(zhí)行上下文的概念,可以關(guān)注后續(xù)文章。

閉包的一些優(yōu)缺點(diǎn)

閉包的用處:

用于保存私有屬性:將不需要對(duì)外暴露的屬性、函數(shù)保存在閉包函數(shù)父函數(shù)里,避免外部操作對(duì)值的干擾

避免局部屬性污染全局變量空間導(dǎo)致的命名空間混亂

模塊化封裝,將對(duì)立的功能模塊通過(guò)閉包進(jìn)去封裝,只暴露較少的 API 供外部應(yīng)用使用

閉包的缺點(diǎn):

內(nèi)存消耗:由于閉包會(huì)使得函數(shù)中的變量都被保存在內(nèi)存中,內(nèi)存消耗很大,所以不能濫用閉包,否則會(huì)造成網(wǎng)頁(yè)的性能問(wèn)題。

導(dǎo)致內(nèi)存泄露:由于IE的 js 對(duì)象和 DOM 對(duì)象使用不同的垃圾收集方法,因此閉包在IE中會(huì)導(dǎo)致內(nèi)存泄露問(wèn)題,也就是無(wú)法銷毀駐留在內(nèi)存中的元素。解決方法是,在退出函數(shù)之前,將不使用的局部變量全部刪除)。

編譯階段和解釋執(zhí)行階段會(huì)在變量對(duì)象一節(jié)詳細(xì)介紹。

關(guān)于閉包會(huì)的一些其他知識(shí)點(diǎn)在后面的章節(jié)里也會(huì)有提及,盡請(qǐng)關(guān)注。

思考

最后,再來(lái)看一個(gè)面試題:

for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}

// 5 5 5 5 5

要求對(duì)上面的代碼進(jìn)行修改,使其輸出"0 1 2 3 4"

這里也涉及到作用域鏈的概念,當(dāng)然跟 javascript 的執(zhí)行機(jī)制也有關(guān)。修改方式有很多種,下面給出一種:

for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(i);
    }(i), 1000);
}

// 0 1 2 3 4

詳細(xì)原理分析會(huì)在javascript 執(zhí)行機(jī)制一節(jié)詳細(xì)介紹。

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

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

相關(guān)文章

  • 講清楚之執(zhí)行上下文

    摘要:棧底為全局上下文,棧頂為當(dāng)前正在執(zhí)行的上下文。位于棧頂?shù)纳舷挛膱?zhí)行完畢后會(huì)自動(dòng)出棧,依次向下直至所有上下文運(yùn)行完畢,最后瀏覽器關(guān)閉時(shí)全局上下文被銷毀。 講清楚之執(zhí)行上下文 標(biāo)簽 : javascript 什么是執(zhí)行上下文? 當(dāng) JavaScript 代碼執(zhí)行一段可執(zhí)行代碼時(shí),會(huì)創(chuàng)建對(duì)應(yīng)的上下文(execution context)并將該上下文壓入上下文棧(context stack...

    3fuyu 評(píng)論0 收藏0
  • 講清楚之 javascript 對(duì)象繼承

    摘要:中的繼承并不是明確規(guī)定的,而是通過(guò)模仿實(shí)現(xiàn)的。繼承中的繼承又稱模擬類繼承。將函數(shù)抽離到全局對(duì)象中,函數(shù)內(nèi)部直接通過(guò)作用域鏈查找函數(shù)。這種范式編程是基于作用域鏈,與前面講的繼承是基于原型鏈的本質(zhì)區(qū)別是屬性查找方式的不同。 這一節(jié)梳理對(duì)象的繼承。 我們主要使用繼承來(lái)實(shí)現(xiàn)代碼的抽象和代碼的復(fù)用,在應(yīng)用層實(shí)現(xiàn)功能的封裝。 javascript 的對(duì)象繼承方式真的是百花齊放,屬性繼承、原型繼承、...

    Jonathan Shieber 評(píng)論0 收藏0
  • 講清楚之 javascript 函數(shù)

    摘要:中函數(shù)是一等公民。小明小明調(diào)用函數(shù)時(shí),傳遞給函數(shù)的值被稱為函數(shù)的實(shí)參值傳遞,對(duì)應(yīng)位置的函數(shù)參數(shù)名叫作形參。所以不推薦使用構(gòu)造函數(shù)創(chuàng)建函數(shù)因?yàn)樗枰暮瘮?shù)體作為字符串可能會(huì)阻止一些引擎優(yōu)化也會(huì)引起瀏覽器資源回收等問(wèn)題。 函數(shù) 之前幾節(jié)中圍繞著函數(shù)梳理了 this、原型鏈、作用域鏈、閉包等內(nèi)容,這一節(jié)梳理一下函數(shù)本身的一些特點(diǎn)。 javascript 中函數(shù)是一等公民。 并且函數(shù)也是對(duì)象,...

    Keagan 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<