忍者級別的操作JavaScript函數

  • 2019 年 10 月 10 日
  • 筆記

者級別的操作JavaScript函數

從名字即可看書,此篇部落格總結與《JavaScript忍者秘籍》。對於JavaScript來說,函數為第一類型對象。所以這裡,我們主要是介紹JavaScript中函數的運用。 點擊原文地址,跳轉至github原文地址

匿名函數

對於什麼是匿名函數,這裡就不做過多介紹了。我們需要知道的是,對於JavaScript而言,匿名函數是一個很重要且具有邏輯性的特性。通常,匿名函數的使用情況是:創建一個供以後使用的函數。

簡單的舉個例子如下:

上面的一個程式碼片段我就不做過多無用解釋了,比較常規。

遞歸

遞歸,說白了,就是自己調用自己,或者調用另外一個函數,但是這個函數的調用樹的某一個地方又調用了自己。所以遞歸,就產生了。

普通命名函數的遞歸

拿普通命名函數的遞歸最好的舉例就是用最簡單的遞歸需求:檢測迴文。

迴文的定義如下:一個短語,不管從哪一個方向讀,都是一樣的。檢測的工作當然方法多樣,我們可以創建一個函數,用待檢測的迴文字元逆序生成出一個字元,然後檢測二者是否相同,如果相同,則為迴文字元。

但是這種方法並不是很有逼格,確切的說,代價比較大,因為我們需要分配並創建新的字元。

所以,我們可以整理出如下簡潔的辦法:

  • 單個和零個字元都是迴文
  • 如果字元串的第一個字元和最後一個字元相同,並且除了兩個字元以外,別的字元也滿足該要求,那麼我們就可以檢測出來了這個是迴文了

上面的程式碼我們並沒有做txt的一些類型檢測,undefined、null等。

方法中的遞歸

所謂的方法,自然離不開對象,直接看例子:

在上述程式碼中,我們通過對象ninja.chirp方法的遞歸調用了自己。但是,因為我們在函數上s會用了非直接引用,也就是ninja對象的chirp屬性,所以才能夠實現遞歸,這也就引出來一個問題:引用丟失

引用丟失的問題

上面的示例程式碼,依賴於一個進行遞歸調用的對象屬性引用。與函數的實際名稱不同,因為這種引用可能是暫時的。

如上,我們把ninja屬性上的方法賦值給了samurai,然後置空ninja,然後你懂得~這就是引用丟失的問題。

截圖自《JavaScript忍者秘籍》

通過完善之前對匿名函數的粗略定義,我們可以修復解決這個問題。在匿名函數中,我們不在使用顯示的ninja引用。這裡我們使用this(關於this的使用詳解,請關注我的個人微信公眾號:前端的全棧之路)。

當函數作為方法被調用的時候,函數的上下文指的是該方法的對象。

使用this調用,可以讓我們的匿名函數更加的強大且靈活。但是。。。

內聯命名函數

上面我們解決了作為函數方法作為遞歸時候的一個完美操作。但實際上,不管是否進行方法遞歸,巧妙使用this都是我們應該所掌握的(關注微信公眾號,早晚都給你說到)。

話說回來,其實這樣寫也還是有問題的,問題在於給對象定義方法的時候,方法名稱是寫死的,如果屬性名稱不一樣,豈不是一樣會丟失引用?

所以,這裡我們採用另一種解決方案,給匿名函數起個名字吧!對的,肯定又人會說,我擦!那還是匿名函數么?嗯。。。好吧,那就不叫匿名函數了吧,叫內聯函數~

所以如上的解決辦法,就完美解決了我們之前說到所有問題。內聯函數還有一個很重要的一點,就是儘管可以給內聯函數進行命名,但是這些名稱只能在自身函數內部才可見。

將函數視為對象

JavaScript中的函數和其他語言中的函數有所不同,JavaScript賦予了函數很多的特性,其中最重要的特性之一就是函數作為第一類型對象。是的,對象!

所以,我們可以給函數添加屬性,甚至可以添加方法。

函數存儲

有時候,我們可能需要存儲一組相關但又獨立的函數,事件回調管理是最為明顯的例子。向這個集合添加函數時候,我們得知道哪些函數在集合中存在,否則不添加。

上述程式碼比較簡單常規,也就不做過多解釋。

自記憶函數

快取記憶是構造函數的過程,這種函數能夠記住先前計算的結果。通過避免重複的計算,極大地提高性能。

快取記憶昂貴的計算結果

作為一個簡單的例子,這裡我來判斷一個數字是否為素數。

如上程式碼也都是常規操作,不做過多解釋。我們可以通過下面的console.log判斷出快取是否成功。

快取記憶有兩個主要的優點:

  • 在函數調用獲取之前計算結果的時候,最終用戶享有性能優勢
  • 發生在幕後,完全無縫,最終用戶和開發者都無需任何特殊的操作或者為此做任何初始化工作。

當然,總歸會有缺點的

  • 為了提高性能,任何類型的快取肯定會犧牲記憶體
  • 純粹主義者可能認為快取這個問題不應該與業務邏輯放到一起。一個函數或者方法只應該做一件事。
  • 很難測試和測量一個演算法的性能。(比如我們這個「簡單」的例子)

快取DOM記憶

