亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

輕松理解Javascript變量的相關(guān)問題

 更新時(shí)間:2017年01月20日 14:03:10   投稿:daisy  
這篇文章主要給大家介紹了關(guān)于Javascript變量的相關(guān)問題,文中給出了詳細(xì)的介紹和示例代碼,相信對(duì)大家的理解和學(xué)習(xí)具有一定的參考借鑒價(jià)值,有需要的朋友們下面來一起看看吧。

前言

再說本文的內(nèi)容之前,我們先回溯到1995年,當(dāng)Brendan Eich在設(shè)計(jì)第一版JavaScript時(shí),他搞錯(cuò)了許多東西,當(dāng)然這也包括曾屬于語(yǔ)言本身的一部分,例如Date對(duì)象,對(duì)象相乘被自動(dòng)轉(zhuǎn)換為NaN等。然而現(xiàn)在回過頭看,語(yǔ)言最重要的部分都是設(shè)計(jì)合理的:對(duì)象、原型、具有詞法作用域的一等函數(shù)、默認(rèn)情況下的可變性等。語(yǔ)言的骨架非常優(yōu)秀,甚至超越了人們對(duì)它的初步印象。

話說回來,正是Brendan當(dāng)初的設(shè)計(jì)錯(cuò)誤才誕生了今天這篇文章。我們這次關(guān)注的目標(biāo)非常小,在你使用這門語(yǔ)言多年后可能根本不會(huì)注意到這個(gè)問題,但是它又如此重要,因?yàn)槲覀兛赡軙?huì)誤認(rèn)為這個(gè)錯(cuò)誤就是語(yǔ)言設(shè)計(jì)中的“the good parts”(譯者注:請(qǐng)參考《JavaScript語(yǔ)言精粹》一書中附錄A:毒瘤中有關(guān)作用域的描述)。

今天我們一定要把這些與變量有關(guān)的問題拿下。

問題 #1:JS沒有塊級(jí)作用域

請(qǐng)看這樣一條規(guī)則: 在JS函數(shù)中的var聲明,其 作用域 是函數(shù)體的全部 。乍一聽沒什么問題,但是如果碰到以下兩種情況就不會(huì)得到令人滿意的結(jié)果。

其一,在代碼塊內(nèi)聲明的變量,其作用域是整個(gè)函數(shù)作用域而不是塊級(jí)作用域。

你之前可能沒有關(guān)注到這一點(diǎn),但我擔(dān)心這個(gè)問題確實(shí)是你不能夠輕易忽視的。我們一起重現(xiàn)一下由這個(gè)問題引發(fā)的bug。

假如你現(xiàn)在的代碼使用了一個(gè)變量t:

function runTowerExperiment(tower, startTime) {
 var t = startTime;
 tower.on("tick", function () {
 ... 使用了變量t的代碼 ...
 });
 ... 更多代碼 ...
 }

到目前為止,一切都很順利?,F(xiàn)在你想添加測(cè)量保齡球速度的功能,所以你在回調(diào)函數(shù)內(nèi)部添加了一個(gè)簡(jiǎn)單的if語(yǔ)句。

function runTowerExperiment(tower, startTime) {
 var t = startTime;
 tower.on("tick", function () {
 ... 使用了變量t的代碼 ...
 if (bowlingBall.altitude() <= 0) {
  var t = readTachymeter();
  ...
 }
 });
 ... 更多代碼 ...
 }

哦,親愛的,之前那段“使用了變量t的代碼”運(yùn)行良好,現(xiàn)在你無意中添加了第二個(gè)變量t,這里的t指向的是一個(gè)新的內(nèi)部變量t而不是原來的外部變量。

JavaScript中var聲明的作用域像是Photoshop中的油漆桶工具,從聲明處開始向前后兩個(gè)方向擴(kuò)散,直到觸及函數(shù)邊界才停止擴(kuò)散。你想啊,這種變量t的作用域甚廣,所以一進(jìn)入函數(shù)就要馬上將它創(chuàng)建出來。這就是所謂的提升(hoisting)。變量提升就好比是,JS引擎用一個(gè)很小的代碼起重機(jī)將所有var聲明和function函數(shù)聲明都舉起到函數(shù)內(nèi)的最高處。

現(xiàn)在看來,提升特性自有它的優(yōu)點(diǎn)。如果沒有提升的動(dòng)作,許多在全局作用域范圍內(nèi)看似合理的完美技術(shù)在立即調(diào)用函數(shù)表達(dá)式( IIFE )中通通失效。但在上面演示的這種情況下,提升會(huì)引發(fā)令人不愉快的bug:所有使用變量t進(jìn)行的計(jì)算最終的結(jié)果都是NaN。這種問題極難定位,尤其是當(dāng)你的代碼量遠(yuǎn)超上面這個(gè)玩具一般的示例,你會(huì)發(fā)狂到崩潰。

