前端面試手寫題30道全收錄:從Promise到虛擬DOM一網打盡

前端手寫題作者: 美歷團隊

涵蓋JS基礎、異步編程、設計模式、DOM/瀏覽器、算法5大類30道前端手寫題,每題含核心思路與關鍵點,助你攻克手寫代碼關。

前端面試手寫題30道全收錄:從Promise到虛擬DOM一網打盡

背景介紹

我面了十幾家前端崗,從字節到拼多多,從快手到小紅書,手寫題是繞不過去的坎。有些人八股文背得溜,但一到手寫就慌了——因為手寫題考的不是記憶,是編碼能力和對原理的理解深度。我把面過的30道最高頻手寫題全整理出來了,分成JS基礎、異步編程、設計模式、DOM/瀏覽器、算法五大類,每道題都附上核心思路和關鍵點。建議你每道題都自己敲一遍,光看是學不會的。

一、JS基礎(10題)

1. 防抖(debounce)

核心思路:延遲執行,在延遲期間再次觸發則重新計時。關鍵點:用閉包保存timer,每次調用clearTimeout再重新setTimeout。面試常問:要不要首次立即執行?加個immediate參數,為true時第一次立即執行。還要注意this和參數的綁定,用apply或...args。

2. 節流(throttle)

核心思路:固定時間間隔內只執行一次。兩種實現:時間戳版(首次立即執行,最後不執行)和定時器版(首次不立即執行,最後會執行)。面試官會問:能不能兩者結合?用時間戳+定時器,首次立即執行,最後一次也執行。我在字節二面被要求手寫這個組合版。

3. 深拷貝(deepClone)

基礎版:遞歸+判斷類型。進階要處理:循環引用(用WeakMap緩存)、特殊對象(Date、RegExp、Map、Set)、Symbol作為key、函數(直接引用不拷貝)。面試官最愛追問循環引用怎麼處理——在遞歸時把已拷貝的對象存到WeakMap裡,遇到已存在的直接返回。

4. call/apply/bind

call:在context上臨時掛載函數,執行後刪除。apply:類似call,但參數是數組。bind:返回新函數,支持柯里化。bind的關鍵點:1)綁定this;2)保存預設參數;3)new調用時this指向實例而非綁定的context;4)原型鏈繼承。我在美團一面被要求完整實現bind,new的情況一定要處理。

5. new操作符

四個步驟:1)創建空對象;2)對象的__proto__指向構造函數的prototype;3)構造函數的this綁定到新對象並執行;4)如果構造函數返回對象則返回該對象,否則返回新對象。注意:返回值判斷——只有返回的是對象才用返回值,基本類型忽略。

6. instanceof

核心思路:沿著左邊對象的原型鏈往上找,看能不能找到右邊構造函數的prototype。用while循環,leftProto = leftProto.__proto__,直到為null。面試追問:能不能判斷基本類型?不能,instanceof只對引用類型有效。怎麼判斷基本類型?用typeof。

7. Object.create

核心思路:創建一個新對象,其__proto__指向傳入的proto參數。用臨時構造函數實現:function F(){},F.prototype = proto,return new F()。注意:proto必須為對象或null,否則拋TypeError。

8. 柯里化(curry)

核心思路:把多參數函數變成一系列單參數函數。用遞歸收集參數,當參數數量足夠時執行原函數。關鍵點:用fn.length獲取原函數參數個數,用閉包保存已收集的參數。面試常考:add(1)(2)(3)和add(1,2)(3)都要支持——參數收集用...args拼接。

9. 數組扁平化(flatten)

三種實現:1)遞歸reduce+concat;2)用棧模擬(迭代,性能更好);3)ES6的flat(Infinity)。面試官會追問:不用遞歸怎麼實現?用棧:每次pop一個元素,如果是數組則展開push回去,否則放到結果裡。還要注意能指定扁平化深度。

10. 數組去重

方案:1)Set(最簡單);2)filter+indexOf;3)reduce+includes;4)Map。面試追問:NaN怎麼去重?Set可以(NaN===NaN為false但Set認為相等),indexOf不行(NaN的indexOf是-1)。對象去重呢?用Map或JSON.stringify(有順序問題)。

二、異步編程(6題)

11. Promise