通過元素標籤名來獲取DOM元素是一個非常常見的操作。但是性能可能不是特別好。所以從上面的快取記憶我們可以進行如下的騷操作:

上面的程式碼很簡單,但是有么有眼前一亮的感覺呢??我有!而且我們還發現,這個簡單的快取的程式碼產生了5倍以上的性能提升。

我們可以將狀態和快取資訊存儲在一個封裝的獨立位置上,不僅在程式碼組織上有好處,而且外部存儲或快取對象無需污染作用域,就可以獲取性能的提升。

別激動,下面還有更多的奇淫技巧~

偽造數組方法

有時候我們想創建一個包含一組數據的對象。如果只是集合,則只需要創建一個數組即可。但是在某些情況下,除了集合本身,可能會有更多的狀體需要保存。

一種選擇是,每次創建對象新版本的時候都創建一個新數組,然後將元數據作為屬性或者方法添加到這個新數組上。但是這個操作太常規了。

欣賞如下騷操作:

通常,Array.prototype.push()是通過其函數上下文操作其自身數組的。這裡我們通過call方法來講我們自己的對象扮演了一次他的上下文。push的方法會增加length的值(會認為他就是數組的length屬性),然後給對象添加一個數字屬性,並將其引用到傳入的元素上。

關於函數的執行上下文,以及prototype的一些說明,將在後續文章寫到。

可變函數的參數列表

JavaScript靈活且強大的特性之一是函數可以接受任意數量的參數。雖然JavaScript沒有函數的重載,但是參數列表的靈活性是獲取其他語言類似重載功能的關鍵所在

使用apply()支援可變參數

需求:查找數組中的最大值、最小值

一開始,我認為Math中提供的min(),max()可以滿足,但是貌似他並不能夠找到數組中的最大值最小值,難道要我這樣:Math.min(arr[0],arr[1],arr[3]…)??

來吧,我們繼續我們的奇淫技巧。

不做過多解釋,操作常規,是不是又是一個眼前一亮呢?

函數重載

之前我們有介紹過函數的隱士傳遞,arguments,也正是因為這個arguments的存在,才讓函數有能力處理不同數量的參數。即使我們只定義固定數量的形參,通過arguments參數我們還是可以訪問到實際傳給函數的所有的參數。

檢測並遍歷參數

方法的重載通常是通過在同名的方法里聲明不同的實例來達到目的。但是在javascript中並非如此,在javaScript中,我們重載函數的時候只有一個實現。只不過這個實現內部是通過函數實際傳入的參數的特性和個數來達到相應目的的。

通過如上程式碼,我們將傳遞給函數的對象都合併到一個對象中。在javascript中,沒有強制函數聲明多少個參數就得穿入多少個參數。函數是否可以成功處理這些參數,完全取決於函數本身的定義。

注意,我們要做的事情是想讓第二個或者第n個參數上的屬性合併到第一個對象中,所以這個遍歷是從1開始的。

利用參數個數進行函數的重載

基於函數的參數,有很多種辦法進行函數的重載。一種通用的方法是,根據傳入參數的類型執行不同的操作。另一種辦法是,可以通過某些特定參數是否存在來進行判斷。還有一種是通過傳入參數個數來進行判斷。

假如對象上有一個方法,根據傳入參數的個數來執行不同的操作,冗長且獃獃的函數應該張這樣:

這種方式,看起來非常的獃獃的。所以我們換一種方式來說下。

如果按照如下思路,添加重載的方法會怎樣呢。

這裡我們使用同樣的名稱(whatever)將方法添加到該對象上,只不過每個重載的函數是單獨的。注意每一個重載的函數參數是不同的。通過這種方式,我們真正為每一個重載都創建了一個獨立的匿名函數。漂亮且簡潔。

下面就讓我操刀來實現這個addMethod函數吧

這個操作我們這裡解釋一下,第一步,我們保存原有的函數,因為調用的時候可能不匹配傳入的參數個數。第二部創建一個新的匿名函數,如果該匿名函數的形參個數和實際個數匹配,就調用這個函數,否則調用原來的函數。

這裡的fn.length是返回函數定義時候定義的形參個數。

下面解釋下這個函數的執行吧。adMethod第一次調用將創建一個新的匿名函數傳入零個參數進行調用的時候將會調用這個fn函數。由於此時這個ninja是一個新的對象,所以不必擔心之前創建過的方法。

第二次調用addMethod的時候,首先將之前的同名函數保存到一個變數old中,然後將新創建的匿名函數作為方法。新方法首先檢查傳入的個數是否為1,如果是則調用新傳入的fn,如果不是,則調用舊的。重新調用該函數的時候將在此檢查參數個數是否為0

這種調用方式類似於剝洋蔥,每一層都檢查參數個數是否匹配。這裡的一個技巧是關於內部匿名函數是否合訪問到old和fn的。這個關於函數閉包的知識就在下一篇部落格講解(關注微信公眾號吧)

關於上面使用的閉包想關注的知識,將在下一篇部落格中,為大家總結。

然後使用如上的技巧的時候需要注意下面幾點:

  • 重載是適用於不同數量的參數,不區分類型、參數名稱或者其他東西
  • 這樣的重載方法會有一些函數調用的開銷。我們要考慮在高性能時的情況。

交流

歡迎關注個人微信公眾號,更多原創文章,一起學習一起進步