欧美亚洲中文,在线国自产视频,欧洲一区在线观看视频,亚洲综合中文字幕在线观看

      1. <dfn id="rfwes"></dfn>
          <object id="rfwes"></object>
        1. 站長(zhǎng)資訊網(wǎng)
          最全最豐富的資訊網(wǎng)站

          es6有哪些語(yǔ)法糖

          es6語(yǔ)法糖有:1、對(duì)象字面量,是指以“{}”形式直接表示的對(duì)象;2、箭頭函數(shù),一種寫匿名函數(shù)的新方法;3、解構(gòu)賦值,允許按照一定的模式,從數(shù)組或?qū)ο笾刑崛≈?,給變量進(jìn)行賦值;4、剩余參數(shù)和拓展符;5、模板字符串;6、let和const聲明語(yǔ)句。

          es6有哪些語(yǔ)法糖

          本教程操作環(huán)境:windows7系統(tǒng)、ECMAScript 6版、Dell G3電腦。

          語(yǔ)法糖:是指編程語(yǔ)言中可以更容易的表達(dá)一個(gè)操作的語(yǔ)法,它可以使程序員更加容易去使用這門語(yǔ)言,操作可以變得更加清晰、方便,或者更加符合程序員的編程習(xí)慣。

          ES6為一些已有的功能提供了非破壞性更新,這類更新中的大部分我們可以理解為語(yǔ)法糖,稱之為語(yǔ)法糖,意味著,這類新語(yǔ)法能做的事情其實(shí)用ES5也可以做,只是會(huì)稍微復(fù)雜一些。本章我們將著重討論這些語(yǔ)法糖,看完之后,可能你會(huì)對(duì)一些你很熟悉的ES6新語(yǔ)法有不一樣的理解。

          對(duì)象字面量

          對(duì)象字面量是指以{}形式直接表示的對(duì)象,比如下面這樣:

          var book = {   title: 'Modular ES6',   author: 'Nicolas',   publisher: 'O′Reilly' }
          登錄后復(fù)制

          ES6 為對(duì)象字面量的語(yǔ)法帶來(lái)了一些改進(jìn):包括屬性/方法的簡(jiǎn)潔表示,可計(jì)算的屬性名等等,我們逐一來(lái)看:

          屬性的簡(jiǎn)潔表示法

          你有沒(méi)有遇到過(guò)這種場(chǎng)景,一個(gè)我們聲明的對(duì)象中包含若干屬性,其屬性值由變量表示,且變量名和屬性名一樣的。比如下面這樣,我們想把一個(gè)名為 listeners 的數(shù)組賦值給events對(duì)象中的listeners屬性,用ES5我們會(huì)這樣做:

          var listeners = [] function listen() {} var events = {   listeners: listeners,   listen: listen }
          登錄后復(fù)制

          ES6則允許我們簡(jiǎn)寫成下面這種形式:

          var listeners = [] function listen() {} var events = { listeners, listen }
          登錄后復(fù)制

          怎么樣,是不是感覺(jué)簡(jiǎn)潔了許多,使用對(duì)象字面量的簡(jiǎn)潔寫法讓我們?cè)诓挥绊懻Z(yǔ)義的情況下減少了重復(fù)代碼。

          這是ES6帶來(lái)的好處之一,它提供了眾多更簡(jiǎn)潔,語(yǔ)義更清晰的語(yǔ)法,讓我們的代碼的可讀性,可維護(hù)性大大提升。

          可計(jì)算的屬性名

          對(duì)象字面量的另一個(gè)重要更新是允許你使用可計(jì)算的屬性名,在ES5中我們也可以給對(duì)象添加屬性名為變量的屬性,一般說(shuō)來(lái),我們要按下面方法這樣做,首先聲明一個(gè)名為expertise的變量,然后通過(guò)person[expertise]這種形式把變量添加為對(duì)象person的屬性:

          var expertise = 'journalism' var person = {   name: 'Sharon',   age: 27 } person[expertise] = {   years: 5,   interests: ['international', 'politics', 'internet'] }
          登錄后復(fù)制

          ES6 中,對(duì)象字面量可以使用計(jì)算屬性名了,把任何表達(dá)式放在中括號(hào)中,表達(dá)式的運(yùn)算結(jié)果將會(huì)是對(duì)應(yīng)的屬性名,上面的代碼,用ES6可以這樣寫:

          var expertise = 'journalism' var person = {   name: 'Sharon',   age: 27,   [expertise]: {     years: 5,     interests: ['international', 'politics', 'internet']   } }
          登錄后復(fù)制

          不過(guò)需要注意的是,簡(jiǎn)寫屬性和計(jì)算的屬性名不可同時(shí)使用。這是因?yàn)?,?jiǎn)寫屬性是一種在編譯階段的就會(huì)生效的語(yǔ)法糖,而計(jì)算的屬性名則在運(yùn)行時(shí)才生效。如果你把二者混用,代碼會(huì)報(bào)錯(cuò)。而且二者混用往往還會(huì)降低代碼的可讀性,所以JavaScript在語(yǔ)言層面上限制二者不能混用也是個(gè)好事。

          var expertise = 'journalism' var journalism = {   years: 5,   interests: ['international', 'politics', 'internet'] } var person = {   name: 'Sharon',   age: 27,   [expertise] // 這里會(huì)報(bào)語(yǔ)法錯(cuò)誤 }
          登錄后復(fù)制

          遇到以下情景時(shí),可計(jì)算的屬性名會(huì)讓我們的代碼更簡(jiǎn)潔:

          • 某個(gè)新對(duì)象的屬性引自另一個(gè)對(duì)象:

          var grocery = {   id: 'bananas',   name: 'Bananas',   units: 6,   price: 10,   currency: 'USD' } var groceries = {   [grocery.id]: grocery }
          登錄后復(fù)制

          • 需構(gòu)建的對(duì)象的屬性名來(lái)自函數(shù)參數(shù)。如果使用ES5來(lái)處理這種問(wèn)題,我們需要先聲明一個(gè)對(duì)象字面量,再動(dòng)態(tài)的添加屬性,再返回這個(gè)對(duì)象。下面的例子中,我們創(chuàng)建了一個(gè)響應(yīng)Ajax請(qǐng)求的函數(shù),這個(gè)函數(shù)的作用在于,請(qǐng)求失敗時(shí),返回的對(duì)象擁有一個(gè)名為error屬性及對(duì)應(yīng)的描述,請(qǐng)求成功時(shí),該對(duì)象擁有一個(gè)名為success屬性及對(duì)應(yīng)的描述。

          // ES5 寫法 function getEnvelope(type, description) {   var envelope = {     data: {}   }   envelope[type] = description   return envelope }
          登錄后復(fù)制

          使用ES6提供的利用計(jì)算屬性名,更簡(jiǎn)潔的實(shí)現(xiàn)如下:

          // ES6 寫法 function getEnvelope(type, description) {   return {     data: {},     [type]: description   } }
          登錄后復(fù)制

          對(duì)象字面量的屬性可以簡(jiǎn)寫,方法其實(shí)也是可以的。

          方法定義

          我們先看看傳統(tǒng)上如何定義對(duì)象方法,下述代碼中,我們構(gòu)建了一個(gè)事件發(fā)生器,其中的on方法用以注冊(cè)事件,emit方法用以執(zhí)行事件:

          var emitter = {   events: {},   on: function (type, fn) {     if (this.events[type] === undefined) {       this.events[type] = []     }     this.events[type].push(fn)   },   emit: function (type, event) {     if (this.events[type] === undefined) {       return     }     this.events[type].forEach(function (fn) {       fn(event)     })   } }
          登錄后復(fù)制

          ES6 的對(duì)象字面量方法簡(jiǎn)寫允許我們省略對(duì)象方法的function關(guān)鍵字及之后的冒號(hào),改寫后的代碼如下:

          var emitter = {   events: {},   on(type, fn) {     if (this.events[type] === undefined) {       this.events[type] = []     }     this.events[type].push(fn)   },   emit(type, event) {     if (this.events[type] === undefined) {       return     }     this.events[type].forEach(function (fn) {       fn(event)     })   } }
          登錄后復(fù)制

          ES6中的箭頭函數(shù)可謂大名鼎鼎了,它有一些特別的優(yōu)點(diǎn)(關(guān)于this),可能你和我一樣,使用箭頭函數(shù)很久了,不過(guò)有些細(xì)節(jié)我之前卻一直不了解,比如箭頭函數(shù)的幾種簡(jiǎn)寫形式及使用注意事項(xiàng)。

          箭頭函數(shù)


          JS中聲明的普通函數(shù),一般有函數(shù)名,一系列參數(shù)和函數(shù)體,如下:

          function name(parameters) {   // function body }
          登錄后復(fù)制

          普通匿名函數(shù)則沒(méi)有函數(shù)名,匿名函數(shù)通常會(huì)被賦值給一個(gè)變量/屬性,有時(shí)候還會(huì)被直接調(diào)用:

          var example = function (parameters) {   // function body }
          登錄后復(fù)制

          ES6 為我們提供了一種寫匿名函數(shù)的新方法,即箭頭函數(shù)。箭頭函數(shù)不需要使用function關(guān)鍵字,其參數(shù)和函數(shù)體之間以=>相連接:

          var example = (parameters) => {   // function body }
          登錄后復(fù)制

          登錄后復(fù)制

          盡管箭頭函數(shù)看起來(lái)類似于傳統(tǒng)的匿名函數(shù),他們卻具有根本性的不同:

          • 箭頭函數(shù)不能被直接命名,不過(guò)允許它們賦值給一個(gè)變量;

          • 箭頭函數(shù)不能用做構(gòu)造函數(shù),你不能對(duì)箭頭函數(shù)使用new關(guān)鍵字;

          • 箭頭函數(shù)也沒(méi)有prototype屬性;

          • 箭頭函數(shù)綁定了詞法作用域,不會(huì)修改this的指向。

          最后一點(diǎn)是箭頭函數(shù)最大的特點(diǎn),我們來(lái)仔細(xì)看看。

          詞法作用域

          我們?cè)诩^函數(shù)的函數(shù)體內(nèi)使用的this,arguments,super等都指向包含箭頭函數(shù)的上下文,箭頭函數(shù)本身不產(chǎn)生新的上下文。下述代碼中,我們創(chuàng)建了一個(gè)名為timer的對(duì)象,它的屬性seconds用以計(jì)時(shí),方法start用以開始計(jì)時(shí),若我們?cè)谌舾擅牒笳{(diào)用start方法,將打印出當(dāng)前的seconds值。

          // ES5 var timer = {   seconds: 0,   start() {     setInterval(function(){       this.seconds++     }, 1000)   } }  timer.start() setTimeout(function () {   console.log(timer.seconds) }, 3500)  > 0
          登錄后復(fù)制

          // ES6 var timer = {   seconds: 0,   start() {     setInterval(() => {       this.seconds++     }, 1000)   } }  timer.start() setTimeout(function () {   console.log(timer.seconds) }, 3500) // <- 3
          登錄后復(fù)制

          第一段代碼中start方法使用的是常規(guī)的匿名函數(shù)定義,在調(diào)用時(shí)this將指向了window,console出的結(jié)果為undefined,想要讓代碼正常工作,我們需要在start方法開頭處插入var self = this,然后替換匿名函數(shù)函數(shù)體中的thisself,第二段代碼中,我們使用了箭頭函數(shù),就不會(huì)發(fā)生這種情況了。

          還需要說(shuō)明的是,箭頭函數(shù)的作用域也不能通過(guò).call,.apply,.bind等語(yǔ)法來(lái)改變,這使得箭頭函數(shù)的上下文將永久不變。

          我們?cè)賮?lái)看另外一個(gè)箭頭函數(shù)與普通匿名函數(shù)的不同之處,你猜猜,下面的代碼最終打印出的結(jié)果會(huì)是什么:

          function puzzle() {   return function () {     console.log(arguments)   } } puzzle('a', 'b', 'c')(1, 2, 3)
          登錄后復(fù)制

          答案是1,2,3,原因是對(duì)常規(guī)匿名函數(shù)而言,arguments指向匿名函數(shù)本身。

          作為對(duì)比,我們看看下面這個(gè)例子,再猜猜,打印結(jié)果會(huì)是什么?

          function puzzle() {   return ()=>{     console.log(arguments)   } } puzzle('a', 'b', 'c')(1, 2, 3)
          登錄后復(fù)制

          答案是a,b,c,箭頭函數(shù)的特殊性決定其本身沒(méi)有arguments對(duì)象,這里的arguments其實(shí)是其父函數(shù)puzzle的。

          前面我們提到過(guò),箭頭函數(shù)還可以簡(jiǎn)寫,接下來(lái)我們一起看看。

          簡(jiǎn)寫的箭頭函數(shù)

          完整的箭頭函數(shù)是這樣的:

          var example = (parameters) => {   // function body }
          登錄后復(fù)制

          登錄后復(fù)制

          簡(jiǎn)寫1:

          當(dāng)只有一個(gè)參數(shù)時(shí),我們可以省略箭頭函數(shù)參數(shù)兩側(cè)的括號(hào):

          var double = value => {   return value * 2 }
          登錄后復(fù)制

          簡(jiǎn)寫2:

          對(duì)只有單行表達(dá)式且,該表達(dá)式的值為返回值的箭頭函數(shù)來(lái)說(shuō),表征函數(shù)體的{},可以省略,return 關(guān)鍵字可以省略,會(huì)靜默返回該單一表達(dá)式的值。

          var double = (value) => value * 2
          登錄后復(fù)制

          簡(jiǎn)寫3:

          上述兩種形式可以合并使用,而得到更加簡(jiǎn)潔的形式

          var double = value => value * 2
          登錄后復(fù)制

          現(xiàn)在,你肯定學(xué)會(huì)了箭頭函數(shù)的基本使用方法,接下來(lái)我們?cè)倏磶讉€(gè)使用示例。

          簡(jiǎn)寫箭頭函數(shù)帶來(lái)的一些問(wèn)題

          當(dāng)你的簡(jiǎn)寫箭頭函數(shù)返回值為一個(gè)對(duì)象時(shí),你需要用小括號(hào)括起你想返回的對(duì)象。否則,瀏覽器會(huì)把對(duì)象的{}解析為箭頭函數(shù)函數(shù)體的開始和結(jié)束標(biāo)記。

          // 正確的使用形式 var objectFactory = () => ({ modular: 'es6' })
          登錄后復(fù)制

          下面的代碼會(huì)報(bào)錯(cuò),箭頭函數(shù)會(huì)把本想返回的對(duì)象的花括號(hào)解析為函數(shù)體,number被解析為label,value解釋為沒(méi)有做任何事情表達(dá)式,我們又沒(méi)有顯式使用return,返回值默認(rèn)是undefined。

          [1, 2, 3].map(value => { number: value }) // <- [undefined, undefined, undefined]
          登錄后復(fù)制

          當(dāng)我們返回的對(duì)象字面量不止一個(gè)屬性時(shí),瀏覽器編譯器不能正確解析第二個(gè)屬性,這時(shí)會(huì)拋出語(yǔ)法錯(cuò)誤。

          [1, 2, 3].map(value => { number: value, verified: true }) // <- SyntaxError
          登錄后復(fù)制

          解決方案是把返回的對(duì)象字面量包裹在小括號(hào)中,以助于瀏覽器正確解析:

          [1, 2, 3].map(value => ({ number: value, verified: true })) /* <- [   { number: 1, verified: true },   { number: 2, verified: true },   { number: 3, verified: true }] */
          登錄后復(fù)制

          該何時(shí)使用箭頭函數(shù)

          其實(shí)我們并不應(yīng)該盲目的在一切地方使用ES6,ES6也不是一定比ES5要好,是否使用主要看其能否改善代碼的可讀性和可維護(hù)性。

          箭頭函數(shù)也并非適用于所有的情況,比如說(shuō),對(duì)于一個(gè)行數(shù)很多的復(fù)雜函數(shù),使用=>代替function關(guān)鍵字帶來(lái)的簡(jiǎn)潔性并不明顯。不過(guò)不得不說(shuō),對(duì)于簡(jiǎn)單函數(shù),箭頭函數(shù)確實(shí)能讓我們的代碼更簡(jiǎn)潔。

          給函數(shù)以合理的命名,有助于增強(qiáng)程序的可讀性。箭頭函數(shù)并不能直接命名,但是卻可以通過(guò)賦值給變量的形式實(shí)現(xiàn)間接命名,如下代碼中,我們把箭頭函數(shù)賦值給變量 throwError,當(dāng)函數(shù)被調(diào)用時(shí),會(huì)拋出錯(cuò)誤,我們可以追溯到是箭頭函數(shù)throwError報(bào)的錯(cuò)。

          var throwError = message => {   throw new Error(message) } throwError('this is a warning') <- Uncaught Error: this is a warning   at throwError
          登錄后復(fù)制

          如果你想完全控制你的函數(shù)中的this,使用箭頭函數(shù)是簡(jiǎn)潔高效的,采用函數(shù)式編程尤其如此。

          [1, 2, 3, 4]   .map(value => value * 2)   .filter(value => value > 2)   .forEach(value => console.log(value))// <- 4// <- 6// <- 8
          登錄后復(fù)制

          解構(gòu)賦值

          ES6提供的最靈活和富于表現(xiàn)性的新特性莫過(guò)于解構(gòu)了。一旦你熟悉了,它用起來(lái)也很簡(jiǎn)單,某種程度上解構(gòu)可以看做是變量賦值的語(yǔ)法糖,可應(yīng)用于對(duì)象,數(shù)組甚至函數(shù)的參數(shù)。

          對(duì)象解構(gòu)

          為了更好的描述對(duì)象解構(gòu)如何使用,我們先構(gòu)建下面這樣一個(gè)對(duì)象(漫威迷一定知道這個(gè)對(duì)象描述的是誰(shuí)):

          // 描述Bruce Wayne的對(duì)象 var character = {   name: 'Bruce',   pseudonym: 'Batman',   metadata: {     age: 34,     gender: 'male'   },   batarang: ['gas pellet', 'bat-mobile control', 'bat-cuffs'] }
          登錄后復(fù)制

          假如現(xiàn)有有一個(gè)名為 pseudonym 的變量,我們想讓其變量值指向character.pseudonym,使用ES5,你往往會(huì)按下面這樣做:

          var pseudonym = character.pseudonym
          登錄后復(fù)制

          ES6致力于讓我們的代碼更簡(jiǎn)潔,通過(guò)ES6我們可以用下面的代碼實(shí)現(xiàn)一樣的功能:

          var { pseudonym } = character
          登錄后復(fù)制

          如同你可以使用var加逗號(hào)在一行中同時(shí)聲明多個(gè)變量,解構(gòu)的花括號(hào)內(nèi)使用逗號(hào)可以做一樣的事情。

          var { pseudonym, name } = character
          登錄后復(fù)制

          我們還可以混用解構(gòu)和常規(guī)的自定義變量,這也是解構(gòu)語(yǔ)法靈活性的表現(xiàn)之一。

          var { pseudonym } = character, two = 2
          登錄后復(fù)制

          解構(gòu)還允許我們使用別名,比如我們想把character.pseudonym賦值給變量 alias,可以按下面的語(yǔ)句這樣做,只需要在pseudonym后面加上:即可:

          var { pseudonym: alias } = character console.log(alias) // <- 'Batman'
          登錄后復(fù)制

          解構(gòu)還有另外一個(gè)強(qiáng)大的功能,解構(gòu)值還可以是對(duì)象:

          var { metadata: { gender } } = character
          登錄后復(fù)制

          當(dāng)然,對(duì)于多層解構(gòu),我們同樣可以賦予別名,這樣我們可以通過(guò)非常簡(jiǎn)潔的方法修改子屬性的名稱:

          var { metadata: { gender: characterGender } } = character
          登錄后復(fù)制

          在ES5 中,當(dāng)你調(diào)用一個(gè)未曾聲明的值時(shí),你會(huì)得到undefined:

          console.log(character.boots) // <- undefined console.log(character['boots']) // <- undefined
          登錄后復(fù)制

          使用解構(gòu),情況也是類似的,如果你在左邊聲明了一個(gè)右邊對(duì)象中不存在的屬性,你也會(huì)得到undefined.

          var { boots } = character console.log(boots) // <- undefined
          登錄后復(fù)制

          對(duì)于多層解構(gòu),如下述代碼中,boots并不存在于character中,這時(shí)程序會(huì)拋出異常,這就好比你你調(diào)用undefined或者null的屬性時(shí)會(huì)出現(xiàn)異常。

          var { boots: { size } } = character // <- Exception var { missing } = null // <- Exception
          登錄后復(fù)制

          解構(gòu)其實(shí)就是一種語(yǔ)法糖,看以下代碼,你肯定就能很快理解為什么會(huì)拋出異常了。

          var nothing = null var missing = nothing.missing // <- Exception
          登錄后復(fù)制

          解構(gòu)也可以添加默認(rèn)值,如果右側(cè)不存在對(duì)應(yīng)的值,默認(rèn)值就會(huì)生效,添加的默認(rèn)值可以是數(shù)值,字符串,函數(shù),對(duì)象,也可以是某一個(gè)已經(jīng)存在的變量:

          var { boots = { size: 10 } } = character console.log(boots) // <- { size: 10 }
          登錄后復(fù)制

          對(duì)于多層的解構(gòu),同樣可以使用默認(rèn)值

          var { metadata: { enemy = 'Satan' } } = character console.log(enemy) // <- 'Satan'
          登錄后復(fù)制

          默認(rèn)值和別名也可以一起使用,不過(guò)需要注意的是別名要放在前面,默認(rèn)值添加給別名:

          var { boots: footwear = { size: 10 } } = character
          登錄后復(fù)制

          對(duì)象解構(gòu)同樣支持計(jì)算屬性名,但是這時(shí)候你必須要添加別名,這是因?yàn)橛?jì)算屬性名允許任何類似的表達(dá)式,不添加別名,瀏覽器解析時(shí)會(huì)有問(wèn)題,使用如下:

          var { ['boo' + 'ts']: characterBoots } = character console.log(characterBoots) // <- true
          登錄后復(fù)制

          還是那句話,我們也不是任何情況下都應(yīng)該使用解構(gòu),語(yǔ)句characterBoots = character[type]看起來(lái)比{ [type]: characterBoots } = character語(yǔ)義更清晰,但是當(dāng)你需要提取對(duì)象中的子對(duì)象時(shí),解構(gòu)就很簡(jiǎn)潔方便了。

          我們?cè)倏纯丛跀?shù)組中該如何使用解構(gòu)。

          數(shù)組解構(gòu)

          數(shù)組解構(gòu)的語(yǔ)法和對(duì)象解構(gòu)是類似的。區(qū)別在于,數(shù)組解構(gòu)我們使用中括號(hào)而非花括號(hào),下面的代碼中,通過(guò)結(jié)構(gòu),我們?cè)跀?shù)組coordinates中提出了變量 x,y 。 你不需要使用x = coordinates[0]這樣的語(yǔ)法了,數(shù)組解構(gòu)不使用索引值,但卻讓你的代碼更加清晰。

          var coordinates = [12, -7] var [x, y] = coordinates console.log(x) // <- 12
          登錄后復(fù)制

          數(shù)組解構(gòu)也允許你跳過(guò)你不想用到的值,在對(duì)應(yīng)地方留白即可:

          var names = ['James', 'L.', 'Howlett'] var [ firstName, , lastName ] = names console.log(lastName) // <- 'Howlett'
          登錄后復(fù)制

          和對(duì)象解構(gòu)一樣,數(shù)組解構(gòu)也允許你添加默認(rèn)值:

          var names = ['James', 'L.'] var [ firstName = 'John', , lastName = 'Doe' ] = names console.log(lastName) // <- 'Doe'
          登錄后復(fù)制

          在ES5中,你需要借助第三個(gè)變量,才能完成兩個(gè)變量值的交換,如下:

          var left = 5, right = 7; var aux = left left = right right = aux
          登錄后復(fù)制

          使用解構(gòu),一切就簡(jiǎn)單多了:

          var left = 5, right = 7; [left, right] = [right, left]
          登錄后復(fù)制

          我們?cè)倏纯春瘮?shù)解構(gòu)。

          函數(shù)默認(rèn)參數(shù)

          在ES6中,我們可以給函數(shù)的參數(shù)添加默認(rèn)值了,下例中我們就給參數(shù) exponent 分配了一個(gè)默認(rèn)值:

          function powerOf(base, exponent = 2) {   return Math.pow(base, exponent) }
          登錄后復(fù)制

          箭頭函數(shù)同樣支持使用默認(rèn)值,需要注意的是,就算只有一個(gè)參數(shù),如果要給參數(shù)添加默認(rèn)值,參數(shù)部分一定要用小括號(hào)括起來(lái)。

          var double = (input = 0) => input * 2
          登錄后復(fù)制

          我們可以給任何位置的任何參數(shù)添加默認(rèn)值。

          function sumOf(a = 1, b = 2, c = 3) {   return a + b + c } console.log(sumOf(undefined, undefined, 4)) // <- 1 + 2 + 4 = 7
          登錄后復(fù)制

          在JS中,給一個(gè)函數(shù)提供一個(gè)包含若干屬性的對(duì)象字面量做為參數(shù)的情況并不常見(jiàn),不過(guò)你依舊可以按下面方法這樣做:

          var defaultOptions = { brand: 'Volkswagen', make: 1999 } function carFactory(options = defaultOptions) {   console.log(options.brand)   console.log(options.make) } carFactory() // <- 'Volkswagen' // <- 1999
          登錄后復(fù)制

          不過(guò)這樣做存在一定的問(wèn)題,當(dāng)你調(diào)用該函數(shù)時(shí),如果傳入的參數(shù)對(duì)象只包含一個(gè)屬性,另一個(gè)屬性的默認(rèn)值會(huì)自動(dòng)失效:

          carFactory({ make: 2000 }) // <- undefined // <- 2000
          登錄后復(fù)制

          函數(shù)參數(shù)解構(gòu)就可以解決這個(gè)問(wèn)題。

          函數(shù)參數(shù)解構(gòu)

          通過(guò)函數(shù)參數(shù)解構(gòu),可以解決上面的問(wèn)題,這里我們?yōu)槊恳粋€(gè)屬性都提供了默認(rèn)值,單獨(dú)改變其中一個(gè)并不會(huì)影響其它的值:

          function carFactory({ brand = 'Volkswagen', make = 1999 }) {   console.log(brand)   console.log(make) } carFactory({ make: 2000 }) // <- 'Volkswagen' // <- 2000
          登錄后復(fù)制

          不過(guò)這種情況下,函數(shù)調(diào)用時(shí),如果參數(shù)為空即carFactory()函數(shù)將拋出異常。這種問(wèn)題可以通過(guò)下面的方法來(lái)修復(fù),下述代碼中我們添加了一個(gè)空對(duì)象作為options的默認(rèn)值,這樣當(dāng)函數(shù)被調(diào)用時(shí),如果參數(shù)為空,會(huì)自動(dòng)以{}作為參數(shù)。

          function carFactory({   brand = 'Volkswagen',   make = 1999 } = {}) {   console.log(brand)   console.log(make) } carFactory() // <- 'Volkswagen' // <- 1999
          登錄后復(fù)制

          除此之外,使用函數(shù)參數(shù)解構(gòu),還可以讓你的函數(shù)自行匹配對(duì)應(yīng)的參數(shù),看接下來(lái)的例子,你就能明白這一點(diǎn)了,我們定義一個(gè)名為car的對(duì)象,這個(gè)對(duì)象擁有很多屬性:owner,brand,make,model,preferences等等。

          var car = {   owner: {     id: 'e2c3503a4181968c',     name: 'Donald Draper'   },   brand: 'Peugeot',   make: 2015,   model: '208',   preferences: {     airbags: true,     airconditioning: false,     color: 'red'   } }
          登錄后復(fù)制

          解構(gòu)能讓我們的函數(shù)方便的只使用里面的部分?jǐn)?shù)據(jù),下面代碼中的函數(shù)getCarProductModel說(shuō)明了具體該如何使用:

          var getCarProductModel = ({ brand, make, model }) => ({   sku: brand + ':' + make + ':' + model,   brand,   make,   model }) getCarProductModel(car)
          登錄后復(fù)制

          解構(gòu)使用示例

          當(dāng)一個(gè)函數(shù)的返回值為對(duì)象或者數(shù)組時(shí),使用解構(gòu),我們可以非常簡(jiǎn)潔的獲取返回對(duì)象中某個(gè)屬性的值(返回?cái)?shù)組中某一項(xiàng)的值)。比如說(shuō),函數(shù)getCoordinates()返回了一系列的值,但是我們只想用其中的x,y,我們可以這樣寫,解構(gòu)幫助我們避免了很多中間變量的使用,也使得我們代碼的可讀性更高。

          function getCoordinates() {   return { x: 10, y: 22, z: -1, type: '3d' } } var { x, y } = getCoordinates()
          登錄后復(fù)制

          通過(guò)使用默認(rèn)值,可以減少重復(fù),比如你想寫一個(gè)random函數(shù),這個(gè)函數(shù)將返回一個(gè)位于minmax之間的值。我們可以分辨設(shè)置min默認(rèn)值為1,max默認(rèn)值為10,在需要的時(shí)候還可以單獨(dú)改變其中的某一個(gè)值:

          function random({ min = 1, max = 10 } = {}) {   return Math.floor(Math.random() * (max - min)) + min } console.log(random())// <- 7 console.log(random({ max: 24 }))// <- 18
          登錄后復(fù)制

          解構(gòu)還可以配合正則表達(dá)式使用??聪旅孢@個(gè)例子:

          function splitDate(date) {   var rdate = /(d+).(d+).(d+)/     return rdate.exec(date) } var [ , year, month, day] = splitDate('2015-11-06')
          登錄后復(fù)制

          不過(guò)當(dāng).exec不比配時(shí)會(huì)返回null,因此我們需要修改上述代碼如下:

          var matches = splitDate('2015-11-06')if (matches === null) {   return } var [, year, month, day] = matches
          登錄后復(fù)制

          下面我們繼續(xù)來(lái)講講spreadrest操作符。

          剩余參數(shù)和拓展符

          ES6之前,對(duì)于不確定數(shù)量參數(shù)的函數(shù)。你需要使用偽數(shù)組arguments,它擁有length屬性,卻又不具備很多一般數(shù)組有的特性。需要通過(guò)Array#slice.call轉(zhuǎn)換arguments對(duì)象真數(shù)組后才能進(jìn)行下一步的操作:

          function join() {   var list = Array.prototype.slice.call(arguments)     return list.join(', ') } join('first', 'second', 'third')// <- 'first, second, third'
          登錄后復(fù)制

          對(duì)于這種情況,ES6提供了一種更好的解決方案:rest。

          剩余參數(shù)rest

          使用rest, 你只需要在任意JavaScript函數(shù)的最后一個(gè)參數(shù)前添加三個(gè)點(diǎn)...即可。當(dāng)rest參數(shù)是函數(shù)的唯一參數(shù)時(shí),它就代表了傳遞給這個(gè)函數(shù)的所有參數(shù)。它起到和前面說(shuō)的.slice一樣的作用,把參數(shù)轉(zhuǎn)換為了數(shù)組,不需要你再對(duì)arguments進(jìn)行額外的轉(zhuǎn)換了。

          function join(...list) {   return list.join(', ') } join('first', 'second', 'third')// <- 'first, second, third'
          登錄后復(fù)制

          rest參數(shù)之前的命名參數(shù)不會(huì)被包含在rest中,

          function join(separator, ...list) {   return list.join(separator) } join('; ', 'first', 'second', 'third')// <- 'first; second; third'
          登錄后復(fù)制

          在箭頭函數(shù)中使用rest參數(shù)時(shí),即使只有這一個(gè)參數(shù),也需要使用圓括號(hào)把它圍起來(lái),不然就會(huì)報(bào)錯(cuò)SyntaxError,使用示例如下:

          var sumAll = (...numbers) => numbers.reduce(   (total, next) => total + next ) console.log(sumAll(1, 2, 5))// <- 8
          登錄后復(fù)制

          上述代碼的ES5實(shí)現(xiàn)如下:

          // ES5的寫法 function sumAll() {   var numbers = Array.prototype.slice.call(arguments)     return numbers.reduce(function (total, next) {       return total + next   }) } console.log(sumAll(1, 2, 5))// <- 8
          登錄后復(fù)制

          拓展運(yùn)算符

          拓展運(yùn)算符可以把任意可枚舉對(duì)象轉(zhuǎn)換為數(shù)組,使用拓展運(yùn)算符可以高效處理目標(biāo)對(duì)象,在拓展目前前添加...就可以使用拓展運(yùn)算符了。下例中...arguments就把函數(shù)的參數(shù)轉(zhuǎn)換為了數(shù)組字面量。

          function cast() {   return [...arguments] } cast('a', 'b', 'c')// <- ['a', 'b', 'c']
          登錄后復(fù)制

          使用拓展運(yùn)算符,我們也可以把字符串轉(zhuǎn)換為由每一個(gè)字母組成的數(shù)組:

          [...'show me']// <- ['s', 'h', 'o', 'w', ' ', 'm', 'e']
          登錄后復(fù)制

          使用拓展運(yùn)算符,還可以拼合數(shù)組:

          function cast() {   return ['left', ...arguments, 'right'] } cast('a', 'b', 'c')// <- ['left', 'a', 'b', 'c', 'right']
          登錄后復(fù)制

          var all = [1, ...[2, 3], 4, ...[5], 6, 7] console.log(all)// <- [1, 2, 3, 4, 5, 6, 7]
          登錄后復(fù)制

          這里我還想再?gòu)?qiáng)調(diào)一下,拓展運(yùn)算符不僅僅適用于數(shù)組和arguments對(duì)象,對(duì)任意可迭代的對(duì)象都可以使用。迭代也是ES6新提出的一個(gè)概念,在[ Iteration and Flow Control]()這一章,我們將詳細(xì)敘述迭代。

          Shifting和Spreading

          當(dāng)你想要抽出一個(gè)數(shù)組的前一個(gè)或者兩個(gè)元素時(shí),常用的解決方案是使用.shift.盡管是函數(shù)式的,下述代碼在第一次看到的時(shí)候卻不好理解,我們使用了兩次.slicelist中抽離出兩個(gè)不同的元素。

          var list = ['a', 'b', 'c', 'd', 'e'] var first = list.shift() var second = list.shift() console.log(first)// <- 'a'
          登錄后復(fù)制

          在ES6中,結(jié)合使用拓展和解構(gòu),可以讓代碼的可讀性更好:

          var [first, second, ...other] = ['a', 'b', 'c', 'd', 'e'] console.log(other)// <- ['c', 'd', 'e']
          登錄后復(fù)制

          除了對(duì)數(shù)組進(jìn)行拓展,你同樣可以對(duì)函數(shù)參數(shù)使用拓展,下例展示了如何添加任意數(shù)量的參數(shù)到multiply函數(shù)中。

          function multiply(left, right) {   return left * right } var result = multiply(...[2, 3]) console.log(result)// <- 6
          登錄后復(fù)制

          向在數(shù)組中一樣,函數(shù)參數(shù)中的拓展運(yùn)算符同樣可以結(jié)合常規(guī)參數(shù)一起使用。下例中,print函數(shù)結(jié)合使用了rest,普通參數(shù),和拓展運(yùn)算符:

          function print(...list) {   console.log(list) } print(1, ...[2, 3], 4, ...[5])// <- [1, 2, 3, 4, 5]
          登錄后復(fù)制

          下表總結(jié)了,拓展運(yùn)算符的常見(jiàn)使用方法:

          使用示例 ES5 ES6
          Concatenation [1, 2].concat(more) [1, 2, ...more]
          Push an array onto list list.push.apply(list, items) list.push(...items)
          Destructuring a = list[0], other = list.slice(1) <span class="Apple-tab-span" style="white-space: pre;"> </span>[a, ...other] = list
          new and apply new (Date.bind.apply(Date, [null,2015,31,8])) new Date(...[2015,31,8])

          模板字符串

          模板字符串是對(duì)常規(guī)JavaScript字符串的重大改進(jìn),不同于在普通字符串中使用單引號(hào)或者雙引號(hào),模板字符串的聲明需要使用反撇號(hào),如下所示:

          var text = `This is my first template literal`
          登錄后復(fù)制

          因?yàn)槭褂玫氖欠雌蔡?hào),你可以在模板字符串中隨意使用單雙引號(hào)了,使用時(shí)不再需要考慮轉(zhuǎn)義,如下:

          var text = `I'm "amazed" at these opportunities!`
          登錄后復(fù)制

          模板字符串具有很多強(qiáng)大的功能,可在其中插入JavaScript表達(dá)式就是其一。

          在字符串中插值

          通過(guò)模板字符串,你可以在模板中插入任何JavaScript表達(dá)式了。當(dāng)解析到表達(dá)式時(shí),表達(dá)式會(huì)被執(zhí)行,該處將渲染表達(dá)式的值,下例中,我們?cè)谧址胁迦肓俗兞?code>name:

          var name = 'Shannon' var text = `Hello, ${ name }!` console.log(text)// <- 'Hello, Shannon!'
          登錄后復(fù)制

          模板字符串是支持任何表達(dá)式的。使用模板字符串,代碼將更容易維護(hù),你無(wú)須再手動(dòng)連接字符串和JavaScript表達(dá)式了。

          看下面插入日期的例子,是不是又直觀又方便:

          `The time and date is ${ new Date().toLocaleString() }.`// <- 'the time and date is 8/26/2015, 3:15:20 PM'
          登錄后復(fù)制

          表達(dá)式中還可以包含數(shù)學(xué)運(yùn)算符:

          `The result of 2+3 equals ${ 2 + 3 }`// <- 'The result of 2+3 equals 5'
          登錄后復(fù)制

          鑒于模板字符串本身也是JavaScript表達(dá)式,我們?cè)谀0遄址羞€可以嵌套模板字符串;

          `This template literal ${ `is ${ 'nested' }` }!`// <- 'This template literal is nested!'
          登錄后復(fù)制

          模板字符串的另外一個(gè)優(yōu)點(diǎn)是支持多行字符串;

          多行文本模板

          在ES6之前,如果你想表現(xiàn)多行字符串,你需要使用轉(zhuǎn)義,數(shù)組拼合,甚至使用使用注釋符做復(fù)雜的hacks.如下所示:

          var escaped ='The first linen A second linen Then a third line'  var concatenated ='The first linen' ` 'A second linen' `'Then a third line'  var joined = ['The first line','A second line','Then a third line'].join('n')
          登錄后復(fù)制

          應(yīng)用ES6,這種處理就簡(jiǎn)單多了,模板字符串默認(rèn)支持多行:

          var multiline =`The first line A second line Then a third line`
          登錄后復(fù)制

          當(dāng)你需要返回的字符串基于html和數(shù)據(jù)生成,使用模板字符串是很簡(jiǎn)潔高效的,如下所示:

          var book = {   title: 'Modular ES6',     excerpt: 'Here goes some properly sanitized HTML',     tags: ['es6', 'template-literals', 'es6-in-depth'] } var html = `<article>   <header>     <h1>${ book.title }</h1>   </header>   <section>${ book.excerpt }</section>   <footer>     <ul>      ${         book.tags           .map(tag => `<li>${ tag }</li>`)           .join('n      ')       }     </ul>   </footer> </article>`
          登錄后復(fù)制

          上述代碼將得到下面這樣的結(jié)果??崭竦靡员A簦鄠€(gè)li也按我們的預(yù)期被合適的渲染:

          <article>   <header>       <h1>Modular ES6</h1>    </header>     <section>Here goes some properly sanitized HTML</section>       <footer>               <ul>                     <li>es6</li>                           <li>template-literals</li>                           <li>es6-in-depth</li>                    </ul>          </footer> </article>
          登錄后復(fù)制

          不過(guò)有時(shí)候我們并不希望空格被保留,下例中我們?cè)诤瘮?shù)中使用包含縮進(jìn)的模板字符串,我們希望結(jié)果沒(méi)有縮進(jìn),但是實(shí)際的結(jié)果卻有四格的縮進(jìn)。

          function getParagraph() {   return `     Dear Rod,      This is a template literal string that's indented     four spaces. However, you may have expected for it     to be not indented at all.      Nico   `}
          登錄后復(fù)制

          我們可以用下面這個(gè)功能函數(shù)對(duì)生成的字符串進(jìn)行處理已得到我們想要的結(jié)果:

          function unindent(text) {   return text     .split('n')     .map(line => line.slice(4))     .join('n')     .trim() }
          登錄后復(fù)制

          不過(guò),使用被稱為標(biāo)記模板的模板字符串新特性處理這種情況可能會(huì)更好。

          標(biāo)記模板

          默認(rèn)情況下,JavaScript會(huì)把解析為轉(zhuǎn)義符號(hào),對(duì)瀏覽器來(lái)說(shuō),以開頭的字符一般具有特殊的含義。比如說(shuō)n意味著新行,u00f1表示?等等。如果你不想瀏覽器執(zhí)行這種特殊解析,你也可以使用String.raw來(lái)標(biāo)記模板。下面的代碼就是這樣做的,這里我們使用了String.row來(lái)處理模板字符串,相應(yīng)的這里面的n沒(méi)有被解析為新行。

          var text = String.raw`"n" is taken literally. It'll be escaped instead of interpreted.` console.log(text) // "n" is taken literally. // It'll be escaped instead of interpreted.
          登錄后復(fù)制

          我們添加在模板字符串之前的String.raw前綴,這就是標(biāo)記模板,這樣的模板字符串在被渲染前被該標(biāo)記代表的函數(shù)預(yù)處理。

          一個(gè)典型的標(biāo)記模板字符串如下:

          tag`Hello, ${ name }. I am ${ emotion } to meet you!`
          登錄后復(fù)制

          實(shí)際上,上面標(biāo)記模板可以用以下函數(shù)形式表示:

          tag(   ['Hello, ', '. I am ', ' to meet you!'],  'Maurice',  'thrilled'  )
          登錄后復(fù)制

          我們還是用代碼來(lái)說(shuō)明這個(gè)概念,下述代碼中,我們先定義一個(gè)名為tag函數(shù):

          function tag(parts, ...values) {   return parts.reduce(       (all, part, index) => all + values[index - 1] + part   ) }
          登錄后復(fù)制

          然后我們調(diào)用使用使用標(biāo)記模板,不過(guò)此時(shí)的結(jié)果和不使用標(biāo)記模板是一樣的,這是因?yàn)槲覀兌x的tag函數(shù)實(shí)際上并未對(duì)字符串進(jìn)行額外的處理。

          var name = 'Maurice' var emotion = 'thrilled' var text = tag`Hello, ${ name }. I am ${ emotion } to meet you!` console.log(text)// <- 'Hello Maurice, I am thrilled to meet you!'
          登錄后復(fù)制

          我們看一個(gè)進(jìn)行額外處理的例子,比如轉(zhuǎn)換所有用戶輸入的值為大寫(假設(shè)用戶只會(huì)輸入英語(yǔ)),這里我們定義標(biāo)記函數(shù)upper來(lái)做這件事:

          function upper(parts, ...values) {   return parts.reduce((all, part, index) =>     all + values[index - 1].toUpperCase() + part   ) } var name = 'Maurice' var emotion = 'thrilled' upper`Hello, ${ name }. I am ${ emotion } to meet you!` // <- 'Hello MAURICE, I am THRILLED to meet you!'
          登錄后復(fù)制

          既然可以轉(zhuǎn)換輸入為大寫,那我們?cè)龠M(jìn)一步想想,如果提供合適的標(biāo)記模板函數(shù),使用標(biāo)記模板,我們還可以對(duì)模板中的表達(dá)式進(jìn)行各種過(guò)濾處理,比如有這么一個(gè)場(chǎng)景,假設(shè)表達(dá)式的值都來(lái)自用戶輸入,假設(shè)有一個(gè)名為sanitize的庫(kù)可用于去除用戶輸入中的html標(biāo)簽,那通過(guò)使用標(biāo)記模板,就可以有效的防止XSS攻擊了,使用方法如下。

          function sanitized(parts, ...values) {   return parts.reduce((all, part, index) =>     all + sanitize(values[index - 1]) + part   ) } var comment = 'Evil comment<iframe src="http://evil.corp">     </iframe>' var html = sanitized`<div>${ comment }</div>`console.log(html) // <- '<div>Evil comment</div>'
          登錄后復(fù)制

          ES6中的另外一個(gè)大的改變是提供了新的變量聲明方式:letconst聲明,下面我們一起來(lái)學(xué)習(xí)。

          let & const 聲明

          可能很早之前你就聽說(shuō)過(guò) let 了,它用起來(lái)像 var 但是,卻有不同的作用域規(guī)則。

          JavaScript的作用域有一套復(fù)雜的規(guī)則,變量提升的存在常常讓新手忐忑不安。變量提升,意味著無(wú)論你在那里聲明的變量,在瀏覽器解析時(shí),實(shí)際上都被提升到了當(dāng)前作用域的頂部被聲明。看下面的這個(gè)例子:

          function isItTwo(value) {   if (value === 2) {       var two = true   }  return two } isItTwo(2)// <- true isItTwo('two')// <- undefined
          登錄后復(fù)制

          盡管two是在代碼分支中被聲明,之后被外部分支引用,上述的JS代碼還是可以工作的。var 聲明的變量two實(shí)際是在isItTwo頂部被聲明的。由于聲明提升的存在,上述代碼其實(shí)和下面代碼的效果是一樣的

          function isItTwo(value) {   var two  if (value === 2) {     two = true   }  return two }
          登錄后復(fù)制

          帶來(lái)了靈活性的同事,變量提升也帶來(lái)了更大的迷惑性,還好ES6 為我們提供了塊作用域。

          塊作用域和let 聲明

          相比函數(shù)作用域,塊作用域允許我們通過(guò)if,for,while聲明創(chuàng)建新作用域,甚至任意創(chuàng)建{}塊也能創(chuàng)建新的作用域:

          {{{{{ var deep = 'This is available from outer scope.'; }}}}} console.log(deep) // <- 'This is available from outer scope.'
          登錄后復(fù)制

          由于這里使用的是var,考慮到變量提升的存在,我們?cè)谕獠恳琅f可以讀取到深層中的deep變量,這里并不會(huì)報(bào)錯(cuò)。不過(guò)在以下情況下,我們可能希望這里會(huì)報(bào)錯(cuò):

          • 訪問(wèn)內(nèi)部變量會(huì)打破我們代碼中的某種封裝原則;

          • 父塊中已有有一個(gè)一個(gè)同名變量,但是內(nèi)部也需要用同名變量;

          使用let就可以解決這個(gè)問(wèn)題,let 創(chuàng)建的變量在塊作用域內(nèi)有效,在ES6提出let以前,想要?jiǎng)?chuàng)建深層作用域的唯一辦法就是再新建一個(gè)函數(shù)。使用let,你只需添加另外一對(duì){}

          let topmost = {} {   let inner = {}   {     let innermost = {}   }   // attempts to access innermost here would throw } // attempts to access inner here would throw // attempts to access innermost here would throw
          登錄后復(fù)制

          for循環(huán)中使用let是一個(gè)很好的實(shí)踐,這樣定義的變量只會(huì)在當(dāng)前塊作用域內(nèi)生效。

          for (let i = 0; i < 2; i++) {   console.log(i)  // <- 0  // <- 1 } console.log(i)// <- i is not defined
          登錄后復(fù)制

          考慮到let聲明的變量在每一次循環(huán)的過(guò)程中都重復(fù)聲明,這在處理異步函數(shù)時(shí)就很有效,不會(huì)發(fā)生使用var時(shí)產(chǎn)生的詭異的結(jié)果,我們看一個(gè)具體的例子。

          我們先看看 var 聲明的變量是怎么工作的,下述代碼中 i變量 被綁定在 printNumber 函數(shù)作用域中,當(dāng)每個(gè)回調(diào)函數(shù)被調(diào)用時(shí),它的值會(huì)逐步升到10,但是當(dāng)每個(gè)回調(diào)函數(shù)運(yùn)行時(shí)(每100us),此時(shí)的i的值已經(jīng)是10了,因此每次打印的結(jié)果都是10.

          function printNumbers() {   for (var i = 0; i < 10; i++) {       setTimeout(function () {             console.log(i)     }, i * 100)   } } printNumbers()
          登錄后復(fù)制

          使用let,則會(huì)把i綁定到每一個(gè)塊作用域中。每一次循環(huán) i 的值還是在增加,但是每次其實(shí)都是創(chuàng)建了一個(gè)新的 i ,不同的 i 之間不會(huì)相互影響 ,因此打印出的就是預(yù)想的0到9了。

          function printNumbers() {   for (let i = 0; i < 10; i++) {       setTimeout(function () {             console.log(i)     }, i * 100)   } } printNumbers()
          登錄后復(fù)制

          為了細(xì)致的講述let的工作原理, 我們還需要弄懂一個(gè)名為 Temporal Dead Zone 的概念。

          Temporal Dead Zone

          簡(jiǎn)言之,如果你的代碼類似下面這樣,就會(huì)報(bào)錯(cuò)。即在某個(gè)作用域中,在let聲明之前調(diào)用了let聲明的變量,導(dǎo)致的問(wèn)題就是由于,Temporal Dead Zone(TDZ)的存在。

          {   console.log(name)   // <- ReferenceError: name is not defined   let name = 'Stephen Hawking' }
          登錄后復(fù)制

          如果定義的是一個(gè)函數(shù),函數(shù)中引用了name變量則是可以的,但是這個(gè)函數(shù)并未在聲明前執(zhí)行則不會(huì)報(bào)錯(cuò)。如果let聲明之前就調(diào)用了該函數(shù),同樣會(huì)導(dǎo)致TDZ。

          // 不會(huì)報(bào)錯(cuò) function readName() {   return name } let name = 'Stephen Hawking' console.log(readName()) // <- 'Stephen Hawking'
          登錄后復(fù)制

          // 會(huì)報(bào)錯(cuò) function readName() {   return name } console.log(readName()) // ReferenceError: name is not defined let name = 'Stephen Hawking'
          登錄后復(fù)制

          即使像下面這樣let定義的變量沒(méi)有被賦值,下面的代碼也會(huì)報(bào)錯(cuò),原因依舊是它試圖在聲明前訪問(wèn)一個(gè)被let定義的變量

          function readName() {   return name } console.log(readName())// ReferenceError: name is not definedlet name
          登錄后復(fù)制

          下面的代碼則是可行的:

          function readName() {   return name } let nameconsole.log(readName())// <- undefined
          登錄后復(fù)制

          TDZ的存在使得程序更容易報(bào)錯(cuò),由于聲明提升和不好的編碼習(xí)慣常常會(huì)存在這樣的問(wèn)題。在ES6中則可以比較好的避免了這種問(wèn)題了,需要注意的是let聲明的變量同樣存在聲明提升。這意味著,變量會(huì)在我們進(jìn)入塊作用域時(shí)就會(huì)創(chuàng)建,TDZ也是在這時(shí)候創(chuàng)建的,它保證該變量不許被訪問(wèn),只有在代碼運(yùn)行到let聲明所在位置時(shí),這時(shí)候TDZ才會(huì)消失,訪問(wèn)限制才會(huì)取消,變量才可以被訪問(wèn)。

          Const 聲明

          const聲明也具有類似let的塊作用域,它同樣具有TDZ機(jī)制。實(shí)際上,TDZ機(jī)制是因?yàn)?code>const才被創(chuàng)建,隨后才被應(yīng)用到let聲明中。const需要TDZ的原因是為了防止由于變量提升,在程序解析到const語(yǔ)句之前,對(duì)const聲明的變量進(jìn)行了賦值操作,這樣是有問(wèn)題的。

          下面的代碼表明,const具有和let一致的塊作用域:

          const pi = 3.1415{   const pi = 6  console.log(pi)  // <- 6 } console.log(pi)// <- 3.1415
          登錄后復(fù)制

          下面我們說(shuō)說(shuō)constlet的主要區(qū)別,首先const聲明的變量在聲明時(shí)必須賦值,否則會(huì)報(bào)錯(cuò):

          const pi = 3.1415 const e  // SyntaxError, missing initializer
          登錄后復(fù)制

          除了必須初始化,被const聲明的變量不能再被賦予別的值。在嚴(yán)格模式下,試圖改變const聲明的變量會(huì)直接報(bào)錯(cuò),在非嚴(yán)格模式下,改變被靜默被忽略。

          const people = ['Tesla', 'Musk'] people = [] console.log(people)// <- ['Tesla', 'Musk']
          登錄后復(fù)制

          請(qǐng)注意,const聲明的變量并非意味著,其對(duì)應(yīng)的值是不可變的。真正不能變的是對(duì)該值的引用,下面我們具體說(shuō)明這一點(diǎn)。

          通過(guò)const聲明的變量值并非不可改變

          使用const只是意味著,變量將始終指向相同的對(duì)象或初始的值。這種引用是不可變的。但是值并非不可變。

          下面的例子說(shuō)明,雖然people的指向不可變,但是數(shù)組本身是可以被修改的。

          const people = ['Tesla', 'Musk'] people.push('Berners-Lee') console.log(people)// <- ['Tesla', 'Musk', 'Berners-Lee']
          登錄后復(fù)制

          const只是阻止變量引用另外一個(gè)值,下例中,盡管我們使用const聲明了people,然后把它賦值給了humans,我們還是可以改變humans的指向,因?yàn)?code>humans不是由const聲明的,其引用可隨意改變。people 是由 const 聲明的,則不可改變。

          const people = ['Tesla', 'Musk'] var humans = people humans = 'evil' console.log(humans)// <- 'evil'
          登錄后復(fù)制

          如果我們的目的是讓值不可修改,我們需要借助函數(shù)的幫助,比如使用Object.freeze

          const frozen = Object.freeze(   ['Ice', 'Icicle', 'Ice cube'] ) frozen.push('Water') // Uncaught TypeError: Can't add property 3 // object is not extensible
          登錄后復(fù)制

          下面我們?cè)敿?xì)討論一下constlet的優(yōu)點(diǎn)

          constlet的優(yōu)點(diǎn)

          新功能并不應(yīng)該因?yàn)槭切鹿δ芏皇褂茫珽S6語(yǔ)法被使用的前提是它可以顯著的提升我們代碼的可讀寫和可維護(hù)性。let聲明在大多數(shù)情況下,可以替換var以避免預(yù)期之外的問(wèn)題。使用let你可以把聲明在塊的頂部進(jìn)行而非函數(shù)的頂部進(jìn)行。

          有時(shí),我們希望有些變量的引用不可變,這時(shí)候使用const就能防止很多問(wèn)題的發(fā)生。下述代碼中 在checklist函數(shù)外給items變量傳遞引用時(shí)就非常容易出錯(cuò),它返回的todo API和items有了交互。當(dāng)items變量被改為指向另外一個(gè)列表時(shí),我們的代碼就出問(wèn)題了。todo API 用的還是items之前的值,items本身的指代則已經(jīng)改變。

          var items = ['a', 'b', 'c'] var todo = checklist(items) todo.check() console.log(items) // <- ['b', 'c'] items = ['d', 'e'] todo.check() console.log(items) // <- ['d', 'e'], would be ['c'] if items had been constant function checklist(items) {   return {     check: () => items.shift()   } }
          登錄后復(fù)制

          這類問(wèn)題很難debug,找到問(wèn)題原因就會(huì)花費(fèi)你很長(zhǎng)一段時(shí)間。使用const運(yùn)行時(shí)就會(huì)報(bào)錯(cuò),可以幫助你可以避免這種問(wèn)題。

          如果我們默認(rèn)只使用cosntlet聲明變量,所有的變量都會(huì)有一樣的作用域規(guī)則,這讓代碼更易理解,由于const造成的影響最小,它還曾被提議作為默認(rèn)的變量聲明。

          總的來(lái)說(shuō),const不允許重新指定值,使用的是塊作用域,存在TDZ。let則允許重新指定值,其它方面和const類似,而var聲明使用函數(shù)作用域,可以重新指定值,可以在未聲明前調(diào)用,考慮到這些,推薦盡量不要使用var聲明了。

          贊(0)
          分享到: 更多 (0)
          網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)