在原有代碼塊之前添加新的代碼塊會(huì)導(dǎo)致詭異的錯(cuò)誤,這時(shí)候我就會(huì)想,到底是誰(shuí)的問題,我的還是系統(tǒng)的?我們可不希望自己搞砸了系統(tǒng)。

而這個(gè)問題與接下來這個(gè)問題相比就相形見絀了。

問題 #2:循環(huán)內(nèi)變量過度共享

你可以猜一下當(dāng)執(zhí)行以下這段代碼時(shí)會(huì)發(fā)生什么,非常簡(jiǎn)單:

var messages = ["嗨!", "我是一個(gè)web頁(yè)面!", "alert()方法非常有趣!"];
 for (var i = 0; i < messages.length; i++) {
 alert(messages[i]);
 }

如果你一直跟隨這個(gè)專欄的文章,你知道我喜歡在示例代碼中使用alert()方法??赡苣阋仓?code>alert()不是一個(gè)好的API,它是一個(gè)同步方法,所以當(dāng)彈出一個(gè)警告對(duì)話框時(shí),輸入事件不會(huì)觸發(fā),你的JS代碼,包括你的整個(gè)UI,直到用戶點(diǎn)擊OK確認(rèn)之前完全處于暫停狀態(tài)。

請(qǐng)不要輕易使用alert()來實(shí)現(xiàn)Web頁(yè)面中的功能,我之所以在代碼中使用是因?yàn)?code>alert()特性使它變成一個(gè)非常有教學(xué)意義的工具。

而且,如果放棄所有笨重的方法和糟糕的行為就可以做出一只會(huì)說話的貓,何樂而不為呢?

var messages = ["喵!", "我是一只會(huì)說話的貓!", "回調(diào)(callback)非常有趣!"];
 for (var i = 0; i < messages.length; i++) {
 setTimeout(function () {
 cat.say(messages[i]);
 }, i * 1500);
 }

然而一定是哪里不對(duì),這只會(huì)說話的貓并沒有按照預(yù)期連說三條消息,它說了三次“undefined”。

你知道問題出在哪里么?

你能看到樹上的毛毛蟲(bug)嗎?(圖片來源: nevil saveri )

事實(shí)上,這個(gè)問題的答案是,循環(huán)本身及三次timeout回調(diào)均共享唯一的變量i。當(dāng)循環(huán)結(jié)束執(zhí)行時(shí),i的值為3(因?yàn)?code>messages.length的值為3),此時(shí)回調(diào)尚未被觸發(fā)。

所以當(dāng)?shù)谝粋€(gè)timeout執(zhí)行時(shí),調(diào)用cat.say(messages[i]) ,此時(shí)i的值為3,所以貓咪最終打印出來的是messages[3]的值亦即undefined。

解決這個(gè)問題有很多種方法( 這里有一種 ),但是你想,var作用域規(guī)則接連給你添麻煩,如果能在第一時(shí)間徹底解決掉這個(gè)問題多好??!

let是更完美的var

JavaScript的設(shè)計(jì)錯(cuò)誤(其它語(yǔ)言也有,奈何JavaScript太突出)多半不能被修復(fù)。保持向后兼容性意味著永不改變JS代碼在Web平臺(tái)上的行為,即使連標(biāo)準(zhǔn)委員會(huì)都無權(quán)要求修復(fù)JavaScript中自動(dòng)插入分號(hào)這種怪異的特性;瀏覽器廠商也從來不會(huì)做出突破性的改變,因?yàn)槿绱艘粊韨Φ氖撬麄兊闹覍?shí)用戶。

所以大約十年以前,Brendan Eich決定修復(fù)這個(gè)問題,但只有唯一的解決方案。

他添加了一個(gè)新的關(guān)鍵詞:let。let與var一樣,也可以用來聲明變量,但它有著更好的作用域規(guī)則。

它看起來是這樣的:

let t = readTachymeter();

或者這樣的:

for (let i = 0; i < messages.length; i++) {
 ...
 }

let與var還是有不同之處的,所以如果你只是在代碼中將var全局搜索替換為let,一些依賴var聲明的獨(dú)特特性(可能你不是故意這樣寫)的代碼可能無法正常運(yùn)行。但對(duì)于絕大多數(shù)代碼來說,在ES6的新代碼模式下,你應(yīng)該停止使用var聲明變量,能使用let就用吧!從現(xiàn)在起,請(qǐng)記住這句口號(hào):“l(fā)et是更完美的var”。

