Web前端開發人員應該知道的JavaScript技巧有哪些?

熟悉Web前端工作的小夥伴都知道,在Web工作中離不開JavaScript的使用,js的技巧有很多,那麼Web程序員都需要掌握哪些呢?下面讓我們一起來看一看吧!

主要技巧有:

· 相等

· 點號vs括號

· 函數上下文

· 函數聲明vs函數表達式

· 命名vs匿名函數

· 立即執行函數表達式

· typeof vs Object.prototype.toString

1、相等

C#出身的我非常熟悉==比較運算符。值類型(或字符串)當有相同值是是相等的。引用類型相等需要有相同的引用。(我們假設你沒有重載==運算符,或實現你自己的等值運算和GetHashCode方法)我很驚訝爲什麼JavaScript有兩個等值運算符:==和===。最初我的大部分代碼都是用的==,所以我並不知道當我運行如下代碼的時候JavaScript爲我做了什麼:學習資料的話可以加下web前端開發學習裙:659加上479再加上860自己去羣裏下載下。

這是黑暗魔法嗎?整數1是如何和字符串”1”相等的?

在JavaScript中,有相等(==)和嚴格相等(===)之說。相等運算符將強制轉換兩邊的操作數爲相同類型後執行嚴格相等比較。所以在上面的例子中,字符串”1”會被轉換爲整數1,這個過程在幕後進行,然後與變量x進行比較。

嚴格相等不進行類型轉換。如果操作數類型不同(如整數和字符串),那麼他們不全等(嚴格相等)。

你可能正在考慮可能發生強制類型轉換而引起的各種恐怖問題——假設你的引用中發生了這種轉換,可能導致你非常困難找到問題出在哪裏。這並不奇怪,這也是爲什麼經驗豐富的JavaScript開發者總是建議使用嚴格相等。

2、點號VS.括號

這取決於你來自其他什麼語言,你可能見過或沒見過這種方式(這就是廢話)。

1. //獲取person對象的firstName值

2. var name = person.firstName;

3.

4. //獲取數組的第三個元素

5. var theOneWeWant = myArray[2]; // remember, 0-based index不要忘了第一個元素的索引是0

然而,你知道它也可以使用括號引用對象的成員嗎?比如說:

1. var name = person["firstName"];

爲什麼會這樣有用嗎?而你會用點符號的大部分時間,有幾個實例的括號使某些方法可能無法這樣做。例如,我會經常重構大開關語句到一個調度表,所以這樣的事情:

爲什麼可以這樣用?你以前可能對使用點更熟悉,有幾個特例只能用括號表示法。例如,我經常會將switch語句重構爲查找表(速度更快),其實就像這樣:

可以轉化爲像下面這樣:

使用switch並沒有錯誤(並且在許多情況下,如果被迭代多次並且非常關注性能,switch可能比查找表表現更好)。然而查找表提供了一個很好的方法來組織和擴展代碼,並且括號允許你的屬性延時求值。

3、函數上下文

已經有一些偉大的博客發表了文章,正確理解了JavaScript中的this上下文(在文章的結尾我會給出一些不錯的鏈接),但它確實應該加到“我希望我知道”的列表。它真的困難看懂代碼並且自信的知道在任何位置this的值——你僅需要學習一組規則。不幸的是,我早起讀到的許多解釋只是增加了我的困惑。因此我試圖簡明扼要的做出解釋。

第一——首先考慮全局情況(Global)

默認情況下,直到某些原因改變了執行上下文,否則this的值都指向全局對象。在瀏覽器中,那將會是window對象(或在node.js中爲global)。

第二——方法中的this值

當你有一個對象,其有一個函數成員,衝父對象調用這方法,this的值將指向父對象。例如:

你可能已經知道你能引用marty對象的timeTravel方法並且創建一個其他對象的新引用。這實際上是JavaScript非常強大的特色——使我們能夠在不同的實例上引用行爲(調用函數)。

所以——如果我們調用doc.timeTravel(1885)將會發生什麼?

再次——上演黑暗魔法。嗯,並不是真的。記住,當你調用一個方法的時候,this上下文是被調用函數父的父對象。

當我們保存marty.TimeTravel方法的引用然後調用我們保存的引用時發生了什麼?讓我們看看:

讓我們問一個關鍵的問題:當我們調用我們的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方法當有人點擊一個按鈕時:

在上面的代碼中,當用戶點擊按鈕是,我們會看見“undefined undefined is time traveling to [object MouseEvent]”。什麼?好——首先,非常明顯的問題是我們沒有給我們的timeTravel方法提供year參數。反而,我們直接訂閱這方法作爲事件處理程序,並且MouseEvent參數被作爲第一個參數傳遞個事件處理程序。這是很容易修復的,但真正的問題是我們再次見到“undefined undefined”。不要無望——你已經知道爲什麼會發生這種情況(即使你還沒意識到)。讓我們修改我們的timeTravel函數,輸出this,從而幫助我們搞清事實:

現在——當我們點擊這按鈕,我們將類似下面的輸出在你的瀏覽器控制檯:

當方法被調用時,第二個console.log輸出出this上下文——它實際上是我們訂閱事件的按鈕元素。你感到吃驚嗎?就像之前——當我們將marty.timeTravel賦值給getBackInTime變量時——對marty.timeTravel的引用被保存到事件處理程序,並被調用,但容器對象不再是marty對象。在這種情況下,它將在按鈕實例的點擊事件中異步調用。

所以——有可能將this設置爲我們想要的結果嗎?絕對可以!在這個例子裏,解決方法非常簡單。不在事件處理程序中直接訂閱marty.timeTravel,而是使用匿名函數作爲事件處理程序,並在匿名函數中調用marty.timeTravel。這也能修復year參數丟失的問題。

1. flux.addEventListener("click", function(e) {

2. marty.timeTravel(someYearValue);

3. });

點擊按鈕將會在控制檯輸出類似下面的信息:

成功了!但爲什麼這樣可以?思考我們是如何調用timeTravel方法的。在我們按鈕點擊的第一個例子中,我們在事件處理程序中訂閱方法自身的引用,所以它沒有從父對象marty上調用。在第二個例子中,通過this爲按鈕元素的匿名函數,並且當我們調用marty.timeTravel時,我們從其父對象marty上調用,所以this爲marty。

第四——構造函數中的this值

當你用構造函數創建對象實例時,函數內部的this值就是新創建的對象。例如:

你可能開始疑惑,上面的例子中,沒有語言級別的特性允許我們在運行時指定調用函數的this值嗎?你是對的。存在於函數原型上的call和apply方法允許我們調用函數並傳遞this值。

call方法的第一個參數是this,後面是被調用函數的參數序列:

1. someFn.call(this, arg1, arg2, arg3);

apply的第一個參數也是this,後面是其餘參數組成的數組:

1. someFn.apply(this, [arg1, arg2, arg3]);

我們的doc和marty實例他們自己能時間旅行,但einstein(愛因斯坦)需要他們的幫助才能完成時間旅行。所以讓我們給我們的doc實例添加一個方法,以至於doc能幫助einstein完成時間旅行。

現在它可以傳送Einstein了:

我知道這個例子有些牽強,但它足以讓你看到應用函數到其他對象的強大之處。

這種方法還有我們沒有發現的另一種用處。讓我們給我們的marty實例添加一個goHome方法,作爲this.timeTravel(1985)的快捷方式。

然而,我們知道如果我們訂閱marty.goHome作爲按鈕的點擊事件處理程序,this的值將是按鈕——並且不幸的是按鈕沒有timeTravel方法。我們能用上面的方法解決——用個一匿名函數作爲事件處理程序,並在其內部調用上述方法——但我們有另一個選擇——bind函數:

1. flux.addEventListener("click", marty.goHome.bind(marty));

bind函數實際上會返回一個新函數,新函數的this值根據你提供的參數設置。如果你需要支持低版本瀏覽器(例如:ie9以下版本),你可能需要bind函數的shim(或者,如果你使用jQuery你可以用$.proxy代替,underscore和lodash都提供_.bind方法)。

記住重要一點,如果你直接使用原型上的bind方法,它將創建一個實例方法,這將繞過原型方法的優點。這不是錯誤,做到心裏清楚就行了。我寫了關於這個問題得更多信息在這裏。

4、函數表達式VS.函數聲明

函數聲明不需要var關鍵字。事實上,如Angus Croll所說:“把他們想象成變量聲明的兄弟有助於理解”。例如:

上面例子裏的函數名字timeTravel不僅在它聲明的在作用域可見,同時在函數本身內部也是可見的(這對遞歸函數調用非常有用)。函數聲明,本質上說其實就是命名函數。換句話說,上面函數的名稱屬性是timeTravel。

函數表達式定義一個函數並指派給一個變量。典型應用如下:

討論函數表達式和函數聲明不能不提“hoisting(提升)”——函數和變量聲明被編譯器移到作用域的頂部。在這裏我們無法詳細解釋hoisting,但你可以讀Ben Cherry和Angus Croll兩個人的偉大解釋。

5、命名VS.匿名函數

基於我們剛纔的討論,你可能一進猜到“匿名”函數其實就是一個沒有名字的函數。大多數JavaScript開發者能迅速識別瞎買年第一個參數爲匿名函數:

然而,同樣的我們的marty.timeTravvel方法也是一個匿名函數:

因爲函數聲明必須有一個唯一的名字,只有函數表達式可以沒有名字。

6、立即執行函數表達式

因爲我們正在談論函數表達式,有一個東西我希望我早知道:立即執行函數表達式(IIFE)。有很多關於IIFE的好文章(我將在文章結尾出列出),但用一句話來形容,函數表達式不是通過將函數表達式賦值給一個標量,稍後再執行,而是理解執行。可以在瀏覽器控制檯看這一過程。

首先——讓我們先敲入一個函數表達式——但不給它指派變量——看看會發什麼:

語法錯誤——這被認爲是函數聲明,缺少函數名字。然而,爲了使其變爲表達式,我們僅需將其包裹在括號內:

讓其變爲表達式後控制檯返回給我們一個匿名函數(記住,我們沒有爲其指派值,但表達式會有返回值)。所以——我們知道“函數表達式”是“立即調用函數表達式”的一部分。爲了等到“立即執行”的特性,我們通過在表達式後面添加另一個括號來調用返回的表達式(就像我們調用其他函數一樣):

“但是等一下,Jim!(指作者)我想我以前見過這種調用方式”。事實上你可能見過——這是合法的語法(衆所周知的是Douglas Crockford的首選語法)

這兩種方法都起作用,但是我強烈建議你讀一讀這裏。

OK,非常棒——現在我們已經知道了IIFE是什麼——以及爲什麼要用它?

它幫助我們控制作用域——任何JavaScript教程中非常重要的部分!前面我們看到的許多實例都創建在全局作用域。這意味着window(假設環境是瀏覽器)對象將有很多屬性。如果我們全部按照這種方式寫我們的JavaScript代碼,我們會迅速在全局作用域積累一噸(誇張)變量聲明,window代碼會被污染。即使在最好的情況下,在全局變量暴漏許多細節是糟糕的建議,但當變量的名字和已經存在的window屬性名字相同時會發生什麼呢?window屬性會被重寫!

例如,如果你最喜歡的“Amelia Earhart”網站在全局作用域聲明瞭一個navigator變量,下面是設置之前和之後的結果:

哎呀!

顯而易見——全局變量被污染是糟糕的。JavaScript使用函數作用域(而不是塊作用域,如果你來自C#或Java,這點非常重要!),所以保持我們的代碼和全局作用域分離的辦法是創建一個新作用域,我們可以使用IIFE來實現,因爲它的內容在它自己的函數作用域內。在下面的例子中,我將在控制檯向你顯示window.navigator的值,然後我常見一個IIFE(立即執行函數表達式)去包裹Amelia Earhart的行爲和數據。IIFE結束後返回一個作爲我們的“程序命名空間”的對象。我在IIFE內聲明的navigator變量將不會重寫window.navigator的值。

作爲額外好處,我們上面創建的IIFE是JavaScript中模塊模式的啓蒙。我將在結尾處包括一些我瀏覽的模塊模式的鏈接。

7、‘typeof’操作符和’Object.prototype.toString’

最終,可能發現在某些情況下,你需要檢查傳遞給函數參數的類型,或其他類似的東西。typeof運算符會是顯而易見的選擇,但是,這並不是萬能的。例如,當我們對一個對象,數組,字符串或正則表達式,調用typeof運算符時會發生什麼?

還好——至少我們可以將字符串和對象,數組,正則表達式區分開,對嗎?幸運的是,我們可以得到更準確的類型信息,我們有其他不同的方法。我們將使用Object.prototype.toString方法,並且應用我們前面提到的call方法:

爲什麼我們要使用Object.prototype上的toString方法?因爲第三方庫或你自己的代碼可能重寫實例的toString方法。通過Object.prototype,我們可以強制實現實例原來的toString行爲。

如果你知道typeof將會返回什麼那麼你不需要進行多餘的檢查(例如,你僅需要知道是或不是一個字符串),此時用typeof非常好。然而,如果你需要區分數組和對象,正則表達式和對象,等等,那麼使用Object.prototype.toString吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章