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

資訊專(zhuān)欄INFORMATION COLUMN

ES2015系列--塊級(jí)作用域

darkbug / 3557人閱讀

摘要:在的閉包中,閉包函數(shù)能夠訪問(wèn)到包庇函數(shù)中的變量,這些閉包函數(shù)能夠訪問(wèn)到的變量也因此被稱(chēng)為自由變量。在之前最常見(jiàn)的兩種作用域,全局作用局和函數(shù)作用域局部作用域。

關(guān)于文章討論請(qǐng)?jiān)L問(wèn):https://github.com/Jocs/jocs....

當(dāng)Brendan Eich在1995年設(shè)計(jì)JavaScript第一個(gè)版本的時(shí)候,考慮的不是很周到,以至于最初版本的JavaScript有很多不完善的地方,在Douglas Crockford的《JavaScript:The Good Parts》中就總結(jié)了很多JavaScript不好的地方,比如允許!===的使用,會(huì)導(dǎo)致隱式的類(lèi)型轉(zhuǎn)換,比如在全局作用域中通過(guò)var聲明變量會(huì)成為全局對(duì)象(在瀏覽器環(huán)境中是window對(duì)象)的一個(gè)屬性,在比如var聲明的變量可以覆蓋window對(duì)象上面原生的方法和屬性等。

但是作為一門(mén)已經(jīng)被廣泛用于web開(kāi)發(fā)的計(jì)算機(jī)語(yǔ)言來(lái)說(shuō),去糾正這些設(shè)計(jì)錯(cuò)誤顯得相當(dāng)困難,因?yàn)槿绻碌恼Z(yǔ)法和老的語(yǔ)法有沖突的話,那么已有的web應(yīng)用無(wú)法運(yùn)行,瀏覽器生產(chǎn)廠商肯定不會(huì)去冒這個(gè)險(xiǎn)去實(shí)現(xiàn)這些和老的語(yǔ)法完全沖突的功能的,因?yàn)檎l(shuí)都不想失去自己的客戶,不是嗎?因此向下兼容便成了解決上述問(wèn)題的唯一途徑,也就是說(shuō)在不改變?cè)姓Z(yǔ)法特性的基礎(chǔ)上,增加一些新的語(yǔ)法或變量聲明方式等,來(lái)把新的語(yǔ)言特性引入到JavaScript語(yǔ)言中。

早在九年前,Brendan Eich在Firefox中就實(shí)現(xiàn)了第一版的let.但是let的功能和現(xiàn)有的ES2015標(biāo)準(zhǔn)規(guī)定有些出入,后來(lái)由Shu-yu Guo將let的實(shí)現(xiàn)升級(jí)到符合現(xiàn)有的ES2015標(biāo)準(zhǔn),現(xiàn)在才有了我們現(xiàn)在在最新的Firefox中使用的let 聲明變量語(yǔ)法。

問(wèn)題一:沒(méi)有塊級(jí)作用域

在ES2015之前,在函數(shù)中通過(guò)var聲明的變量,不論其在{}中還是外面,其都可以在整個(gè)函數(shù)范圍內(nèi)訪問(wèn)到,因此在函數(shù)中聲明的變量被稱(chēng)為局部變量,作用域被稱(chēng)為局部作用域,而在全局中聲明的變量存在整個(gè)全局作用域中。但是在很多情境下,我們迫切的需要塊級(jí)作用域的存在,也就是說(shuō)在{}內(nèi)部聲明的變量只能夠在{}內(nèi)部訪問(wèn)到,在{}外部無(wú)法訪問(wèn)到其內(nèi)部聲明的變量,比如下面的例子:

function foo() {
    var bar = "hello"
    if (true) {
        var zar = "world"
        console.log(zar)
    }
    console.log(zar) // 如果存在塊級(jí)作用域那么將報(bào)語(yǔ)法錯(cuò)誤:Uncaught ReferenceError
}

在上面的例子中,如果JavaScript在ES2015之前就存在塊級(jí)作用域,那么在{}之外將無(wú)法訪問(wèn)到其內(nèi)部聲明的變量zar,但是實(shí)際上,第二個(gè)console卻打印了zar的賦值,"world"。

