国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

ES2015系列--塊級作用域

darkbug / 3548人閱讀

摘要:在的閉包中,閉包函數能夠訪問到包庇函數中的變量,這些閉包函數能夠訪問到的變量也因此被稱為自由變量。在之前最常見的兩種作用域,全局作用局和函數作用域局部作用域。

關于文章討論請訪問:https://github.com/Jocs/jocs....

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

但是作為一門已經被廣泛用于web開發的計算機語言來說,去糾正這些設計錯誤顯得相當困難,因為如果新的語法和老的語法有沖突的話,那么已有的web應用無法運行,瀏覽器生產廠商肯定不會去冒這個險去實現這些和老的語法完全沖突的功能的,因為誰都不想失去自己的客戶,不是嗎?因此向下兼容便成了解決上述問題的唯一途徑,也就是說在不改變原有語法特性的基礎上,增加一些新的語法或變量聲明方式等,來把新的語言特性引入到JavaScript語言中。

早在九年前,Brendan Eich在Firefox中就實現了第一版的let.但是let的功能和現有的ES2015標準規定有些出入,后來由Shu-yu Guo將let的實現升級到符合現有的ES2015標準,現在才有了我們現在在最新的Firefox中使用的let 聲明變量語法。

問題一:沒有塊級作用域

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

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

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

問題二:for循環中共享迭代變量值

在for循環初始循環變量時,如果使用var聲明初始變量i,那么在整個循環中,for循環內部將共享i的值。如下代碼:

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

上面的代碼并沒有按著我們希望的方式執行,我們本來希望是最后打印0、1、2...9這10個數字。但是最后的結果卻出乎我們的意料,而是將數字10打印了10次,究其原因,聲明的變量i在上面的整個代碼塊能夠訪問到,也就是說,funcs數組中每一個函數返回的i都是全局聲明的變量i。也就說在funcs中函數執行時,將返回同一個值,而變量i初始值為0,當迭代最后一次進行累加,9+1 = 10時,通過條件語句i < 10判斷為false,循環運行完畢。最后i的值為10.也就是為什么最后所有的函數都打印為10。那么在ES2015之前能夠使上面的循環打印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中的兩個很棒的特性,立即執行函數(IIFEs)和閉包(closure)。在JavaScript的閉包中,閉包函數能夠訪問到包庇函數中的變量,這些閉包函數能夠訪問到的變量也因此被稱為自由變量。只要閉包沒有被銷毀,那么外部函數將一直在內存中保存著這些變量,在上面的代碼中,形參value就是自由變量,return的函數是一個閉包,閉包內部能夠訪問到自由變量value。同時這兒我們還使用了立即執行函數,立即函數的作用就是在每次迭代的過程中,將i的值作為實參傳入立即執行函數,并執行返回一個閉包函數,這個閉包函數保存了外部的自由變量,也就是保存了當次迭代時i的值。最后,就能夠達到我們想要的結果,調用funcs中每個函數,最終返回0、1、2、… 9。

問題三:變量提升(Hoisting)

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

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

在上面的代碼中,我們可以看到,第一個console并沒有報錯(ReferenceError)。說明在第一個console.log(a)的時候,變量a已經被定義了,JavaScript引擎在解析上面的代碼時實際上是像下面這樣的:

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

也就是說,JavaScript引擎把變量的定義和賦值分開了,首先對變量進行提升,將變量提升到函數的頂部,注意,這兒變量的賦值并沒有得到提升,也就是說a = "hello"依然是在后面賦值的。因此第一次console.log(a)并沒有打印hello也沒有報ReferenceError錯誤。而是打印undefined。無論是函數內部還是外部,變量提升都會給我們帶來意想不到的bug。比如下面代碼:

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

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

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

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

雖然使用關鍵詞let進行變量聲明也會有變量提升,但是其和通過var申明的變量帶來的變量提升是不一樣的,這一點將在后面的letvar的區別中討論到。

關于ES2015之前作用域的概念

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

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.

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

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

再到ES5的時候,在嚴格模式下(use strict),函數中使用eval函數并不會再在原有函數中的作用域中執行代碼或變量賦值了,而是會動態生成一個作用域嵌套在原有函數作用域內部。如下面代碼:

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

在非嚴格模式下,a函數內部的console.log(c)是不會報錯的,因為eval會共享a函數中的作用域,但是在嚴格模式下,eval將會動態創建一個新的子作用域嵌套在a函數內部,而外部是訪問不到這個子作用域的,也就是為什么console.log(c)會報錯。

通過let來聲明變量

通過let關鍵字來聲明變量也通過var來聲明變量的語法形式相同,在某些場景下你甚至可以直接把var替換成let。但是使用let來申明變量與使用var來聲明變量最大的區別就是作用域的邊界不再是函數,而是包含let變量聲明的代碼塊({})。下面的代碼將說明let聲明的變量只在代碼塊內部能夠訪問到,在代碼塊外部將無法訪問到代碼塊內部使用let聲明的變量。

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

