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

資訊專欄INFORMATION COLUMN

理解ES6中的暫時(shí)死區(qū)(TDZ)

Mike617 / 3024人閱讀

摘要:以英文名詞來說明,是時(shí)間的暫時(shí)的意義,則是死區(qū),意指電波達(dá)不到的區(qū)域。所以可以翻為時(shí)間上暫時(shí)的無法達(dá)到的區(qū)域,簡稱為時(shí)間死區(qū)或暫時(shí)死區(qū)。以聲明的變量或常量,必需是經(jīng)過對聲明的賦值語句的求值后,才算初始化完成,創(chuàng)建時(shí)并不算初始化。

Temporal Dead Zone(TDZ)是ES6(ES2015)中對作用域新的專用語義。TDZ名詞并沒有明確地寫在ES6的標(biāo)準(zhǔn)文件中,一開始是出現(xiàn)在ES Discussion討論區(qū)中,是對于某些遇到在區(qū)塊作用域綁定早于聲明語句時(shí)的狀況時(shí),所使用的專用術(shù)語。

以英文名詞來說明,Temporal是"時(shí)間的、暫時(shí)的"意義,Dead Zone則是"死區(qū)",意指"電波達(dá)不到的區(qū)域"。所以TDZ可以翻為"時(shí)間上暫時(shí)的無法達(dá)到的區(qū)域",簡稱為"時(shí)間死區(qū)"或"暫時(shí)死區(qū)"。

let/const與var

在ES6的新特性中,最容易看到TDZ作用就是在let/const的使用上,let/const與var的主要不同有兩個(gè)地方:

let/const是使用區(qū)塊作用域;var是使用函數(shù)作用域

在let/const聲明之前就訪問對應(yīng)的變量與常量,會拋出ReferenceError錯誤;但在var聲明之前就訪問對應(yīng)的變量,則會得到undefined

console.log(aVar) // undefined
console.log(aLet) // causes ReferenceError: aLet is not defined
var aVar = 1
let aLet = 2

根據(jù)ES6標(biāo)準(zhǔn)中對于let/const聲明的章節(jié)13.3.1,有以下的文字說明:

The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated.

意思是說由let/const聲明的變量,當(dāng)它們包含的詞法環(huán)境(Lexical Environment)被實(shí)例化時(shí)會被創(chuàng)建,但只有在變量的詞法綁定(LexicalBinding)已經(jīng)被求值運(yùn)算后,才能夠被訪問。

注: 這里指的"變量"是let/const兩者,const在ES6定義中是constant variable(固定的變量)的意思。

說得更明白些,當(dāng)程序的控制流程在新的作用域(module, function或block作用域)進(jìn)行實(shí)例化時(shí),在此作用域中的用let/const聲明的變量會先在作用域中被創(chuàng)建出來,但因此時(shí)還未進(jìn)行詞法綁定,也就是對聲明語句進(jìn)行求值運(yùn)算,所以是不能被訪問的,訪問就會拋出錯誤。所以在這運(yùn)行流程一進(jìn)入作用域創(chuàng)建變量,到變量開始可被訪問之間的一段時(shí)間,就稱之為TDZ(暫時(shí)死區(qū))。

以上面解說來看,以let/const聲明的變量,的確也是有提升(hoist)的作用。這個(gè)是很容易被誤解的地方,實(shí)際上以let/const聲明的變量也是會有提升(hoist)的作用。提升是JS語言中對于變量聲明的基本特性,只是因?yàn)門DZ的作用,并不會像使用var來聲明變量,只是會得到undefined而已,現(xiàn)在則是會直接拋出ReferenceError錯誤,而且很明顯的這是一個(gè)在運(yùn)行期間才會出現(xiàn)的錯誤。

用一個(gè)簡單的例子來說明let聲明的變量會在作用域中被提升,就像下面這樣:

let x = "outer value"

(function() {
  // 這里會產(chǎn)生 TDZ for x
  console.log(x) // TDZ期間訪問,產(chǎn)生ReferenceError錯誤
  let x = "inner value" // 對x的聲明語句,這里結(jié)束 TDZ for x
}())