問(wèn)題二:for循環(huán)中共享迭代變量值

在for循環(huán)初始循環(huán)變量時(shí),如果使用var聲明初始變量i,那么在整個(gè)循環(huán)中,for循環(huán)內(nèi)部將共享i的值。如下代碼:

var funcs = []
for (var i = 0; i < 10; i++) {
    funcs.push(function() {
        return i
    })
}
funcs.forEach(function(f) {
    console.log(f()) // 將在打印10數(shù)字10次
})

上面的代碼并沒(méi)有按著我們希望的方式執(zhí)行,我們本來(lái)希望是最后打印0、1、2...9這10個(gè)數(shù)字。但是最后的結(jié)果卻出乎我們的意料,而是將數(shù)字10打印了10次,究其原因,聲明的變量i在上面的整個(gè)代碼塊能夠訪問(wèn)到,也就是說(shuō),funcs數(shù)組中每一個(gè)函數(shù)返回的i都是全局聲明的變量i。也就說(shuō)在funcs中函數(shù)執(zhí)行時(shí),將返回同一個(gè)值,而變量i初始值為0,當(dāng)?shù)詈笠淮芜M(jìn)行累加,9+1 = 10時(shí),通過(guò)條件語(yǔ)句i < 10判斷為false,循環(huán)運(yùn)行完畢。最后i的值為10.也就是為什么最后所有的函數(shù)都打印為10。那么在ES2015之前能夠使上面的循環(huán)打印0、1、2、… 9嗎?答案是肯定的。

var funcs = []
for (var i = 1; i < 10; i++) {
    funcs.push((function(value) {
        return function() {
            return value
        }
    })(i))
}
funcs.forEach(function(f) {
    console.log(f())
})

在這兒我們使用了JavaScript中的兩個(gè)很棒的特性,立即執(zhí)行函數(shù)(IIFEs)和閉包(closure)。在JavaScript的閉包中,閉包函數(shù)能夠訪問(wèn)到包庇函數(shù)中的變量,這些閉包函數(shù)能夠訪問(wèn)到的變量也因此被稱(chēng)為自由變量。只要閉包沒(méi)有被銷(xiāo)毀,那么外部函數(shù)將一直在內(nèi)存中保存著這些變量,在上面的代碼中,形參value就是自由變量,return的函數(shù)是一個(gè)閉包,閉包內(nèi)部能夠訪問(wèn)到自由變量value。同時(shí)這兒我們還使用了立即執(zhí)行函數(shù),立即函數(shù)的作用就是在每次迭代的過(guò)程中,將i的值作為實(shí)參傳入立即執(zhí)行函數(shù),并執(zhí)行返回一個(gè)閉包函數(shù),這個(gè)閉包函數(shù)保存了外部的自由變量,也就是保存了當(dāng)次迭代時(shí)i的值。最后,就能夠達(dá)到我們想要的結(jié)果,調(diào)用funcs中每個(gè)函數(shù),最終返回0、1、2、… 9。

問(wèn)題三:變量提升(Hoisting)

我們先來(lái)看看函數(shù)中的變量提升, 在函數(shù)中通過(guò)var定義的變量,不論其在函數(shù)中什么位置定義的,都將被視作在函數(shù)頂部定義,這一特定被稱(chēng)為提升(Hoisting)。想知道變量提升具體是怎樣操作的,我們可以看看下面的代碼:

function foo() {
    console.log(a) // undefined
    var a = "hello"
    console.log(a) // "hello"
}

在上面的代碼中,我們可以看到,第一個(gè)console并沒(méi)有報(bào)錯(cuò)(ReferenceError)。說(shuō)明在第一個(gè)console.log(a)的時(shí)候,變量a已經(jīng)被定義了,JavaScript引擎在解析上面的代碼時(shí)實(shí)際上是像下面這樣的:

function foo() {
  var a
  console.log(a)
  a = "hello"
  console.log(a)
}