那到底let和var有什么不同呢?非常高興你提出這個(gè)問題!

這一規(guī)則可以幫助你捕捉bug,除了NaN錯(cuò)誤以外,每一個(gè)異常都會(huì)在當(dāng)前行拋出。

let聲明的變量擁有塊級(jí)作用域。也就是說用let聲明的變量的作用域只是外層塊,而不是整個(gè)外層函數(shù)。

let聲明仍然保留了提升的特性,但不會(huì)盲目提升。在runTowerExperiment這個(gè)示例中,通過將var替換為let可以快速修復(fù)問題,如果你處處使用let進(jìn)行聲明,就不會(huì)遇到類似的bug。

let聲明的全局變量不是全局對(duì)象的屬性。這就意味著,你不可 以通過window.變量名的方式訪問這些變量。它們只存在于一個(gè)不可見的塊的作用域中,這個(gè)塊理論上是Web頁(yè)面中運(yùn)行的所有JS代碼的外層塊。

形如for (let x...)的循環(huán)在每次迭代時(shí)都為x創(chuàng)建新的綁定。

這是一個(gè)非常微妙的區(qū)別,拿我們的會(huì)說話的貓的例子來說,如果一個(gè)for (let...)循環(huán)執(zhí)行多次并且循環(huán)保持了一個(gè)閉包,那么每個(gè)閉包將捕捉一個(gè)循環(huán)變量的不同值作為副本,而不是所有閉包都捕捉循環(huán)變量的同一個(gè)值。

所以在會(huì)說話的貓示例中,也可以通過將var替換為let修復(fù)bug。

這種情況適用于現(xiàn)有的三種循環(huán)方式:for-of、for-in、以及傳統(tǒng)的用分號(hào)分隔的類C循環(huán)。

let聲明的變量直到控制流到達(dá)該變量被定義的代碼行時(shí)才會(huì)被裝載,所以在到達(dá)之前使用該變量會(huì)觸發(fā)錯(cuò)誤。舉個(gè)例子:

function update() {
 console.log("當(dāng)前時(shí)間:", t); // 引用錯(cuò)誤(ReferenceError)
 ...
 let t = readTachymeter()
 }

不可訪問的這段時(shí)間變量一直處于作用域中,但是尚未裝載,它們位于臨時(shí)死區(qū)(Temporal Dead Zone,簡(jiǎn)稱TDZ)中。我一直想用科幻小說來類比這個(gè)腦洞大開的行話,但是還沒想好怎么搞。

(脆弱的性能細(xì)節(jié):在大多數(shù)情況下,查看代碼就可以區(qū)分聲明是否已經(jīng)執(zhí)行,所以事實(shí)上,JavaScript引擎不需要在每次代碼運(yùn)行時(shí)都額外執(zhí)行 一次變量可訪問檢查來確保變量已經(jīng)被初始化。然而在閉包內(nèi)部有時(shí)不是透明的,這時(shí)JavaScript引擎將會(huì)做一個(gè)運(yùn)行時(shí)檢查,也就意味著let相對(duì)var而言比較慢。)

(脆弱的平行宇宙作用域細(xì)節(jié):在一些編程語(yǔ)言中,一個(gè)變量的作用域始于聲明之處,而非前后覆蓋整個(gè)封閉代碼塊。標(biāo)準(zhǔn)委員會(huì)曾考慮過將這種作用域準(zhǔn)則賦予let關(guān)鍵詞,但是一旦使用這種準(zhǔn)則,原本提前使用變量的語(yǔ)句會(huì)導(dǎo)致引用錯(cuò)誤(ReferenceError),現(xiàn)在該語(yǔ)句不位于let t的聲明作用域中,根本不會(huì)引用此處的變量t,而是引用外層作用域的相應(yīng)變量。但是這個(gè)方法無法與閉包和函數(shù)提升很好得結(jié)合,所以該提案最終被否決了。)

用let重定義變量會(huì)拋出一個(gè)語(yǔ)法錯(cuò)誤(SyntaxError)。

這一條規(guī)則也可以幫助你檢測(cè)瑣碎的小問題。誠(chéng)然,這亦是var與let的不同之處,當(dāng)你全局搜索var替換為let時(shí)也會(huì)導(dǎo)致let重定義語(yǔ)法錯(cuò)誤,因?yàn)檫@一規(guī)則對(duì)全局let變量也有效。