在例子中的IIFE里的函數(shù)作用域,變量x在作用域中會先被提升到函數(shù)區(qū)域中的最上面,但這時(shí)會產(chǎn)生TDZ,如果在程序流程還未運(yùn)行到x的聲明語句時(shí),算是在TDZ作用的期間,這時(shí)候訪問x的值,就會拋出ReferenceError錯誤。

在let與const聲明的章節(jié)13.3.1接著的幾句,說明有關(guān)變量是如何進(jìn)行初始化的:

A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer’s AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.

這幾句比較重點(diǎn)的部份是關(guān)于初始化的過程。以let/const聲明的變量或常量,必需是經(jīng)過對聲明的賦值語句的求值后,才算初始化完成,創(chuàng)建時(shí)并不算初始化。如果以let聲明的變量沒有賦給初始值,那么就賦值給它undefined值。也就是經(jīng)過初始化的完成,才代表著TDZ期間的真正結(jié)束,這些在作用域中的被聲明的變量才能夠正常地被訪問。

下面這個(gè)例子是一個(gè)未初始化完成的結(jié)果,它一樣是在TDZ中,也是會拋出ReferenceError錯誤:

let x = x

因?yàn)橛抑?要被賦的值),它在此時(shí)是一個(gè)還未被初始化完成的變量,實(shí)際上我們就在這一個(gè)同一表達(dá)式中要初始化它。

注: TDZ最一開始是為了const所設(shè)計(jì)的,但后來的對let的設(shè)計(jì)也是一致的,例子中都用let來說明會比較容易。

注: 在ES6標(biāo)準(zhǔn)中,對于const所聲明的識別子仍然也經(jīng)常為variable(變量),稱為constant variable(固定的變量)。以const聲明所創(chuàng)建出來的常量,在JS中只是不能再被賦(can"t re-assignment),并不是不可被改變(immutable)的,這兩種概念仍然有很大的差異。

函數(shù)的傳參預(yù)設(shè)值

TDZ作用在ES6中,很明確的就是與區(qū)塊作用域(block scope),以及變量/常量的要如何被初始化有關(guān)。實(shí)際上在許多ES6新特性中都有出現(xiàn)TDZ作用,而另一個(gè)常會被提及的是函數(shù)的傳參預(yù)設(shè)值中的TDZ作用。

下面的例子可以看到在傳參預(yù)設(shè)值的識別名稱,在未經(jīng)初始化(有賦到值)時(shí),它會進(jìn)入TDZ而產(chǎn)生錯誤,而這個(gè)錯誤是只有在函數(shù)調(diào)用時(shí),要使用到傳參預(yù)設(shè)值時(shí)才會出現(xiàn):

function foo(x = y, y = 1) {
  console.log(y)
}

foo(1) // 這不會有錯誤
foo(undefined, 1) // 錯誤 ReferenceError: y is not defined
foo() // 錯誤 ReferenceError: y is not defined

從這個(gè)例子可以知道TDZ的作用,實(shí)際上在ES6中到處都有類似的作用。

傳參預(yù)設(shè)值有另一個(gè)作用域的議題會被討論,就是對于傳參預(yù)設(shè)值的作用域,到底是屬于"全局作用域"還是"函數(shù)中的作用域"的議題,目前看到比較常見的說法是,它是處于"中介的作用域",夾在這兩者之間,但仍然會互相影響。中介的作用域的一個(gè)例子,是使用其他函數(shù)作為傳參的預(yù)設(shè)值,這通常會是一個(gè)callback(回調(diào)、回呼)函數(shù),一般的情況沒什么特別,但涉及作用域時(shí)互相影響的情況下會不易理解。下面這個(gè)例子來自這里:

let x = 1

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

foo()

console.log(x)

這個(gè)例子中的最后結(jié)果,在函數(shù)foo中輸出的x值到底是1、2還是3?另外,在最外圍作用域的x最后會被改變嗎?

函數(shù)中的x輸出結(jié)果不可能是1,這是很明確的,因?yàn)楹瘮?shù)區(qū)塊中有另一個(gè)x的聲明與賦值let x = 3語句,這兩個(gè)都有可能被運(yùn)行產(chǎn)生作用。剩下的是傳參預(yù)設(shè)值中的那個(gè)函數(shù),是不是會變量到函數(shù)區(qū)塊中的x值的問題。另一個(gè)是,在全局中的那個(gè)x變量,會不會被改變,這也是一個(gè)問題。