也就是說(shuō),JavaScript引擎把變量的定義和賦值分開(kāi)了,首先對(duì)變量進(jìn)行提升,將變量提升到函數(shù)的頂部,注意,這兒變量的賦值并沒(méi)有得到提升,也就是說(shuō)a = "hello"依然是在后面賦值的。因此第一次console.log(a)并沒(méi)有打印hello也沒(méi)有報(bào)ReferenceError錯(cuò)誤。而是打印undefined。無(wú)論是函數(shù)內(nèi)部還是外部,變量提升都會(huì)給我們帶來(lái)意想不到的bug。比如下面代碼:

if (!("a" in window)) {
  var a = "hello"
}
console.log(a) // undefined

很多公司都把上面的代碼作為面試前端工程師JavaScript基礎(chǔ)的面試題,其考點(diǎn)也就是考察全局環(huán)境下的變量提升,首先,答案是undefined,并不是我們期許的hello。原因就在于變量a被提升到了最上面,上面的代碼JavaScript其實(shí)是這樣解析的:

var a
if (!("a" in window)) {
  a = "hello"
}
console.log(a) // undefined

現(xiàn)在就很明了了,bianlianga被提升到了全局環(huán)境最頂部,但是變量a的賦值還是在條件語(yǔ)句內(nèi)部,我們知道通過(guò)關(guān)鍵字var在全局作用域中聲明的變量將作為全局對(duì)象(window)的一個(gè)屬性,因此"a" in windowtrue。所以if語(yǔ)句中的判斷語(yǔ)句就為false。因此條件語(yǔ)句內(nèi)部就根本不會(huì)執(zhí)行,也就是說(shuō)不會(huì)執(zhí)行賦值語(yǔ)句。最后通過(guò)console.log(a)打印也就是undefined,而不是我們想要的hello。

雖然使用關(guān)鍵詞let進(jìn)行變量聲明也會(huì)有變量提升,但是其和通過(guò)var申明的變量帶來(lái)的變量提升是不一樣的,這一點(diǎn)將在后面的letvar的區(qū)別中討論到。

關(guān)于ES2015之前作用域的概念

上面提及的一些問(wèn)題,很多都是由于JavaScript中關(guān)于作用域的細(xì)分粒度不夠,這兒我們稍微回顧一下ES2015之前關(guān)于作用域的概念。

Scope: collects and maintains a look-up list of all the declared identifiers (variables), and enforces a strict set of rules as to how these are accessible to currently executing code.

上面是關(guān)于作用域的定義,作用域就是一些規(guī)則的集合,通過(guò)這些規(guī)則我們能夠查找到當(dāng)前執(zhí)行代碼所需變量的值,這就是作用域的概念。在ES2015之前最常見(jiàn)的兩種作用域,全局作用局和函數(shù)作用域(局部作用域)。函數(shù)作用域可以嵌套,這樣就形成了一條作用域鏈,如果我們自頂向下的看,一個(gè)作用域內(nèi)部可以嵌套幾個(gè)子作用域,子作用域又可以嵌套更多的作用域,這就更像一個(gè)‘’作用域樹(shù)‘’而非作用域鏈了,作用域鏈?zhǔn)且粋€(gè)自底向上的概念,在變量查找的過(guò)程中很有用的。在ES3時(shí),引入了try catch語(yǔ)句,在catch語(yǔ)句中形成了新的作用域,外部是訪問(wèn)不到catch語(yǔ)句中的錯(cuò)誤變量。代碼如下:

try {
  throw new Error()
} catch(err) {
  console.log(err)
}
console.log(err) //Uncaught ReferenceError

再到ES5的時(shí)候,在嚴(yán)格模式下(use strict),函數(shù)中使用eval函數(shù)并不會(huì)再在原有函數(shù)中的作用域中執(zhí)行代碼或變量賦值了,而是會(huì)動(dòng)態(tài)生成一個(gè)作用域嵌套在原有函數(shù)作用域內(nèi)部。如下面代碼:

"use strict"
var a = function() {
    var b = "123"
    eval("var c = 456;console.log(c + b)") // "456123"
    console.log(b) // "123"
    console.log(c) // 報(bào)錯(cuò)
}