如果你的多個(gè)腳本中都聲明了相同的全局變量,你最好繼續(xù)用var聲明這些變量。如果你換用了let,后加載的腳本都會(huì)執(zhí)行失敗并拋出錯(cuò)誤。

或者你可以考慮使用ES6內(nèi)建的模塊機(jī)制,后面的文章中會(huì)詳細(xì)講解。

(脆弱的語(yǔ)法細(xì)節(jié):let是一個(gè)嚴(yán)格模式下的保留詞。在非嚴(yán)格模式下,出于向后兼容的目的,你仍可以用let命名來聲明變量、函數(shù)和參數(shù),雖然你不會(huì)犯傻,但是你確實(shí)可以編寫var let = 'q';這樣的代碼!不過let let;無論如何都是非法的。)

在那些不同之外,let和var幾乎很相似了。舉個(gè)例子,它們都支持使用逗號(hào)分隔聲明多重變量,它們也都支持 解構(gòu) 特性。

注意,class類聲明的行為與var不同而與let一致。如果你加載一段包含同名類的腳本,后定義的類會(huì)拋出重定義錯(cuò)誤。

const

是的,還有一個(gè)新的關(guān)鍵詞!

ES6引入的第三個(gè)聲明類關(guān)鍵詞與let類似:const。

const聲明的變量與let聲明的變量類似,它們的不同之處在于,const聲明的變量只可以在聲明時(shí)賦值,不可隨意修改,否則會(huì)導(dǎo)致SyntaxError(語(yǔ)法錯(cuò)誤)。

const MAX_CAT_SIZE_KG = 3000; // 正確

 MAX_CAT_SIZE_KG = 5000; // 語(yǔ)法錯(cuò)誤(SyntaxError)
 MAX_CAT_SIZE_KG++; // 雖然換了一種方式,但仍然會(huì)導(dǎo)致語(yǔ)法錯(cuò)誤

當(dāng)然,規(guī)范設(shè)計(jì)的足夠明智,用const聲明變量后必須要賦值,否則也拋出語(yǔ)法錯(cuò)誤。

const theFairest; // 依然是語(yǔ)法錯(cuò)誤,你這個(gè)倒霉蛋

神秘的代理命名空間

“命名空間是一種絕妙的理念,我們應(yīng)當(dāng)多加利用!”——Tim Peters,“這是Python之禪”

嵌套作用域是編程語(yǔ)言背后的核心理念之一,這個(gè)理念始于大約57年前的 ALGOL,現(xiàn)在回過頭看當(dāng)時(shí)的決定無比正確。

在ES3之前,JavaScript中只有全局作用域和函數(shù)作用域。(讓我們忽略with語(yǔ)句吧。)ES3中引入了try-catch語(yǔ)句,意味著語(yǔ)言中誕生一種新的作用域,只用于catch塊中的異常變量。ES5添加了用于嚴(yán)格的eval()方法的作用域。ES6添加了塊作用域,for循環(huán)作用域,新的全局let作用域,模塊作用域,以及求參數(shù)的默認(rèn)值時(shí)使用的附加作用域。

所有自ES3開始添加的其它作用域非常重要,它們的加入使得JavaScript面向過程與面向?qū)ο蟮奶匦赃\(yùn)行得猶如閉包一樣平穩(wěn)、精準(zhǔn),當(dāng)然閉包也可以無縫銜接這些作用域?qū)崿F(xiàn)各種功能。或許你在閱讀這篇文章之前從未注意到這些作用域規(guī)則的存在,如果真的這樣,那這門語(yǔ)言就恰如其分地完成了它的本職工作。

我現(xiàn)在可以使用let和const了么?

可以。如果要在Web上使用let和const特性,你需要使用一個(gè)諸如 Babel 、 Traceur或 TypeScript 的ES6轉(zhuǎn)譯器。(Babel和Traceur暫不支持臨時(shí)死區(qū)特性。)

io.js支持let和const,但是只在嚴(yán)格模式下編碼可以使用。Node.js同樣支持,但是需要啟用--harmony選項(xiàng)。

九年前 ,Brendan Eich在Firefox中實(shí)現(xiàn)了初版的let關(guān)鍵詞。這個(gè)特性在隨后的標(biāo)準(zhǔn)化進(jìn)程中徹底地被重新設(shè)計(jì)了。Shu-yu Guo正在按照新標(biāo)準(zhǔn)對(duì)原有實(shí)現(xiàn)進(jìn)行升級(jí),該項(xiàng)目由Jeff Walden和其他人做代碼審查。

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流。

相關(guān)文章

最新評(píng)論