按照這個(gè)例子的出處文檔的說明,作者認(rèn)為答案是3與1。但是根據(jù)我的實(shí)驗(yàn),下面的幾個(gè)瀏覽器與編譯器并不是這樣認(rèn)為:

babel編譯器: 2與1

Closure Compiler: 3與2

Google Chrome(v55): 3與2

Firefox(v50): 2與1

Edge(v38): 3與2

實(shí)際測試的結(jié)果,怎么都不會有3與1的答案,要不就3與2,要不就2與1。

3與2的答案是讓b傳參的x = 2運(yùn)行出來,但因?yàn)槭艿街薪樽饔糜虻挠绊?,因此干擾不到函數(shù)中的原本區(qū)塊中的作用域,但會影響到全局中的x變量。也就是基本上認(rèn)定函數(shù)預(yù)設(shè)值中的那個(gè)callback中的作用域與全局(或外層)有關(guān)系。

2與1的答案則是倒過來,只會影響到函數(shù)中的區(qū)塊,對全局(或外層)沒有影響。

所以除非中介作用域,有自己獨(dú)立的作用域,完全與函數(shù)區(qū)塊中的作用域與全局都不相干,才有可能產(chǎn)生3與1的結(jié)果,這是這篇文檔的作者所認(rèn)為的。

這個(gè)函數(shù)預(yù)設(shè)值的作用域因?yàn)閷?shí)作不同,造成兩種不同的結(jié)果,但如果以Chrome(v55)與Firefox(v50)來實(shí)驗(yàn),在TDZ期間的拋出錯誤的行為基本上會一致,但Firefox有兩種不同的錯誤消息,例如下面的幾個(gè)例子:

// Chrome: ReferenceError: x is not defined
// Firefox: ReferenceError: x is not defined
function foo(a = 1, b = function(){ let x = 2 }){
  b()
  console.log(x)
}
foo()
// Chrome: ReferenceError: x is not defined
// Firefox: ReferenceError: can"t access lexical declaration `x" before initialization
function foo(a = 1, b = function(){ x = 2 }){
  b()
  console.log(x)
}
foo()
let x = 1
// Chrome: ReferenceError: x is not defined
// Firefox: ReferenceError: can"t access lexical declaration `x" before initialization
function foo(a = 1, b = function(){ x = 2 }){
  b()
  console.log(x)
  let x = 3
}
foo()

不管如何,這個(gè)作用域的影響仍然是有爭議的,目前并沒有統(tǒng)一的答案。這代表ES6雖然標(biāo)準(zhǔn)定好了,但里面的一些新特性仍然有實(shí)作細(xì)節(jié)的差異,未來有可能這些差異才會慢慢一致。但對一般的開發(fā)者來說,因?yàn)橹懒擞羞@些情況,所以要盡量避免,以免產(chǎn)生不兼容的情況。

要如何避免這種情況?最重要的就是,"不要在傳參預(yù)設(shè)值中作有副作用的運(yùn)算",上面的function(){ x = 2 }是有副作用的,它有可能會改變函數(shù)區(qū)塊中,或是全局中的同名稱變量,而在整個(gè)代碼中,可能會互相影響的作用域彼此間,避免使用同樣識別名稱的變量,這也是一個(gè)很基本的撰寫規(guī)則。

注: 本節(jié)的內(nèi)容可以參考這幾篇文檔TEMPORAL DEAD ZONE (TDZ) DEMYSTIFIED、ES6 Notes: Default values of parameters與這個(gè)Default parameters intermediate scope討論文。

TDZ的其它議題(陷阱) typeof語句

對TDZ期間中的變量/常量作任何的訪問動作,一律會拋出錯誤,使用typeof的語句也一樣。如下面的例子:

typeof x // "undefined"