在非嚴(yán)格模式下,a函數(shù)內(nèi)部的console.log(c)是不會(huì)報(bào)錯(cuò)的,因?yàn)閑val會(huì)共享a函數(shù)中的作用域,但是在嚴(yán)格模式下,eval將會(huì)動(dòng)態(tài)創(chuàng)建一個(gè)新的子作用域嵌套在a函數(shù)內(nèi)部,而外部是訪問(wèn)不到這個(gè)子作用域的,也就是為什么console.log(c)會(huì)報(bào)錯(cuò)。

通過(guò)let來(lái)聲明變量

通過(guò)let關(guān)鍵字來(lái)聲明變量也通過(guò)var來(lái)聲明變量的語(yǔ)法形式相同,在某些場(chǎng)景下你甚至可以直接把var替換成let。但是使用let來(lái)申明變量與使用var來(lái)聲明變量最大的區(qū)別就是作用域的邊界不再是函數(shù),而是包含let變量聲明的代碼塊({})。下面的代碼將說(shuō)明let聲明的變量只在代碼塊內(nèi)部能夠訪問(wèn)到,在代碼塊外部將無(wú)法訪問(wèn)到代碼塊內(nèi)部使用let聲明的變量。

if (true) {
  let foo = "bar"
}
console.log(foo) // Uncaught ReferenceError

在上面的代碼中,foo變量在if語(yǔ)句中聲明并賦值。if語(yǔ)句外部卻訪問(wèn)不到foo變量,報(bào)ReferenceError錯(cuò)誤。

letvar的區(qū)別
變量提升的區(qū)別

在ECMAScript 2015中,let也會(huì)提升到代碼塊的頂部,在變量聲明之前去訪問(wèn)變量會(huì)導(dǎo)致ReferenceError錯(cuò)誤,也就是說(shuō),變量被提升到了一個(gè)所謂的“temporal dead zone”(以下簡(jiǎn)稱(chēng)TDZ)。TDZ區(qū)域從代碼塊開(kāi)始,直到顯示得變量聲明結(jié)束,在這一區(qū)域訪問(wèn)變量都會(huì)報(bào)ReferenceError錯(cuò)誤。如下代碼:

function do_something() {
  console.log(foo); // ReferenceError
  let foo = 2;
}

而通過(guò)var聲明的變量不會(huì)形成TDZ,因此在定義變量之前訪問(wèn)變量只會(huì)提示undefined,也就是上文以及討論過(guò)的var的變量提升。

全局環(huán)境聲明變量的區(qū)別

在全局環(huán)境中,通過(guò)var聲明的變量會(huì)成為window對(duì)象的一個(gè)屬性,甚至對(duì)一些原生方法的賦值會(huì)導(dǎo)致原生方法的覆蓋。比如下面對(duì)變量parseInt進(jìn)行賦值,將覆蓋原生parseInt方法。

var parseInt = function(number) {
  return "hello"
}
parseInt(123) // "hello"
window.parseInt(123) // "hello"

而通過(guò)關(guān)鍵字let在全局環(huán)境中進(jìn)行變量聲明時(shí),新的變量將不會(huì)成為全局對(duì)象的一個(gè)屬性,因此也就不會(huì)覆蓋window對(duì)象上面的一些原生方法了。如下面的例子:

let parseInt = function(number) {
  return "hello"
}
parseInt(123) // "hello"
window.parseInt(123) // 123

在上面的例子中,我們看到let生命的函數(shù)parsetInt并沒(méi)有覆蓋window對(duì)象上面的parseInt方法,因此我們通過(guò)調(diào)用window.parseInt方法時(shí),返回結(jié)果123。

在多次聲明同一變量時(shí)處理不同

在ES2015之前,可以通過(guò)var多次聲明同一個(gè)變量而不會(huì)報(bào)錯(cuò)。下面的代碼是不會(huì)報(bào)錯(cuò)的,但是是不推薦的。

var a = "xiaoming"
var a = "huangxiaoming"

