29個合格前端工程師需要掌握的JavaScript 技能
前言
如果你走過了前端的入門初級階段,那么接下來就是向中高級進階,當然,關于這個初中高級的分界線,也沒有一個標準固定的指標,但是,不管怎么樣,努力讓自己變得強,是每個技術人的底氣。
我們其他不多說,我們現在就開今天的內容吧。
另外,就是今天文章中代碼對應的詳細注釋和具體使用方法放在我的GitHub上,源碼可以在底部找到,希望這對你的工作和面試有所幫助。
1.判斷對象的數據類型
使用 Object.prototype.toString 配合閉包,通過傳入不同的判斷類型來返回不同的判斷函數,一行代碼,簡潔優雅靈活(注意傳入 type 參數時首字母大寫)
不推薦將這個函數用來檢測可能會產生包裝類型的基本數據類型上,因為 call 始終會將第一個參數進行裝箱操作,導致基本類型和包裝類型無法區分。
2.循環實現數組map方法
使用方法:將selfMap注入到Array.prototype中(下面數組的迭代方法也是如此)。
值得一提的是,map的第二個參數就是第一個參數回調中的這個點。如果第一個參數是箭頭函數,則設置第二個 this 將無效,因為箭頭函數的詞法綁定。
另一個是稀疏數組的處理,HasOwnProperty 用于判斷當前下標的元素是否存在于數組中,歡迎大家在評論區交流。
3.使用reduce實現數組map方法
4.循環實現數組filter方法
5.使用reduce實現數組filter方法
6.循環實現數組的some方法
如果執行某個方法的數組是一個空數組,它總是返回false,如果另一個數組的每個方法中的數組都是一個空數組,它總是返回true。
7.循環實現數組的reduce方法
因為可能存在稀疏數組關系,reduce需要保證跳過稀疏元素,遍歷正確的元素和下標。
8.使用reduce實現數組的flat方法
因為selfFlat依賴這個指向,所以在減少遍歷的時候需要指定這個指向selfFlat,否則會默認指向一個窗口,會報錯。
原理是通過歸約來遍歷數組。當數組的一個元素還是數組的時候,通過ES6展開操作符(ES5可以使用concat方法)對其進行降維處理,而這個數組元素內部也可能有嵌套數組,所以,需要遞歸調用selfFlat。
同時,原生的flat方法支持一個depth參數來表示降維的深度。默認值為 1,將數組的維度減少一層。
傳入 Infinity 會將傳入的數組變成一維數組。
原理是每次遞歸將深度參數減1。如果depth參數為0,直接返回原數組。
9. 實現 ES6 的Class語法
ES6的Class內部是基于寄生組合式繼承,是目前最理想的繼承方式。通過 Object.create() 方法創建一個空對象,并從 Object.create() 方法的參數中繼承該空對象,然后,讓子類(subType)如果原型對象等于這個空對象,則子類實例的prototype可以實現等于這個空對象,這個空對象的原型就等于父類原型對象(superType.prototype)的繼承關系。
Object.create() 支持第二個參數,即為生成的空對象定義屬性和屬性描述符/訪問器描述符。我們可以為這個空對象定義一個構造函數屬性,更符合默認的繼承行為,不可枚舉,可枚舉的內部屬性(可枚舉:false)。
ES6類允許子類繼承父類的靜態方法和靜態屬性,而普通的寄生組合繼承只能實現實例之間的繼承。對于類之間的繼承,需要定義額外的方法。這里使用 Object.setPrototypeOf() 將 superType 設置為 subType 的原型,以便能夠從父類繼承靜態方法和靜態屬性。
10.函數柯里化
用法:
柯里化是函數式編程中的一項重要技術,該技術將一個接受多個參數的函數轉換為一系列接受一個參數的函數。
函數式編程的另一個重要功能,compose,可以組合函數,而組合函數只接受一個參數,所以,如果需要接受多個函數并且需要使用compose進行函數組合,則需要對函數使用currying 要組成的部分被評估,因此它總是只需要一個參數。
讓我們看另一個例子:
11.函數柯里化(支持占位符)
用法:
使用占位符可以使柯里化更加靈活, 這個想法是用每一輪的傳入參數填充上一輪的占位符。 如果當前輪的參數包含一個占位符,它們將被放置在內部存儲的數組中。 最后,當前輪的元素不會填充當前輪參數的占位符,只會填充之前傳入的占位符。
12. 部分函數
用法:
偏函數的概念和柯里化類似。 我個人認為它們的區別在于偏函數會固定傳入的幾個參數,然后,一次性接受剩下的參數,而函數柯里化會根據傳入函數的參數不斷返回,直到參數個數在被柯里化之前滿足函數的參數數量。
Function.prototype.bind 函數是偏函數的典型代表。 它接受的第二個參數以預先添加到綁定函數的參數列表中的參數開始。 與bind不同的是,上述函數還支持記帳位字符。
13. 斐波那契數列及其優化
使用函數內存來保存之前運算的結果,可以為頻繁依賴之前結果的計算節省大量時間,比如斐波那契數列, 缺點是閉包中的obj對象會占用額外的內存。
另外,使用動態規劃的空間復雜度比前者低,也是比較推薦的方案。
14.實現函數bind方法
實現函數bind方法的核心是使用調用綁定this指針,同時,考慮到其他一些情況,比如:
? 當bind 返回的函數被new 調用構造函數時,綁定的值將失效并變為new 指定的對象。
? 定義綁定函數的長度屬性和名稱屬性(不可枚舉的屬性)。
? 綁定函數的原型需要指向原函數的原型(實際情況下,綁定函數沒有原型。而是綁定函數中有一個內部屬性[[TargetFunction]]來保存原函數。當final函數作為構造函數時,創建實例的__proto__指向[[TargetFunction]]的原型,這里無法模擬內部屬性,所以直接聲明一個原型屬性)。
15.實現函數call方法
原理是將函數作為傳入的上下文參數(context)的一個屬性來執行。 這里使用 ES6 Symbol 類型來防止屬性沖突。
16.簡單的CO模塊
用法:
run函數接受一個生成器函數,只要run函數包裹的生成器函數遇到yield關鍵字就停止。
當后面的yield的promise成功resolve后,會自動調用next方法執行到下一個yield關鍵字,最終,只要一個promise成功resolve,就會resolve下一個promise。
當所有解析成功后,打印所有解析的結果,演變成現在最常用的async/await語法
17. 去抖動
Leading進入時是否執行一次,原理是使用的定時器。 如果在指定時間內再次觸發事件,則清除最后一個定時器,即不執行該函數,并重新設置一個新的定時器,直到超過指定時間, 時間自動觸發定時器中的功能。
同時,通過閉包暴露了一個取消函數,使得外部可以直接清除內部計數器。
18. 節流
與防抖功能類似,不同之處在于額外使用內部時間戳作為判斷,如果在一段時間內沒有觸發事件,則允許觸發下一個事件。 同時增加了一個尾隨選項,表示最后是否觸發附加時間。
19. 圖像延遲加載
getBoundClientRect 的實現方法是監聽滾動事件(建議在監聽事件中加入節流),圖片加載后會從img標簽組成的DOM列表中刪除,最后需要解除所有圖片的綁定加載監視器事件后。
實現一個intersectionObserver,實例化IntersectionObserver,讓它觀察所有img標簽。
當img標簽進入可見區域時,執行實例化回調,并傳入一個entry參數給回調,其中保存了實例觀察到的所有元素的一些狀態,比如,每個元素的邊界信息,DOM節點 對應于當前元素,當前元素進入可見區域的速率。
每當元素進入可見區域時,將真實圖像分配給當前的img標簽并釋放其觀察。
20. new 關鍵字
21. 實現 Object.assign
22.實現instanceof
原理是遞歸遍歷右參數的原型鏈,每次與左參數比較,遍歷到原型鏈末端返回false,找到返回true。
23.私有變量的實現
使用Proxy代理所有以_開頭的變量,使其無法被外部訪問。
以閉包的形式保存私有變量,缺點是類的所有實例都訪問同一個私有變量。
閉包的另一種實現解決了上述閉包的缺點,每個實例都有自己的私有變量。 缺點是拋棄了類語法的簡單性,拋棄了所有特權方法(訪問私有變量的方法), 存儲在構造函數中。
通過WeakMap和閉包,在每次實例化時保存當前實例和所有私有變量組成的對象,而閉包中的WeakMap無法從外部訪問。 使用 WeakMap 的好處是當實例沒有變量引用時,會自動釋放, 實例保存的私有變量,減少內存溢出問題。
24. 洗牌算法
早期的chrome對少于10個元素的數組使用了插入排序,這樣會導致數組亂序,并不是真的亂序,即使最新版的chrome使用了in-place算法,使得排序成為一種穩定的算法, 亂序問題仍未解決。
真正的無序可以通過shuffle算法來實現, 洗牌算法分為原位和非原位。 圖1是原位改組算法,無需聲明額外的數組來節省內存使用。 原則是按順序遍歷。 數組的元素,隨機選擇當前元素和所有后續元素中的一個,進行交換。
25. 單例模式
ES6的Proxy實現的單例模式攔截構造函數的執行方法。
26.Promisify
用法:
promisify 函數是將回調函數變成promise的輔助函數,適用于錯誤優先風格(nodejs)的回調函數。
原理是無論error-first風格的回調成功還是失敗,執行完后都會執行最后一個回調函數。 我們需要做的就是讓這個回調函數控制 Promise 的狀態。
這里也使用proxy來代理整個fs模塊,攔截get方法,這樣就不用手動把fs模塊的所有方法都用promisify函數包裹起來,比較靈活。
27. 優雅地處理 async/await
用法:
不需要每次使用 async/await 時都包裹一層 try/catch,更加優雅。 這是另一個想法。 如果使用webpack,可以寫一個loader,分析AST語法樹,遇到await語法時自動注入try/catch, 這樣你甚至不需要使用輔助函數。
28. EventEmitter
on 方法用于注冊事件,trigger 方法觸發事件實現事件之間的松散解耦,并增加了額外的once and off 輔助功能來注冊僅觸發一次的事件和注銷事件。
29. 實現 JSON.stringify
使用 JSON.stringify 將對象轉換為 JSON 字符串時,一些非法數據類型會被扭曲,主要如下:
? 如果對象包含 toJSON 方法,將調用 toJSON。
? Array
1.有Undefined/Symbol/Function數據類型時會變為null。
2. Infinity/NaN 的存在也將變為 null。
? Object
1.當屬性值為Undefined/Symbol/Function數據類型時,屬性和值都不會轉為字符串。
2.如果屬性值為Infinity/NaN,屬性值會變為null。
? 日期數據類型值調用 toISOString。
? 不是數組/對象/函數/日期的復雜數據類型變為空對象。
? 循環引用引發錯誤。
此外,JSON.stringify 還可以傳入第二個和第三個可選參數,有興趣的朋友可以詳細了解一下。
實現代碼比較長,這里我直接貼上對應的源碼地址 JSON.stringify:https://github.com/yeyan1996/practical-javascript/blob/master/json.js
文章中源代碼地址:
https://github.com/yeyan1996/practical-javascript
總結
以上就是今天我跟你分享的29個關于JavaScript的技能,如果你覺得有用的話,請記得點贊我,關注我,并將它分享給你身邊的朋友,也許能夠幫助到他。
最后,感謝你的閱讀,祝編程愉快!