{
  // TDZ
  typeof x // ReferenceError
  let x = 42
}

但有些開發(fā)者會認(rèn)為像typeof這樣的語句,需要被用來判斷變量是否存在,不應(yīng)該是導(dǎo)致拋出錯誤,所以有部份反對的聲音,認(rèn)為它讓typeof語句變得不安全,會造成使用上的陷阱。實(shí)際上這原本就是TDZ的設(shè)計(jì),變量本來就不該在沒聲明完成前訪問,這是為了讓JS運(yùn)行更為合理的改善設(shè)計(jì),只是之前JS在這一部份是有缺陷的作法,實(shí)際上會用typeof與undefined來判別變量/常量存在與否的方式,通常是對于全局變量的才會作的事情。

TDZ期間拋出的錯誤是運(yùn)行階段的錯誤

TDZ期間所拋出的錯誤,是一種運(yùn)行階段的錯誤,因?yàn)門DZ除了作用域的綁定過程外,還需要有變量/常量初始化的過程,才會創(chuàng)建出TDZ的期間。下面兩個(gè)例子就可以看到TDZ的錯誤需要真正運(yùn)行到才會出現(xiàn):

// 這個(gè)例子會有因TDZ拋出的錯誤
function f() { return x }
f() // ReferenceError
// 這個(gè)例子不會有錯誤
function f() { return x }
let x = 1

那這會有什么問題出現(xiàn)?因?yàn)橐軅蓽y出代碼中的因TDZ造成的錯誤,唯有透過靜態(tài)的代碼分析工具,或是要真正調(diào)用到函數(shù)運(yùn)行里面的代碼,才會產(chǎn)生錯誤,這將會讓TDZ在編譯工具中實(shí)作變得困難。

不過只要你理解TDZ的設(shè)計(jì),就知道只能這樣設(shè)計(jì),初始化過程原本就只會在調(diào)用運(yùn)行階段作這事,這部份還是只能靠其它工具來補(bǔ)強(qiáng)。

支持ES6的瀏覽器上的運(yùn)行效能

在ES Discussion上對于let/const的效能很早以前就已經(jīng)有些批評的,認(rèn)為在瀏覽器上實(shí)作的結(jié)果,由于TDZ的設(shè)計(jì),會讓let相較于var的效能至少要慢5%。

上面這篇貼文是在4年前所發(fā)表,就算是當(dāng)時(shí)的實(shí)驗(yàn)性質(zhì)的實(shí)作在JS引擎上,沒有經(jīng)過優(yōu)化,實(shí)際上真的效能有差這么大也不得而知。加上let本身在for回圈上有另外的花費(fèi),與var的設(shè)計(jì)不同,這兩個(gè)比較當(dāng)然會有所不同,是不是都是TDZ影響的也不知道。

以最近在討論區(qū)中的let與var的效能比較議題來看,let的運(yùn)行效率只有在某些情況下(for回圈中)會慢var很多,在基本的內(nèi)部作用域測試反而是快過var的,當(dāng)然這也是要視不同的瀏覽器與版本而定。

題外話是,在其它的回答中就有明確的指出,會促使加入TDZ的主因是針對const,而不是let。但最后TC39的決議是讓let與const都有一致的TDZ設(shè)計(jì)。

ES6到ES5的編譯

ES6中的許多新式的設(shè)計(jì)仍然是很新的JS語言特性,目前ES6仍然需要依賴如babel之類的編譯器,將ES6語法編譯到ES5,來進(jìn)行在瀏覽器上運(yùn)行前的最后編譯。

這些編譯器對于TDZ是會如何編譯?答案是目前"并不會直接編譯"。

以babel來說,它預(yù)設(shè)不會編譯出具有TDZ的代碼,它需要額外使用babel-plugin-transform-es2015-block-scoping或編譯時(shí)的選項(xiàng)es6.blockScopingTDZ,才會將TDZ與區(qū)域作用域的功能編譯出來。基本上這應(yīng)該屬于實(shí)驗(yàn)性質(zhì)的,而且現(xiàn)在在使用上還有滿多問題的。ES5標(biāo)準(zhǔn)中原本就沒這種設(shè)計(jì),所以說實(shí)在硬要使用也是麻煩,TDZ會造成的錯誤是運(yùn)行期間的錯誤,對于編譯器來說,在實(shí)作上也有一定的難度。

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

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