核心:三個狀態(pending/fulfilled/rejected),狀態不可逆。then方法返回新Promise實現鏈式調用。關鍵點:1)resolve/reject用setTimeout保證異步執行;2)then的onFulfilled/onRejected是微任務(用queueMicrotask);3)值穿透——then不傳參數時值往下傳;4)then中拋異常走下一個reject。我在阿里一面被要求手寫完整Promise,至少要實現then和catch。

12. Promise.all

核心思路:等所有Promise都fulfilled才fulfilled,有一個rejected就rejected。用計數器記錄完成數量,用數組按順序保存結果。注意:傳入的不是Promise要先包裝,空數組直接resolve。面試追問:結果順序怎麼保證?用索引而不是push。

13. Promise.race

核心思路:返回最先改變狀態的Promise結果。實現很簡單——遍歷所有Promise,誰先resolve/reject就返回誰。面試官會問:和Promise.any的區別?race是第一個完成(不管成功失敗),any是第一個成功。

14. Promise並發限制

核心思路:維護一個執行隊列,同時運行的任務不超過limit個。完成一個就補一個。實現方式:用計數器或用Promise+遞歸。這是高頻中的高頻——我在字節、快手、拼多多都被考過。關鍵點:1)任務完成回調裡要遞歸啟動下一個;2)所有任務完成要resolve總Promise;3)錯誤處理——某個失敗不影響其他任務。

15. async/await原理

本質是Generator+自動執行器的語法糖。Generator函數yield交出控制權,執行器調用next()恢復執行。手寫核心:用遞歸調用gen.next(),如果done為true則resolve,否則對value(Promise)調用then再遞歸。面試官會問:為什麼async函數返回Promise?因為執行器包裝了一層Promise。

16. 紅綠燈交替亮

題目:紅燈3秒→綠燈2秒→黃燈1秒,循環。用async/await最優雅:while(true){ await red(); await green(); await yellow(); }。每個燈用Promise+setTimeout實現。面試追問:不用async/await怎麼實現?用回調嵌套或Promise鏈式調用。

三、設計模式(5題)

17. 發布訂閱模式(EventEmitter)

核心:on註冊事件、emit觸發事件、off移除事件、once註冊一次性事件。用對象存儲事件名到回調數組的映射。關鍵點:1)once用wrapper函數包裝,執行後自動off;2)emit要處理沒有該事件的情況;3)參數傳遞用...args。這是最常考的設計模式手寫題,我在5家公司的面試中都遇到了。

18. 觀察者模式

和發布訂閱的區別:觀察者模式中Subject直接通知Observer,沒有中間事件總線;發布訂閱有EventBus解耦。手寫:Subject維護observer列表,notify時遍歷調用update方法。面試官會問:Vue的響應式是哪種?是觀察者模式——Dep(Subject)通知Watcher(Observer)。

19. 單例模式

核心:保證一個類只有一個實例。實現:1)閉包+靜態方法;2)ES6 class的static屬性;3)代理模式。面試常考:實現一個全局的Storage單例。關鍵點:getInstance方法判斷是否已創建,已創建直接返回。我在拼多多被問:懶漢式和餓漢式的區別?懶漢式是使用時才創建,餓漢式是類加載時就創建。

20. 策略模式

核心:定義一系列算法,把它們封裝成獨立的策略類,可以互相替換。經典場景:表單驗證——不同規則是不同策略,Validator類負責調用。手寫:策略對象+環境類。策略對象的每個方法對應一種規則,環境類接收策略名調用對應方法。好處:避免大量if-else,新增策略不影響已有代碼。

21. 代理模式

核心:為對象提供代理以控制訪問。常見代理類型:虛擬代理(圖片懶加載)、緩存代理(計算結果緩存)、保護代理(權限控制)。手寫緩存代理:創建代理函數,內部用Map/對象緩存結果,相同的參數直接返回緩存。面試官會追問:ES6 Proxy了解嗎?那是元編程層面的代理,更強大。

四、DOM/瀏覽器(5題)

22. 事件委託

核心思路:利用事件冒泡,在父元素上監聽事件,通過event.target判斷實際觸發的子元素。手寫:給ul添加click監聽,判斷target.tagName是否為LI。面試追問:如果LI裡有子元素怎麼辦?用closest方法向上查找。我在快手一面被要求手寫一個事件委託,還問了e.target和e.currentTarget的區別。

23. 圖片懶加載

