前端進階必會的22個JavaScript技巧總結(jié)
前言
關(guān)于技術(shù),只有不停重復學習,方能如扎如穩(wěn)的前行。
1.函數(shù)柯里化
函數(shù)柯里化的是一個為多參函數(shù)實現(xiàn)遞歸降解的方式。其實現(xiàn)的核心是:
要思考如何緩存每一次傳入的參數(shù)
傳入的參數(shù)和目標函數(shù)的入?yún)⒆霰容^
這里通過閉包的方式緩存參數(shù),實現(xiàn)如下:

使用方式如下:
函數(shù)柯里化僅僅只是上面求和的這種運用嗎??
??這個問題,有必要去??一下。其實利用函數(shù)柯里化這種思想,我們可以更好的實現(xiàn)函數(shù)的封裝。
就比如有監(jiān)聽某一事件那么就會有移除該事件的操作,那么就可以利用柯里化的思想去封裝代碼了。
或者說一個輸入 A 有唯一并且對應的輸出 B,那么從更大的角度去思想這樣的工程項目是更安全,獨立的。也便于去維護。
2.關(guān)于數(shù)組
手寫 map 方法
map() 方法根據(jù)回調(diào)函數(shù)映射一個新數(shù)組

手寫 filter 方法
filter() 方法返回一個數(shù)組,返回的每一項是在回調(diào)函數(shù)中執(zhí)行結(jié)果 true。

filter 和 map 的區(qū)別:filter 是映射出條件為 true 的 item,map 是映射每一個 item。
手寫 reduce 方法
reduce() 方法循環(huán)迭代,回調(diào)函數(shù)的結(jié)果都會作為下一次的形參的第一個參數(shù)。

手寫 every 方法
every() 方法測試一個數(shù)組內(nèi)的所有元素是否都能通過某個指定函數(shù)的測試。它返回一個布爾值。

手寫 some 方法
some() 方法測試數(shù)組中是不是至少有 1 個元素通過了被提供的函數(shù)測試。它返回的是一個 Boolean 類型的值。

手寫 find 方法
find() 方法返回數(shù)組中滿足提供的測試函數(shù)的第一個元素的值。否則返回 undefined。

拉平數(shù)組
將嵌套的數(shù)組扁平化,在處理業(yè)務數(shù)據(jù)場景中是頻率出現(xiàn)比較高的。那如何實現(xiàn)呢?
利用 ES6 語法 flat(num) 方法將數(shù)組拉平。
該方法不傳參數(shù)默認只會拉平一層,如果想拉平多層嵌套的數(shù)組,需要傳入一個整數(shù),表示要拉平的層級。該返回返回一個新的數(shù)組,對原數(shù)組沒有影響。

利用 reduce() 方法將數(shù)組拉平。
利用 reduce 進行迭代,核心的思想是遞歸實現(xiàn)。

模擬棧實現(xiàn)數(shù)組拉平
該方法是模擬棧,在性能上相對最優(yōu)解。

3.圖片懶加載 & 惰性函數(shù)
實現(xiàn)圖片懶加載其核心的思想就是將 img 的 src 屬性先使用一張本地占位符,或者為空。然后真實的圖片路徑再定義一個 data-set 屬性存起來,待達到一定條件的時將 data-img 的屬性值賦給 src。
如下是通過scroll滾動事件監(jiān)聽來實現(xiàn)的圖片懶加載,當圖片都加載完畢移除事件監(jiān)聽,并且將移除 html 標簽。

scroll滾動事件容易造成性能問題。那可以通過 IntersectionObserver 自動觀察 img 標簽是否進入可視區(qū)域。
實例化 IntersectionObserver 實例,接受兩個參數(shù):callback 是可見性變化時的回調(diào)函數(shù),option 是配置對象(該參數(shù)可選)。
當 img 標簽進入可視區(qū)域時會執(zhí)行實例化時的回調(diào),同時給回調(diào)傳入一個 entries 參數(shù),保存著實例觀察的所有元素的一些狀態(tài),比如每個元素的邊界信息,當前元素對應的 DOM 節(jié)點,當前元素進入可視區(qū)域的比率,每當一個元素進入可視區(qū)域,將真正的圖片賦值給當前 img 標簽,同時解除對其的觀察。