其實(shí)這一特性不利于我們找出程序中的問(wèn)題,雖然有一些代碼檢測(cè)工具,比如ESLint能夠檢測(cè)到對(duì)同一個(gè)變量進(jìn)行多次聲明賦值,能夠大大減少我們程序出錯(cuò)的可能性,但畢竟不是原生支持的。不用擔(dān)心,ES2015來(lái)了,如果一個(gè)變量已經(jīng)被聲明,不論是通過(guò)var還是let或者const,該變量再次通過(guò)let聲明時(shí)都會(huì)語(yǔ)法報(bào)錯(cuò)(SyntaxError)。如下代碼:

var a = 345
let a = 123 // Uncaught SyntaxError: Identifier "a" has already been declared
最好的總是放在最后:const

通過(guò)const生命的變量將會(huì)創(chuàng)建一個(gè)對(duì)該值的一個(gè)只讀引用,也就是說(shuō),通過(guò)const聲明的原始數(shù)據(jù)類(lèi)型(number、string、boolean等),聲明后就不能夠再改變了。通過(guò)const聲明的對(duì)象,也不能改變對(duì)對(duì)象的引用,也就是說(shuō)不能夠再將另外一個(gè)對(duì)象賦值給該const聲明的變量,但是,const聲明的變量并不表示該對(duì)象就是不可變的,依然可以改變對(duì)象的屬性值,只是該變量不能再被賦值了。

const MY_FAV = 7
MY_FAY = 20 // 重復(fù)賦值將會(huì)報(bào)錯(cuò)(Uncaught TypeError: Assignment to constant variable)
const foo = {bar: "zar"}
foo.bar = "hello world" // 改變對(duì)象的屬性并不會(huì)報(bào)錯(cuò)

通過(guò)const生命的對(duì)象并不是不可變的。但是在很多場(chǎng)景下,比如在函數(shù)式編程中,我們希望聲明的變量是不可變的,不論其是原始數(shù)據(jù)類(lèi)型還是引用數(shù)據(jù)類(lèi)型。顯然現(xiàn)有的變量聲明不能夠滿足我們的需求,如下是一種聲明不可變對(duì)象的一種實(shí)現(xiàn):

const deepFreeze = function(obj) {
    Object.freeze(obj)
    for (const key in obj) {
        if (typeof obj[key] === "object") deepFreeze(obj[key])
    }
    return obj
}
const foo = deepFreeze({
  a: {b: "bar"}
})
foo.a.b = "zar"
console.log(foo.a.b) // bar
最佳實(shí)踐

在ECMAScript 2015成為最新標(biāo)準(zhǔn)之前,很多人都認(rèn)為let是解決本文開(kāi)始羅列的一系列問(wèn)題的最佳方案,對(duì)于很多JavaScript開(kāi)發(fā)者而言,他們認(rèn)為一開(kāi)始var就應(yīng)該像現(xiàn)在let一樣,現(xiàn)在let出來(lái)了,我們只需要根據(jù)現(xiàn)有的語(yǔ)法把以前代碼中的var換成let就好了。然后使用const聲明那些我們永遠(yuǎn)不會(huì)修改的值。

但是,當(dāng)很多開(kāi)發(fā)者開(kāi)始將自己的項(xiàng)目遷移到ECMAScript2015后,他們發(fā)現(xiàn),最佳實(shí)踐應(yīng)該是,盡可能的使用const,在const不能夠滿足需求的時(shí)候才使用let,永遠(yuǎn)不要使用var。為什么要盡可能的使用const呢?在JavaScript中,很多bug都是因?yàn)闊o(wú)意的改變了某值或者對(duì)象而導(dǎo)致的,通過(guò)盡可能使用const,或者上面的deepFreeze能夠很好地規(guī)避這些bug的出現(xiàn),而我的建議是:如果你喜歡函數(shù)式編程,永遠(yuǎn)不改變已經(jīng)聲明的對(duì)象,而是生成一個(gè)新的對(duì)象,那么對(duì)于你來(lái)說(shuō),const就完全夠用了。

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

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

