轉自:http://udn.yyuap.com/thread-55283-1-1.html
如果你是一個JavaScript新手或僅僅最近才在你的開發工作中接觸它,你可能感到沮喪。所有的語言都有自己的怪癖(quirks)——但從基於強型別的伺服器端語言轉移過來的開發人員可能會感到困惑。我就曾經這樣,幾年前,當我被推到了全職JavaScript開發人員的時候,有很多事情我希望我一開始就知道。在這篇文章中,我將分享一些怪癖,希望我能分享給你一些曾經令我頭痛不已的經驗。這不是一個完整列表——僅僅是一部分——但希望它讓你看清這門語言的強大之處,可能曾經被你認為是障礙的東西。 我們將看下列技巧: 相等 點號vs括弧 函數上下文 函式宣告vs函數運算式 命名vs匿名函數 立即執行函數運算式 typeof vs Object.prototype.toString
1.) 相等 C#出身的我非常熟悉==比較子。實值型別(或字串)當有相同值是是相等的。參考型別相等需要有相同的引用。(我們假設你沒有重載==運算子,或實現你自己的等值運算和GetHashCode方法)我很驚訝為什麼JavaScript有兩個等值運算子:==和===。最初我的大部分代碼都是用的==,所以我並不知道當我運行如下代碼的時候JavaScript為我做了什麼: var x = 1; if(x == "1") { console.log("YAY! They're equal!"); } 複製代碼
這是黑暗魔法嗎。整數1是如何和字串”1”相等的。
在JavaScript中,有相等(==)和嚴格相等(===)之說。相等運算子將強制轉換兩邊的運算元為相同類型後執行嚴格相等比較。所以在上面的例子中,字串”1”會被轉換為整數1,這個過程在幕後進行,然後與變數x進行比較。
嚴格相等不進行類型轉換。如果運算元類型不同(如整數和字串),那麼他們不全等(嚴格相等)。 var x = 1; // 嚴格平等,類型必須相同 if(x === "1") { console.log("Sadly, I'll never write this to the console"); } if(x === 1) { console.log("YES! Strict Equality FTW.") } 複製代碼
你可能正在考慮可能發生強制類型轉換而引起的各種恐怖問題——假設你的引用中發生了這種轉換,可能導致你非常困難找到問題出在哪裡。這並不奇怪,這也是為什麼經驗豐富的JavaScript開發人員總是建議使用嚴格相等。
2.) 點號 vs 括弧 這取決於你來自其他什麼語言,你可能見過或沒見過這種方式(這就是廢話)。 // 擷取person對象的firstName值 var name = person.firstName; // 擷取數組的第三個元素 var theOneWeWant = myArray[2]; // remember, 0-based index不要忘了第一個元素的索引是0 複製代碼
然而,你知道它也可以使用括弧引用對象的成員嗎。比如說: var name = person["firstName"]; 複製代碼
為什麼會這樣有用嗎。而你會用點符號的大部分時間,有幾個執行個體的括弧使某些方法可能無法這樣做。例如,我會經常重構大開關語句到一個調度表,所以這樣的事情:
為什麼可以這樣用。你以前可能對使用點更熟悉,有幾個特例只能用括弧標記法。例如,我經常會將switch語句重構為尋找表(速度更快),其實就像這樣: var doSomething = function(doWhat) { switch(doWhat) { case "doThisThing": // more code... break; case "doThatThing": // more code... break; case "doThisOtherThing": // more code.... break; // additional cases here, etc. default: // default behavior break; } } 複製代碼
可以轉化為像下面這樣: var thingsWeCanDo = { doThisThing : function() { /* behavior */ }, doThatThing : function() { /* behavior */ }, doThisOtherThing : function() { /* behavior */ }, default : function() { /* behavior */ } }; var doSomething = function(doWhat) { var thingToDo = thingsWeCanDo.hasOwnProperty(doWhat) ? doWhat : "default" thingsWeCanDo[thingToDo](); } 複製代碼
使用switch並沒有錯誤(並且在許多情況下,如果被迭代多次並且非常關注效能,switch可能比尋找表表現更好)。然而尋找表提供了一個很好的方法來組織和擴充代碼,並且括弧允許你的屬性延時求值。
3.) 函數上下文 已經有一些偉大的部落格發表了文章,正確理解了JavaScript中的this上下文(在文章的結尾我會給出一些不錯的連結),但它確實應該加到“我希望我知道”的列表。它真的困難看懂代碼並且自信的知道在任何位置this的值——你僅需要學習一組規則。不幸的是,我早起讀到的許多解釋只是增加了我的困惑。因此我試圖簡明扼要的做出解釋。
第一——首先考慮全域情況(Global) 預設情況下,直到某些原因改變了執行內容,否則this的值都指向全域對象。在瀏覽器中,那將會是window對象(或在node.js中為global)。
第二——方法中的this值 當你有一個對象,其有一個函數成員,沖父物件調用這方法,this的值將指向父物件。例如: var marty = { firstName: "Marty", lastName: "McFly", timeTravel: function(year) { console.log(this.firstName + " " + this.lastName + " is time traveling to " + year); } } marty.timeTravel(1955); // Marty McFly is time traveling to 1955 複製代碼
你可能已經知道你能引用marty對象的timeTravel方法並且建立一個其他對象的新引用。這實際上是JavaScript非常強大的特色——使我們能夠在不同的執行個體上引用行為(調用函數)。 var doc = { firstName: "Emmett", lastName: "Brown", } doc.timeTravel = marty.timeTravel; 複製代碼
所以——如果我們調用doc.timeTravel(1885)將會發生什麼。 doc.timeTravel(1885); // Emmett Brown is time traveling to 1885 複製代碼
再次——上演黑暗魔法。嗯,並不是真的。記住,當你調用一個方法的時候,this上下文是被調用函數父的父物件。
當我們儲存marty.TimeTravel方法的引用然後調用我們儲存的引用時發生了什麼。讓我們看看: var getBackInTime = marty.timeTravel; getBackInTime(2014); // undefined undefined is time traveling to 2014 為什麼是“undefined undefined”。。而不是“Matry McFly”。 複製代碼
讓我們問一個關鍵的問題:當我們調用我們的getBackInTime函數時父物件/容器物件是什麼。當getBackIntTime函數存在於window中時,我們調用它作為一個函數,而不是一個對象的方法。當我們像這樣調用一個函數——沒有容器物件——this上下文將是全域對象。David Shariff有一個偉大的描述關於這:
無論何時調用一個函數,我們必須立刻查看括弧的左邊。如果在括弧的左邊存在一個引用,那麼被傳遞個調用函數的this值確定為引用所屬的對象,否則是全絕對象。
由於getBackInTime的this上下文是window——沒有firstName和lastName屬性——這解釋了為什麼我們看見“undefined undefined”。
因此我們知道直接調用一個函數——沒有容器物件——this內容相關的結果是全域對象。然而我也說我早就知道我們的getBackInTime函數存在於window上。我是如何知道的。好的,不像上面我包裹getBackInTime在不同的上下文(我們探討立即執行函數運算式的時候),我聲明的任何變數都被添加的window。來自Chrome控制台的驗證:
是時候討論下this的主要用武之地之一了:訂閱事件處理。
第三(僅僅是#2的擴充)——非同步呼叫方法中的this值 所以,讓我們假裝我們想調用我們的marty.timeTravel方法當有人點擊一個按鈕時: var flux = document.getElementById("flux-capacitor"); flux.addEventListener("click", marty.timeTravel); 複製代碼
在上面的代碼中,當使用者點擊按鈕是,我們會看見“undefined undefined is time traveling to [object MouseEvent]”。什麼。好——首先,非常明顯的問題是我們沒有給我們的timeTravel方法提供year參數。反而,我們直接訂閱這方法作為事件處理常式,並且MouseEvent參數被作為第一個參數傳遞個事件處理常式。這是很容易修複的,但真正的問題是我們再次見到“undefined undefined”。不要無望——你已經知道為什麼會發生這種情況(即使你還沒意識到)。讓我們修改我們的timeTravel函數,輸出this,從而協助我們搞清事實: marty.timeTravel = function(year) { console.log(this.firstName + " " + this.lastName + " is time traveling to " + year); console.log(this); }; 複製代碼
現在——當我們點擊這按鈕,我們將類似下面的輸出 在你的瀏覽器控制台:
當方法被調用時,第二個console.log輸出出this上下文——它實際上是我們訂閱事件的按鈕元素。你感到吃驚嗎。就像之前——當我們將marty.timeTravel賦值給getBackInTime變數時——對marty.timeTravel的引用被儲存到事件處理常式,並被調用,但容器物件不再是marty對象。在這種情況下,它將在按鈕執行個體的點擊事件中非同步呼叫。
所以——有可能將this設定為我們想要的結果嗎。絕對可以。在這個例子裡,解決方案非常簡單。不在事件處理常式中直接訂閱marty.timeTravel,而是使用匿名函數作為事件處理常式,並在匿名函數中調用marty.timeTravel。這也能修複year參數丟失的問題。 flux.addEventListener("click", function(e) { marty.timeTravel(someYearValue); }); 複製代碼
點擊按鈕將會在控制台輸出類似下面的資訊:
成功了。但為什麼這樣可以。思考我們是如何調用timeTravel方法的。在我們按鈕點擊的第一個例子中,我們在事件處理常式中訂閱者法自身的引用,所以它沒有從父物件marty上調用。在第二個例子中,通過this為按鈕元素的匿名函數,並且當我們調用marty.timeTravel時,我們從其父物件marty上調用,所以this為marty。
第四——建構函式中的this值 當你用建構函式建立對象執行個體時,函數內部的this值就是新建立的對象。例如: var TimeTraveler = function(fName, lName) { this.firstName = fName; this.lastName = lName; // Constructor functions return the // newly created object for us unless // we specifically return something else |