如上是懶加載圖片的實現(xiàn)方式。
值得思考的是,懶加載和惰性函數(shù)有什么不一樣嘛?
我所理解的懶加載顧名思義就是需要了才去加載,懶加載正是惰性的一種,但惰性函數(shù)不僅僅是懶加載,它還可以包含另外一種方向。
惰性函數(shù)的另一種方向是在重寫函數(shù),每一次調(diào)用函數(shù)的時候無需在做一些條件的判斷,判斷條件在初始化的時候執(zhí)行一次就好了,即下次在同樣的條件語句不需要再次判斷了,比如在事件監(jiān)聽上的兼容。
4.預加載
預加載顧名思義就是提前加載,比如提前加載圖片。

當用戶需要查看時,可直接從本地緩存中取。預加載的優(yōu)點在于如果一張圖片過大,那么請求加載圖片一定會慢,頁面會出現(xiàn)空白的現(xiàn)象,用戶體驗感就變差了,為了提高用戶體驗,先提前加載圖片到本地緩存,當用戶一打開頁面時就會看到圖片。
5.節(jié)流 & 防抖
針對高頻的觸發(fā)的函數(shù),我們一般都會思考通過節(jié)流或者防抖去實現(xiàn)性能上的優(yōu)化。
節(jié)流實現(xiàn)原理是通過定時器以和時間差做判斷。定時器有延遲的能力,事件一開始不會立即執(zhí)行,事件結(jié)束后還會再執(zhí)行一次;而時間差事件一開始就立即執(zhí)行,時間結(jié)束之后也會立即停止。
結(jié)合兩者的特性封裝節(jié)流函數(shù):

函數(shù)節(jié)流不管事件觸發(fā)有多頻繁,都會保證在規(guī)定時間內(nèi)一定會執(zhí)行一次真正的事件處理函數(shù)。
防抖實現(xiàn)原理是通過定時器,如果在規(guī)定時間內(nèi)再次觸發(fā)事件會將上次的定時器清除,即不會執(zhí)行函數(shù)并重新設置一個新的定時器,直到超過規(guī)定時間自動觸發(fā)定時器中的函數(shù)。

6.實現(xiàn) new 關(guān)鍵字

7.實現(xiàn) instanceof
instanceof 運算符用于檢測構(gòu)造函數(shù)的 prototype 屬性是否出現(xiàn)在某個實例對象的原型鏈上。

8.實現(xiàn) call,apply,bind
call
call 函數(shù)實現(xiàn)的原理是借用方法,關(guān)鍵在于隱式改變this的指向。

apply
apply 函數(shù)實現(xiàn)的原理和 call 是相同的,關(guān)鍵在于參數(shù)的處理和判斷。

call() 方法的作用和 apply() 方法類似,區(qū)別就是 call() 方法接受的是參數(shù)列表,而 apply() 方法接受的是一個參數(shù)數(shù)組。
bind
bind() 方法創(chuàng)建一個新的函數(shù),在 bind() 被調(diào)用時,這個新函數(shù)的 this 被指定為 bind() 的第一個參數(shù),而其余參數(shù)將作為新函數(shù)的參數(shù),供調(diào)用時使用。
實現(xiàn)的關(guān)鍵思路:
拷貝保存原函數(shù),新函數(shù)和原函數(shù)原型鏈接
生成新的函數(shù),在新函數(shù)里調(diào)用原函數(shù)

9.封裝數(shù)據(jù)類型函數(shù)

10.自記憶函數(shù)

11.是否存在循環(huán)引用