相關(guān)文章

  • ES6系列】變量與塊級(jí)作用

    摘要:不允許在相同作用域內(nèi),重復(fù)聲明同一個(gè)變量。如但是在中則不再必要了,我們可以通過(guò)塊級(jí)作用域就能夠?qū)崿F(xiàn)本次主要針對(duì)中的變量和塊級(jí)作用域進(jìn)行了梳理學(xué)習(xí),并且通過(guò)與的實(shí)現(xiàn)方式進(jìn)行了對(duì)比,從而看出其變化以及快捷與便利。 ECMAScript 6.0(以下簡(jiǎn)稱(chēng) ES6)是 JavaScript 語(yǔ)言的下一代標(biāo)準(zhǔn),已經(jīng)在 2015 年 6 月正式發(fā)布了。它的目標(biāo),是使得 JavaScript 語(yǔ)言可...

    PascalXie 評(píng)論0 收藏0
  • ES6 走馬觀花(ECMAScript2015 新特性)

    摘要:字面上是生成器的意思,在里是迭代器生成器,用于生成一個(gè)迭代器對(duì)象。當(dāng)執(zhí)行的時(shí)候,并不執(zhí)行函數(shù)體,而是返回一個(gè)迭代器。迭代器具有方法,每次調(diào)用方法,函數(shù)就執(zhí)行到語(yǔ)句的地方。也有觀點(diǎn)極力反對(duì),認(rèn)為隱藏了本身原型鏈的語(yǔ)言特性,使其更難理解。 本文為 ES6 系列的第一篇。旨在給新同學(xué)一些指引,帶大家走近 ES6 新特性。簡(jiǎn)要介紹: 什么是 ES6 它有哪些明星特性 它可以運(yùn)行在哪些環(huán)境 ...

    wangzy2019 評(píng)論0 收藏0
  • ES6學(xué)習(xí)手稿之基本類(lèi)型擴(kuò)展

    摘要:它是一個(gè)通用標(biāo)準(zhǔn),奠定了的基本語(yǔ)法。年月發(fā)布了的第一個(gè)版本,正式名稱(chēng)就是標(biāo)準(zhǔn)簡(jiǎn)稱(chēng)。結(jié)語(yǔ)的基本擴(kuò)展還有一些沒(méi)有在這里詳細(xì)介紹。 前言 ES6標(biāo)準(zhǔn)以及頒布兩年了,但是,好像還沒(méi)有完全走進(jìn)我們的日常開(kāi)發(fā)。這篇文章從ES6的基本類(lèi)型擴(kuò)展入手,逐步展開(kāi)對(duì)ES6的介紹。 ECMAScript和JavaScript JavaScript是由Netscape創(chuàng)造的,該公司1996年11月將JavaSc...

    tommego 評(píng)論0 收藏0
  • JavaScript從初級(jí)往高級(jí)走系列————ES6

    摘要:采用二八定律,主要涉及常用且重要的部分。對(duì)象是當(dāng)前模塊的導(dǎo)出對(duì)象,用于導(dǎo)出模塊公有方法和屬性。箭頭函數(shù)函數(shù)箭頭函數(shù)把去掉,在與之間加上當(dāng)我們使用箭頭函數(shù)時(shí),函數(shù)體內(nèi)的對(duì)象,就是定義時(shí)所在的對(duì)象,而不是使用時(shí)所在的對(duì)象。 ES6 原文博客地址:https://finget.github.io/2018/05/10/javascript-es6/ 現(xiàn)在基本上開(kāi)發(fā)中都在使用ES6,瀏覽器環(huán)境...

    孫淑建 評(píng)論0 收藏0
  • ES2015入門(mén)系列2-let和const

    摘要:新增了兩個(gè)變量修飾關(guān)鍵字它們都是塊級(jí)別的,那什么是塊簡(jiǎn)單的來(lái)說(shuō),塊就是一組花括號(hào)中間的部分。全局變量使用基本上可以不用了 ES2015 新增了兩個(gè)變量修飾關(guān)鍵字: let const 它們都是塊級(jí)別的,那什么是塊?簡(jiǎn)單的來(lái)說(shuō),塊就是一組花括號(hào)中間的部分。 Var 為了理解let我們先從var說(shuō)起,如下代碼: function checkStatus(status) { if (...

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

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

0條評(píng)論

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