方案一:監聽scroll事件+getBoundingClientRect判斷元素是否在視口內。方案二:IntersectionObserver(推薦)。手寫IntersectionObserver版:new IntersectionObserver,當isIntersecting為true時把data-src賦值給src並取消觀察。面試官會問:scroll版怎麼優化?加節流+requestAnimationFrame。

24. 模板引擎

核心思路:把模板字符串中的<%=xxx%>替換為數據。用正則/<%=(.*?)%>/g匹配,replace時從data中取值。進階:支持if/for等邏輯——用Function構造函數動態生成渲染函數。面試官會問:XSS怎麼防?對輸出做HTML轉義,轉義<>&"'等字符。

25. 路由(hash路由)

核心:監聽hashchange事件,根據hash渲染對應組件。手寫:1)Route類存path和component;2)Router類管理routes,有add/go/init方法;3)init時監聽hashchange,匹配route渲染組件。面試追問:history路由怎麼實現?用pushState+popstate事件,需要後端配合。

26. JSONP

核心:利用script標籤不受同源策略限制的特點。手寫:1)創建script標籤,src為接口地址+callback參數;2)在window上掛載callback函數;3)服務端返回callback(data);4)前端callback函數接收數據;5)清理script和callback。注意:只支持GET,有XSS風險。

五、算法(4題)

27. LRU緩存

核心數據結構:Map(保持插入順序)或雙向鏈表+HashMap。Map版最簡潔:get時先delete再set(移到末尾),put時如果超容量就delete第一個key。雙向鏈表版更經典:get/put時把節點移到頭部,淘汰尾部節點。我在字節三面被要求手寫雙向鏈表版,要定義Node類和LRUCache類。

28. 數組扁平化(遞歸/迭代)

這個和第9題的區別是面試官可能要求指定深度。遞歸版:reduce+concat,每層遞歸depth-1。迭代版:用棧,每個元素標記當前深度。面試官會追問:如果數組裡有稀疏項(empty)怎麼處理?flat方法會跳過稀疏項。

29. 數組去重(多種方案)

這個和第10題的區別是面試官可能要求手寫多種方案並比較。Set版:[...new Set(arr)]。filter版:arr.filter((item, index) => arr.indexOf(item) === index)。Map版:用Map存已見過的值。追問:對NaN的處理?Set認為NaN===NaN,indexOf找不到NaN。

30. 排序算法(快排/歸併)

快排:選基準值,小的放左邊大的放右邊,遞歸。面試常考原地快排:雙指針從兩端掃描,交換位置。歸併:分治,遞歸拆半再合併。時間複雜度都是O(nlogn),但快排最壞O(n²),歸併穩定。面試官會問:什麼時候用歸併?數據量大且要求穩定排序時。我在美團二面被要求手寫原地快排。

真題彙總

以上30題按出現頻率排序:Top 10高頻是防抖節流、深拷貝、call/apply/bind、Promise、Promise.all、並發限制、EventEmitter、LRU緩存、new操作符、instanceof。其中Promise相關和EventEmitter幾乎每場面試必考,一定要滾瓜爛熟。

心得建議

1. 每道題必須手敲,看懂和寫出來差了十萬八千里。建議在LeetCode或CodePen上練習。

2. 注意邊界條件,面試官最愛在這些地方挖坑:null/undefined參數、空數組、循環引用、NaN處理等。

3. 說出思路再寫代碼,先和面試官確認理解是否正確,避免寫完發現理解偏了。

4. 寫完要測試,自己構造測試用例跑一遍。面試官很看重你有沒有驗證意識。

5. 了解原理,不只是能寫出來,還要能解釋為什麼這麼寫。比如Promise為什麼用微任務,LRU為什麼用雙向鏈表。

FAQ

Q:手寫題要寫到什麼程度?

A:核心邏輯必須完整,邊界條件盡量覆蓋。不用追求100%完美,但主要功能要能跑。面試官更看重思路和編碼習慣。

Q:時間不夠怎麼取捨?

A:優先準備Top 10高頻題,特別是Promise系列、防抖節流、深拷貝、EventEmitter。這些覆蓋了80%的考點。

Q:手寫題用什麼語言?

A:前端崗用JavaScript/TypeScript。如果面試官允許用TS,類型標註會加分。

Q:寫不出來怎麼辦?

A:先說思路,哪怕代碼寫不完整,思路對了也有分。千萬別沉默——邊想邊說,讓面試官看到你的思考過程。

#前端#手寫題#Promise#JavaScript#設計模式#算法#面試