12.拷貝函數(shù)
拷貝數(shù)據(jù)一直是業(yè)務開發(fā)中繞不開的技巧,對于深淺拷貝數(shù)據(jù)之前寫過一篇文章來講述聊聊深拷貝淺拷貝。
通過深度優(yōu)先思維拷貝數(shù)據(jù)(DFS)
深度優(yōu)先是通過縱向的維度去思考問題,在處理過程中也考慮到對象環(huán)的問題。
解決對象環(huán)的核心思路是先存再拷貝。一開始先通過一個容器用來儲存原來的對象再進行拷貝,在每一次拷貝之前去查找容器里是否已存在該對象。這樣就切斷了原來的對象和拷貝對象的聯(lián)系。

通過廣度優(yōu)先思維拷貝數(shù)據(jù)(BFS)
廣度優(yōu)先是通過橫向的維度去思考問題,通過創(chuàng)造源隊列和拷貝數(shù)組隊列之間的關(guān)系實現(xiàn)拷貝。

13.Promise 系列
之前寫過一篇關(guān)于 Promise 的學習分享。
Promsie.all

Promsie.race

Promsie.finally

14.實現(xiàn) async-await

15.實現(xiàn)簡易訂閱 - 發(fā)布

16.單例模式
單例模式:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。實現(xiàn)方法一般是先判斷實例是否存在,如果存在直接返回,如果不存在就先創(chuàng)建再返回。

17.實現(xiàn) Object.create
Object.create() 方法創(chuàng)建一個新對象,使用現(xiàn)有的對象來提供新創(chuàng)建的對象的__proto__。

該方法是實現(xiàn)了已有對象和新建對象的原型是一個淺拷貝的過程。
18.實現(xiàn) ES6 的 class 語法

使用 Object.create() 方法將子類的實例對象繼承與父類的原型對象,通過 Object.setPrototypeOf() 能夠?qū)崿F(xiàn)從父類中繼承靜態(tài)方法和靜態(tài)屬性。
19.實現(xiàn)一個 compose 函數(shù)
compose 函數(shù)是用來組合合并函數(shù),最后輸出值的思想。在 redux 源碼中用于中間件的處理。
使用 while 循環(huán)實現(xiàn)

使用 reduce 迭代實現(xiàn)

20.實現(xiàn)異步并行函數(shù)
fn 是一個返回 Promise 的函數(shù)才可使用下面的函數(shù):

fn 不是一個返回 Promsie 的話那就包一層:

21.實現(xiàn)異步串行函數(shù)

22.私有變量的實現(xiàn)

以上是 es5 實現(xiàn)的私有變量的封裝,通過使用 WeakMap 可以擴展每個實例所對應的私有屬性,私有屬性在外部無法被訪問,而且隨 this 對象的銷毀和消失。
這里有個小細節(jié)值得一提, 請看如下的代碼:

如上是掛在到原型上的方法和每個實例獨有的方法不同寫法。它們有什么區(qū)別呢?(ps: 可以手動打印)
調(diào)用原型上的方法那么私有變量的值是與最近一個實例調(diào)用原型方法的值。其上一個實例的值也是隨之改變的,那么就出現(xiàn)問題了...
而使用 WeakMap 可以解決如上的問題:做到將方法掛在到原型,且不同時期同一個實例調(diào)用所產(chǎn)生的結(jié)果是一致的。
源代碼
javascript--,歡迎 star
總結(jié)
我一直認為有輸入就得有輸出,那總結(jié)就是最好的輸出方式了。因此有了一篇這樣的文章,希望讀者能靜下來去手寫并理解 code 的思路和運行過程,我想也會對 js 有更深入的理解。(ps: 可以一起探討)
如上的總結(jié),如有新的內(nèi)容也會持續(xù)更新...
參考資料
一個合格的中級前端工程師需要掌握的 28 個 JavaScript 技巧
MDN
更多優(yōu)質(zhì)原創(chuàng)內(nèi)容請點擊“閱讀原文”了解~
關(guān)注公眾號后可以加入前端技術(shù)社群哦~