相關(guān)文章

  • 深入理解let和var的區(qū)別(暫時(shí)死區(qū))?。?!

    摘要:會出現(xiàn)這樣的情況是因?yàn)閾碛袝簳r(shí)性死區(qū)。規(guī)定暫時(shí)性死區(qū)和語句不出現(xiàn)變量提升,主要是為了減少運(yùn)行時(shí)錯誤,防止在變量聲明前就使用這個(gè)變量,從而導(dǎo)致意料之外的行為。 首先我們應(yīng)該知道js引擎在讀取js代碼時(shí)會進(jìn)行兩個(gè)步驟: 第一個(gè)步驟是解釋。 第二個(gè)步驟是執(zhí)行。 所謂解釋就是會先通篇掃描所有的Js代碼,然后把所有聲明提升到頂端,第二步是執(zhí)行,執(zhí)行就是操作一類的。 我們先來看個(gè)簡單的變量提升...

    tanglijun 評論0 收藏0
  • let,const與var的比較

    摘要:聲明一個(gè)只讀的常量。的作用域與命令相同只在聲明所在的塊級作用域內(nèi)有效。這在語法上,稱為暫時(shí)性死區(qū),簡稱。暫時(shí)性死區(qū)也意味著不再是一個(gè)百分之百安全的操作。重復(fù)聲明是允許在相同作用域內(nèi)重復(fù)聲明同一個(gè)變量的,而與不允許這一現(xiàn)象。 轉(zhuǎn)載自阮一峰老師的ES6入門,稍有修改 1.基本概念MDN var聲明了一個(gè)變量,并且可以同時(shí)初始化該變量。let語句聲明一個(gè)塊級作用域的本地變量,并且可選的賦予...

    lemon 評論0 收藏0
  • 揭秘變量提升

    摘要:想閱讀更多優(yōu)質(zhì)文章請猛戳博客一年百來篇優(yōu)質(zhì)文章等著你引用規(guī)范作者一條最近的推特變量提升是一個(gè)陳舊且令人困惑的術(shù)語。變量提升部分提前激活是在和之前聲明變量的一種較老的方法。 為了保證可讀性,本文采用意譯而非直譯。 想閱讀更多優(yōu)質(zhì)文章請猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等著你! 引用 ES6 規(guī)范作者 Allen Wirfs-Brock一條最近的推特: 變量提升是一個(gè)陳舊且令人困惑的...

    Harpsichord1207 評論0 收藏0
  • 揭秘變量提升

    摘要:想閱讀更多優(yōu)質(zhì)文章請猛戳博客一年百來篇優(yōu)質(zhì)文章等著你引用規(guī)范作者一條最近的推特變量提升是一個(gè)陳舊且令人困惑的術(shù)語。變量提升部分提前激活是在和之前聲明變量的一種較老的方法。 為了保證可讀性,本文采用意譯而非直譯。 想閱讀更多優(yōu)質(zhì)文章請猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等著你! 引用 ES6 規(guī)范作者 Allen Wirfs-Brock一條最近的推特: 變量提升是一個(gè)陳舊且令人困惑的...

    lanffy 評論0 收藏0
  • 深入理解ES6筆記(一)塊級作用域綁定

    摘要:和都能夠聲明塊級作用域,用法和是類似的,的特點(diǎn)是不會變量提升,而是被鎖在當(dāng)前塊中。聲明常量,一旦聲明,不可更改,而且常量必須初始化賦值。臨時(shí)死區(qū)臨時(shí)死區(qū)的意思是在當(dāng)前作用域的塊內(nèi),在聲明變量前的區(qū)域叫做臨時(shí)死區(qū)。 主要知識點(diǎn)有:var變量提升、let聲明、const聲明、let和const的比較、塊級綁定的應(yīng)用場景showImg(https://segmentfault.com/img...

    馬忠志 評論0 收藏0

發(fā)表評論

0條評論

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