在上面的代碼中,foo變量在if語句中聲明并賦值。if語句外部卻訪問不到foo變量,報ReferenceError錯誤。

letvar的區別
變量提升的區別

在ECMAScript 2015中,let也會提升到代碼塊的頂部,在變量聲明之前去訪問變量會導致ReferenceError錯誤,也就是說,變量被提升到了一個所謂的“temporal dead zone”(以下簡稱TDZ)。TDZ區域從代碼塊開始,直到顯示得變量聲明結束,在這一區域訪問變量都會報ReferenceError錯誤。如下代碼:

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

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

全局環境聲明變量的區別

在全局環境中,通過var聲明的變量會成為window對象的一個屬性,甚至對一些原生方法的賦值會導致原生方法的覆蓋。比如下面對變量parseInt進行賦值,將覆蓋原生parseInt方法。

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

而通過關鍵字let在全局環境中進行變量聲明時,新的變量將不會成為全局對象的一個屬性,因此也就不會覆蓋window對象上面的一些原生方法了。如下面的例子:

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

在上面的例子中,我們看到let生命的函數parsetInt并沒有覆蓋window對象上面的parseInt方法,因此我們通過調用window.parseInt方法時,返回結果123。

在多次聲明同一變量時處理不同

在ES2015之前,可以通過var多次聲明同一個變量而不會報錯。下面的代碼是不會報錯的,但是是不推薦的。

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

其實這一特性不利于我們找出程序中的問題,雖然有一些代碼檢測工具,比如ESLint能夠檢測到對同一個變量進行多次聲明賦值,能夠大大減少我們程序出錯的可能性,但畢竟不是原生支持的。不用擔心,ES2015來了,如果一個變量已經被聲明,不論是通過var還是let或者const,該變量再次通過let聲明時都會語法報錯(SyntaxError)。如下代碼:

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

通過const生命的變量將會創建一個對該值的一個只讀引用,也就是說,通過const聲明的原始數據類型(number、string、boolean等),聲明后就不能夠再改變了。通過const聲明的對象,也不能改變對對象的引用,也就是說不能夠再將另外一個對象賦值給該const聲明的變量,但是,const聲明的變量并不表示該對象就是不可變的,依然可以改變對象的屬性值,只是該變量不能再被賦值了。

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

通過const生命的對象并不是不可變的。但是在很多場景下,比如在函數式編程中,我們希望聲明的變量是不可變的,不論其是原始數據類型還是引用數據類型。顯然現有的變量聲明不能夠滿足我們的需求,如下是一種聲明不可變對象的一種實現:

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
最佳實踐

在ECMAScript 2015成為最新標準之前,很多人都認為let是解決本文開始羅列的一系列問題的最佳方案,對于很多JavaScript開發者而言,他們認為一開始var就應該像現在let一樣,現在let出來了,我們只需要根據現有的語法把以前代碼中的var換成let就好了。然后使用const聲明那些我們永遠不會修改的值。

但是,當很多開發者開始將自己的項目遷移到ECMAScript2015后,他們發現,最佳實踐應該是,盡可能的使用const,在const不能夠滿足需求的時候才使用let,永遠不要使用var。為什么要盡可能的使用const呢?在JavaScript中,很多bug都是因為無意的改變了某值或者對象而導致的,通過盡可能使用const,或者上面的deepFreeze能夠很好地規避這些bug的出現,而我的建議是:如果你喜歡函數式編程,永遠不改變已經聲明的對象,而是生成一個新的對象,那么對于你來說,const就完全夠用了。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90856.html

相關文章

  • ES6系列】變量與塊級作用

    摘要:不允許在相同作用域內,重復聲明同一個變量。如但是在中則不再必要了,我們可以通過塊級作用域就能夠實現本次主要針對中的變量和塊級作用域進行了梳理學習,并且通過與的實現方式進行了對比,從而看出其變化以及快捷與便利。 ECMAScript 6.0(以下簡稱 ES6)是 JavaScript 語言的下一代標準,已經在 2015 年 6 月正式發布了。它的目標,是使得 JavaScript 語言可...

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

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

    wangzy2019 評論0 收藏0
  • ES6學習手稿之基本類型擴展

    摘要:它是一個通用標準,奠定了的基本語法。年月發布了的第一個版本,正式名稱就是標準簡稱。結語的基本擴展還有一些沒有在這里詳細介紹。 前言 ES6標準以及頒布兩年了,但是,好像還沒有完全走進我們的日常開發。這篇文章從ES6的基本類型擴展入手,逐步展開對ES6的介紹。 ECMAScript和JavaScript JavaScript是由Netscape創造的,該公司1996年11月將JavaSc...

    tommego 評論0 收藏0
  • JavaScript從初級往高級走系列————ES6

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

    孫淑建 評論0 收藏0
  • ES2015入門系列2-let和const

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

    godiscoder 評論0 收藏0

發表評論

0條評論

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