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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

call() 方法的作用和 apply() 方法類似,區(qū)別就是 call() 方法接受的是參數(shù)列表,而 apply() 方法接受的是一個(gè)參數(shù)數(shù)組。
bind
bind() 方法創(chuàng)建一個(gè)新的函數(shù),在 bind() 被調(diào)用時(shí),這個(gè)新函數(shù)的 this 被指定為 bind() 的第一個(gè)參數(shù),而其余參數(shù)將作為新函數(shù)的參數(shù),供調(diào)用時(shí)使用。
實(shí)現(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è)務(wù)開發(fā)中繞不開的技巧,對(duì)于深淺拷貝數(shù)據(jù)之前寫過一篇文章來(lái)講述聊聊深拷貝淺拷貝。
通過深度優(yōu)先思維拷貝數(shù)據(jù)(DFS)
深度優(yōu)先是通過縱向的維度去思考問題,在處理過程中也考慮到對(duì)象環(huán)的問題。
解決對(duì)象環(huán)的核心思路是先存再拷貝。一開始先通過一個(gè)容器用來(lái)儲(chǔ)存原來(lái)的對(duì)象再進(jìn)行拷貝,在每一次拷貝之前去查找容器里是否已存在該對(duì)象。這樣就切斷了原來(lái)的對(duì)象和拷貝對(duì)象的聯(lián)系。

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

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

Promsie.race

Promsie.finally

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

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

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

17.實(shí)現(xiàn) Object.create
Object.create() 方法創(chuàng)建一個(gè)新對(duì)象,使用現(xiàn)有的對(duì)象來(lái)提供新創(chuàng)建的對(duì)象的__proto__。

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

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

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

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

fn 不是一個(gè)返回 Promsie 的話那就包一層:

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

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

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

如上是掛在到原型上的方法和每個(gè)實(shí)例獨(dú)有的方法不同寫法。它們有什么區(qū)別呢?(ps: 可以手動(dòng)打?。?/p>
調(diào)用原型上的方法那么私有變量的值是與最近一個(gè)實(shí)例調(diào)用原型方法的值。其上一個(gè)實(shí)例的值也是隨之改變的,那么就出現(xiàn)問題了...
而使用 WeakMap 可以解決如上的問題:做到將方法掛在到原型,且不同時(shí)期同一個(gè)實(shí)例調(diào)用所產(chǎn)生的結(jié)果是一致的。
總結(jié)
我一直認(rèn)為有輸入就得有輸出,那總結(jié)就是最好的輸出方式了。因此有了一篇這樣的文章,希望讀者能靜下來(lái)去手寫并理解 code 的思路和運(yùn)行過程,我想也會(huì)對(duì) js 有更深入的理解。(ps: 可以一起探討)
如上的總結(jié),如有新的內(nèi)容也會(huì)持續(xù)更新...
參考資料
一個(gè)合格的中級(jí)前端工程師需要掌握的 28 個(gè) JavaScript 技巧
