與手寫代碼實(shí)戰(zhàn))
2023年那場小滿春招第二批筆試題我到現(xiàn)在還留著草稿紙。當(dāng)時大環(huán)境不算好Web前端崗位的競爭比往年激烈不少筆試刷人刷得特別狠。我最后能走到offer環(huán)節(jié)靠的不完全是運(yùn)氣更多是考前把常見考點(diǎn)吃透了尤其是那些看似基礎(chǔ)、實(shí)則暗藏坑的題目。這篇就把我當(dāng)時拿到的那套題結(jié)合我自己的答題思路和事后復(fù)盤完整拆一遍。題型分布、知識點(diǎn)覆蓋、解題過程、踩坑記錄都會講到。如果你是準(zhǔn)備校招或者實(shí)習(xí)面試的這套題的含金量很高值得認(rèn)真走一遍。1. 內(nèi)容整體設(shè)計與思路拆解1.1 筆試考察的核心方向這套筆試整體分四個部分計算機(jī)基礎(chǔ)、JavaScript語言深度、瀏覽器與工程化、代碼實(shí)戰(zhàn)。滿分100分考試時間90分鐘。從試卷結(jié)構(gòu)能明顯感覺到面試官想看的是扎實(shí)的語言功底和工程思維不是背了多少API。計算機(jī)基礎(chǔ)部分考察了網(wǎng)絡(luò)協(xié)議、數(shù)據(jù)結(jié)構(gòu)題目不多但很典型。JavaScript部分分值最高占了將近40分涉及原型鏈、閉包、異步、this綁定這些高頻考點(diǎn)。瀏覽器與工程化部分考了渲染流程、緩存機(jī)制、模塊化、構(gòu)建工具。最后的代碼實(shí)戰(zhàn)有兩道題一道是算法題一道是手寫源碼題。這種出題邏輯其實(shí)反映了當(dāng)前Web前端崗位的真實(shí)需求基礎(chǔ)要牢語言要熟工程化思維要有動手能力不能虛。如果你只刷過各種框架的API文檔這套題會做得很吃力。1.2 出題人的考察意圖分析把整套題看完能明顯感覺到出題人不是隨便拼題的每一道都在考察特定的能力維度基礎(chǔ)題考察的是你大學(xué)四年有沒有認(rèn)真上計算機(jī)基礎(chǔ)課計算機(jī)網(wǎng)絡(luò)、數(shù)據(jù)結(jié)構(gòu)這些科班功底是后續(xù)發(fā)展的天花板。JavaScript題目考察的是真正理解了還是只停留在會用的層面。很多應(yīng)屆生項(xiàng)目經(jīng)驗(yàn)豐富但是問到底層原理就含糊不清這類題目專門篩選這種情況。工程化題目緊跟行業(yè)實(shí)踐考察你是否真的在真實(shí)項(xiàng)目中折騰過而不是只寫了幾個demo頁面。實(shí)戰(zhàn)題考察的是在線編碼能力和代碼風(fēng)格45分鐘內(nèi)完成兩道題時間安排和思路清晰度都很關(guān)鍵。這樣一套組合拳下來基本能把“背題型選手”和“實(shí)戰(zhàn)型選手”區(qū)分清楚。所以如果你要準(zhǔn)備這種筆試不要只看知識點(diǎn)清單更要理解每道題背后的考察意圖。2. 核心細(xì)節(jié)解析與實(shí)操要點(diǎn)2.1 計算機(jī)基礎(chǔ)題網(wǎng)絡(luò)與數(shù)據(jù)結(jié)構(gòu)的必考點(diǎn)這套題第一部分考了三個題目難度不高但很典型。第一題是TCP三次握手的過程描述。這個屬于必考中的必考但很多同學(xué)答不完整。要答好這道題不能只寫“客戶端發(fā)SYN服務(wù)端回SYNACK客戶端再回ACK”得把每個狀態(tài)說清楚包括為什么是三次而不是兩次以及SYN Flood攻擊為什么利用了這個機(jī)制。我當(dāng)時還補(bǔ)充了序列號初始化的意義這會讓面試官覺得你是真的懂網(wǎng)絡(luò)而不是背了流程。第二題是數(shù)組和鏈表的區(qū)別與適用場景。這種題看起來簡單但想拿滿分需要答出幾個維度內(nèi)存分配方式、插入刪除時間復(fù)雜度、隨機(jī)訪問時間復(fù)雜度、緩存局部性。我在答題時用了表格對比的方式把時間復(fù)雜度一列列列清楚然后舉了實(shí)際場景的例子比如LRU緩存用鏈表、頻繁按下標(biāo)訪問用數(shù)組。第三題是哈希表的沖突解決方法。拉鏈法和開放定址法都要寫出來還要說明各自的優(yōu)缺點(diǎn)。我當(dāng)時還提到了Java HashMap在鏈表長度超過閾值時轉(zhuǎn)紅黑樹的優(yōu)化雖然這是偏向后端的內(nèi)容但能體現(xiàn)知識面的廣度。這類基礎(chǔ)題的經(jīng)驗(yàn)是不要只背結(jié)論要把“為什么”準(zhǔn)備到位。面試官后續(xù)追問往往是順著你的答案深挖你答得越深入后續(xù)壓力反而越小。2.2 JavaScript基礎(chǔ)原型、閉包、this綁定與異步第二部分的JavaScript題目是整套題的核心我一個個拆。原型鏈考了一道代碼輸出題讓我寫出構(gòu)造函數(shù)的實(shí)例查找屬性的完整鏈路過。這道題的本質(zhì)是考察__proto__和prototype的關(guān)系。我當(dāng)時的答題方式是畫了一條完整的鏈實(shí)例對象 → 構(gòu)造函數(shù).prototype → Object.prototype → null然后把每個環(huán)節(jié)的屬性查找邏輯標(biāo)注出來。閉包考了一道經(jīng)典場景題寫一個計數(shù)器函數(shù)每次調(diào)用返回的值加一。這個看似簡單但考察的是閉包的形成條件和變量生命周期。我當(dāng)時寫的答案是立即執(zhí)行函數(shù)加閉包實(shí)現(xiàn)的模塊化計數(shù)器還額外說明如果不用閉包變量會被全局污染的問題。this綁定考了四種調(diào)用方式的輸出結(jié)果包括普通函數(shù)調(diào)用、對象方法調(diào)用、call/apply調(diào)用、new調(diào)用。這種題沒有技巧只能把規(guī)則徹底搞清楚。我記得有一道是考察箭頭函數(shù)不綁定this的特性箭頭函數(shù)的this是在定義時確定的。這題我很確定自己答對了因?yàn)橛械李}里的setTimeout回調(diào)用了箭頭函數(shù)this指向定義時的外層對象而不是window。異步考察了事件循環(huán)和Promise的輸出順序。當(dāng)時給了一段代碼有同步代碼、setTimeout、Promise.then要求寫出輸出順序。這個考點(diǎn)需要理解宏任務(wù)和微任務(wù)的執(zhí)行順序Promise構(gòu)造函數(shù)內(nèi)的代碼是同步執(zhí)行的resolve之后注冊的then回調(diào)才是微任務(wù)。我的記憶口訣是同步先跑、微任務(wù)先于宏任務(wù)、每個宏任務(wù)結(jié)束后清空微任務(wù)隊(duì)列。給準(zhǔn)備筆試的同學(xué)一個建議JavaScript部分的題目一定要自己動手寫代碼驗(yàn)證。很多人就靠看題以為懂了實(shí)際上手全是錯。每一種this綁定場景、每一個異步輸出順序都用Node跑一遍比看十遍文章都管用。2.3 瀏覽器與工程化從加載到構(gòu)建的全鏈路理解這部分考察了瀏覽器從輸入URL到頁面展示的完整過程、HTTP緩存機(jī)制、ESModule和CommonJS的區(qū)別、打包工具的核心原理。URL到頁面展示這道題我在答題時盡量寫完整DNS解析、TCP連接、TLS握手、HTTP請求、服務(wù)器返回HTML、瀏覽器解析HTML構(gòu)建DOM樹、解析CSS構(gòu)建CSSOM樹、合成RenderTree、布局、繪制、合成。每一步展開說明可能涉及的關(guān)鍵點(diǎn)比如渲染進(jìn)程中主線程和合成線程的分工重排和重繪的區(qū)別。HTTP緩存考察了強(qiáng)緩存和協(xié)商緩存的字段。這題我比較有把握因?yàn)槲以陧?xiàng)目里實(shí)際配置過CDN和處理過緩存問題。Cache-Control的max-age、Etag的If-None-Match、Last-Modified的If-Modified-Since這幾個字段的位置和作用我寫得很細(xì)還畫了一個判斷流程圖。ESModule和CommonJS的區(qū)別那題我從幾個方面回答語法層面import/export和require/module.exports的區(qū)別加載時機(jī)一個是靜態(tài)編譯時一個是運(yùn)行時輸出值一個是值引用一個是拷貝還有tree-shaking依賴ESModule的靜態(tài)分析能力。這道題能延伸到構(gòu)建優(yōu)化我當(dāng)時還有意識地把tree-shaking和Vite/Rollup聯(lián)系起來了。工程化部分問的是Webpack打包的核心流程。我按四個階段來寫入口解析、依賴收集、代碼轉(zhuǎn)換、打包輸出。loader和plugin的區(qū)別是重點(diǎn)一句話說清楚loader負(fù)責(zé)文件內(nèi)容的轉(zhuǎn)換plugin負(fù)責(zé)在打包生命周期里做擴(kuò)展操作。這塊內(nèi)容有沒有真實(shí)項(xiàng)目經(jīng)驗(yàn)答題深度會有明顯區(qū)別。如果只是背概念很難寫出工程中的實(shí)際場景。建議準(zhǔn)備筆試的同學(xué)自己動手把Webpack從零配一遍經(jīng)歷過一次配置地獄很多概念自然就通了。3. 實(shí)操過程與核心環(huán)節(jié)實(shí)現(xiàn)3.1 代碼實(shí)戰(zhàn)數(shù)組扁平化與去重的多種解法實(shí)戰(zhàn)題第一道是給定一個多維數(shù)組要求扁平化并去重且需要保持原有順序不允許使用Array.prototype.flat和Set??吹筋}目后我第一反應(yīng)是考察兩個基礎(chǔ)能力的組合遞歸處理和哈希表思想。不限制flat說明想考手寫遞歸不限制Set說明想考手動去重邏輯。我的解題思路分兩步先寫一個遞歸的扁平化函數(shù)遍歷數(shù)組的每一項(xiàng)如果是數(shù)組就遞歸調(diào)用否則收集起來然后寫一個去重的邏輯利用一個對象作為哈希表記錄已出現(xiàn)的值遍歷時判斷是否出現(xiàn)過沒有出現(xiàn)過才放入結(jié)果數(shù)組。完整代碼我整理了一下基本是這樣function flattenAndUnique(arr) { // 第一步扁平化這里用遞歸實(shí)現(xiàn) const flattened []; function flatten(input) { for (let i 0; i input.length; i) { if (Array.isArray(input[i])) { flatten(input[i]); } else { flattened.push(input[i]); } } } flatten(arr); // 第二步去重利用對象鍵的唯一性 const result []; const seen {}; for (let i 0; i flattened.length; i) { const item flattened[i]; // 注意直接用 item 做鍵數(shù)字 1 和字符串 1 會沖突 // 所以這里要用類型加上值來做復(fù)合鍵 const key typeof item _ item; if (!seen[key]) { seen[key] true; result.push(item); } } return result; }這里有個坑要提醒大家直接用item作為對象的鍵1和1會被當(dāng)成同一個鍵導(dǎo)致不同類型的相同值被誤去重。我當(dāng)時想到了這個邊界情況用了typeof加item的組合鍵這里在閱卷時應(yīng)該有加分。如果你用ES6語法還可以用更簡潔的寫法function flattenAndUniqueByReduce(arr) { const flattened arr.reduce((acc, item) { return acc.concat(Array.isArray(item) ? flattenAndUniqueByReduce(item) : item); }, []); return flattened.filter((item, index) { return flattened.indexOf(item) index; }); }遞歸加indexOf去重的方式時間復(fù)雜度偏高indexOf每次查找是O(n)的整體會到O(n2)。如果數(shù)組很長性能會不夠好。但作為筆試答案兩種思路各有利弊我是把第一種哈希的思路作為正式答案寫的并且在注釋里分析了復(fù)雜度扁平化過程是O(n)去重過程是O(n)整體是O(n)空間復(fù)雜度也是O(n)屬于最優(yōu)解。3.2 代碼實(shí)戰(zhàn)手寫防抖函數(shù)與節(jié)流函數(shù)的區(qū)別實(shí)戰(zhàn)題第二道是手寫防抖函數(shù)與節(jié)流函數(shù)并說明它們的使用場景區(qū)別。這道題在真實(shí)業(yè)務(wù)中太常用了我在項(xiàng)目里優(yōu)化搜索框輸入時就寫過。防抖的核心思路是事件被連續(xù)觸發(fā)時只有在最后一次觸發(fā)后等待指定時間才執(zhí)行函數(shù)。如果等待期間再次觸發(fā)則重新計時??梢韵胂蟪呻娞蓐P(guān)門只要有人進(jìn)來就重新等一會兒再關(guān)門。function debounce(fn, delay 300) { let timer null; return function(...args) { // 每次觸發(fā)都清掉上一次的定時器 if (timer) { clearTimeout(timer); } const context this; timer setTimeout(() { fn.apply(context, args); timer null; }, delay); }; }這里有一個面試官特別愛追問的點(diǎn)為什么要在返回的函數(shù)里保存context this因?yàn)橹苯诱{(diào)用fn(args)時this會指向window或undefined嚴(yán)格模式丟失原調(diào)用者。用fn.apply(context, args)就能把this綁定到正確的對象上。節(jié)流的核心思路是在規(guī)定時間內(nèi)無論事件觸發(fā)多少次只執(zhí)行一次??梢韵胂蟪捎螒蚶锏募寄芾鋮s冷卻沒結(jié)束你怎么按都沒效果冷卻一好按一下就能放出來。function throttle(fn, interval 300) { let lastTime 0; return function(...args) { const context this; const now Date.now(); // 當(dāng)前時間與上次執(zhí)行時間的差大于間隔就執(zhí)行 if (now - lastTime interval) { fn.apply(context, args); lastTime now; } }; }這兩種寫法的使用場景區(qū)別我當(dāng)時是這樣寫的防抖適合“只關(guān)心最終狀態(tài)”的場景比如搜索框輸入后發(fā)送請求、窗口調(diào)整后計算布局節(jié)流適合“需要持續(xù)響應(yīng)但不想太頻繁”的場景比如滾動事件監(jiān)聽、鼠標(biāo)移動事件、游戲中的射擊。另外我還補(bǔ)充了帶立即執(zhí)行版本的防抖也就是第一次觸發(fā)時立即執(zhí)行后續(xù)連續(xù)觸發(fā)時只重置計時器。這個版本在有些場景下更實(shí)用比如提交按鈕防重復(fù)點(diǎn)擊第一次點(diǎn)擊要立即生效后面的點(diǎn)擊要攔掉function debounceImmediate(fn, delay 300, immediate true) { let timer null; return function(...args) { if (timer) { clearTimeout(timer); } const context this; if (immediate !timer) { // 第一次觸發(fā)立即執(zhí)行 fn.apply(context, args); } timer setTimeout(() { timer null; }, delay); }; }這道題我寫完的時候距離考試結(jié)束還有不到十分鐘所以沒有做更深入的擴(kuò)展。但事后復(fù)盤時意識到手寫防抖節(jié)流不僅要能寫出來還要能分析邊界情況和應(yīng)用場景這才是這道題拿高分的關(guān)鍵。3.3 編程題實(shí)現(xiàn)一個簡單的事件發(fā)布訂閱這套題最后一道編程題是手寫一個簡單的事件發(fā)布訂閱EventEmitter要求實(shí)現(xiàn)on、emit、off、once四個方法。這道題考的是對觀察者模式的理解。事件發(fā)布訂閱在日常開發(fā)中無處不在Vue的$emit和$on就是基于這種模式實(shí)現(xiàn)的Node.js的EventEmitter也是。理解了這個機(jī)制對后續(xù)讀框架源碼會有很大幫助。我的實(shí)現(xiàn)思路是內(nèi)部維護(hù)一個事件對象鍵是事件名值是一個回調(diào)函數(shù)的數(shù)組。on方法往數(shù)組里push回調(diào)emit方法遍歷數(shù)組并執(zhí)行所有回調(diào)off方法根據(jù)傳入的回調(diào)函數(shù)過濾數(shù)組once方法則是包裝一層執(zhí)行完后自動解綁。代碼實(shí)現(xiàn)如下class EventEmitter { constructor() { // 存儲事件和對應(yīng)的回調(diào)函數(shù)數(shù)組 this.events {}; } // 訂閱事件 on(eventName, callback) { if (!this.events[eventName]) { this.events[eventName] []; } this.events[eventName].push(callback); // 返回取消訂閱函數(shù)便于鏈?zhǔn)秸{(diào)用和自動清理 return () this.off(eventName, callback); } // 發(fā)布事件 emit(eventName, ...args) { const callbacks this.events[eventName]; if (!callbacks || callbacks.length 0) { return; } // 遍歷時用slice復(fù)制一份避免回調(diào)里執(zhí)行off時影響當(dāng)前遍歷 callbacks.slice().forEach(callback { callback.apply(this, args); }); } // 取消訂閱 off(eventName, callback) { if (!this.events[eventName]) { return; } this.events[eventName] this.events[eventName].filter( item item ! callback ); } // 只訂閱一次 once(eventName, callback) { const wrapper (...args) { // 執(zhí)行前先解綁 this.off(eventName, wrapper); callback.apply(this, args); }; // 把原函數(shù)掛到wrapper上方便通過原函數(shù)解綁 wrapper.original callback; this.on(eventName, wrapper); } }這段代碼里有幾個細(xì)節(jié)值得注意第一個是emit里為什么要用callbacks.slice()復(fù)制一份。如果不復(fù)制當(dāng)回調(diào)函數(shù)內(nèi)部調(diào)用了off方法會導(dǎo)致正在遍歷的數(shù)組被修改可能跳過某些回調(diào)。這是事件系統(tǒng)實(shí)現(xiàn)中非常經(jīng)典的一個坑。第二個是once的實(shí)現(xiàn)方式。不能用簡單的this.on(eventName, callback)然后執(zhí)行完再off因?yàn)榛卣{(diào)里如果還有其它訂閱邏輯順序會出問題。用包裝函數(shù)統(tǒng)一處理解綁邏輯是最穩(wěn)妥的方式。第三個是我加了一個返回值on方法調(diào)用后返回一個自動解綁的函數(shù)。這個設(shè)計在Vue 3的onUnmounted清理場景中很實(shí)用組件卸載時自動取消訂閱避免內(nèi)存泄漏。這道題我最終拿了滿分因?yàn)椴粌H實(shí)現(xiàn)了基本功能還處理了邊界情況并且每個方法的注釋里都寫了設(shè)計理由。筆試不能只追求能跑思路的完整性和代碼的健壯性更重要。3.4 手寫深拷貝與淺拷貝的完整對比筆試最后一部分第一道手寫題是深淺拷貝的實(shí)現(xiàn)與對比。這道題在JavaScript面試?yán)锍霈F(xiàn)頻率極高不僅考察遞歸和類型判斷還能看出你對內(nèi)存模型的理解。題目要求寫出淺拷貝和深拷貝的代碼實(shí)現(xiàn)并說明兩者區(qū)別。如果拷貝的嵌套對象中修改了基本類型和引用類型的值會影響原對象嗎淺拷貝的實(shí)現(xiàn)相對簡單只復(fù)制一層屬性。如果屬性值是引用類型拷貝的是引用地址不新開辟內(nèi)存空間。常見實(shí)現(xiàn)方式有Object.assign和展開運(yùn)算符function shallowCopy(obj) { // 處理基本類型和null if (obj null || typeof obj ! object) { return obj; } const result Array.isArray(obj) ? [] : {}; for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { result[key] obj[key]; } } return result; }深拷貝則要遞歸處理所有層級的嵌套對象。這里要特別注意幾個方面數(shù)組的判斷、日期和正則等特殊對象的處理、循環(huán)引用的處理。筆試中能考慮到循環(huán)引用這個點(diǎn)絕對能加分。大多數(shù)人的答案只能處理普通對象和數(shù)組遇到循環(huán)引用就直接爆棧。一個相對完善的深拷貝實(shí)現(xiàn)function deepClone(obj, map new WeakMap()) { // 處理基本類型和函數(shù)函數(shù)直接復(fù)用 if (obj null || typeof obj ! object) { return obj; } // 處理循環(huán)引用如果已經(jīng)拷貝過這個對象直接返回之前的結(jié)果 if (map.has(obj)) { return map.get(obj); } // 處理日期對象 if (obj instanceof Date) { return new Date(obj.getTime()); } // 處理正則對象 if (obj instanceof RegExp) { return new RegExp(obj.source, obj.flags); } // 數(shù)組和普通對象分開處理 const result Array.isArray(obj) ? [] : {}; // 保存到map里用于處理循環(huán)引用 map.set(obj, result); for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { result[key] deepClone(obj[key], map); } } return result; }我在答題時的實(shí)際書寫順序是這樣的先寫了淺拷貝的兩種方式展開運(yùn)算符和Object.assign說明了它們都是淺層復(fù)制然后寫深拷貝的完整實(shí)現(xiàn)從基本類型判斷開始逐層遞歸每到一層先處理特殊類型再處理普通屬性。一個容易忽略的點(diǎn)是for...in會遍歷到原型鏈上的可枚舉屬性所以要用Object.prototype.hasOwnProperty.call來判斷屬性是本對象的還是繼承來的。這個細(xì)節(jié)寫上了就顯得你對JavaScript的掌握不只是表面功夫。這里想多說一句題外話我看到有些同學(xué)直接用JSON.parse(JSON.stringify(obj))來寫深拷貝。這樣寫在筆試中肯定不行因?yàn)檫@種方式有明顯的局限性函數(shù)會被忽略、undefined會被忽略、日期會被轉(zhuǎn)成字符串、循環(huán)引用會直接報錯。雖然日常開發(fā)中偶爾能用但筆試中最好還是手寫遞歸版本并且主動說出JSON方法的局限性。4. 常見筆試問題與提問陷阱4.1 明明會做但丟分的低級失誤有哪些考完對照答案復(fù)盤的時候我發(fā)現(xiàn)了幾個共性的低級失誤這也是很多人筆試成績不理想的原因。第一大失誤是不仔細(xì)讀題。當(dāng)時的題目明確說了“不允許使用Array.prototype.flat和Set”但我聽說不少同學(xué)還是用arr.flat(Infinity)加上new Set()一把梭結(jié)果直接判錯。筆試不是做項(xiàng)目不是代碼能跑就行了條件里寫清楚的要求必須要遵守。第二大失誤是代碼風(fēng)格太差。筆試答題是有閱卷的面試官會看你的代碼寫得好不好。變量命名為a、b、c函數(shù)沒有注釋邏輯混亂這些即使功能正確分?jǐn)?shù)也會打折扣。我當(dāng)時的做法是每個函數(shù)都寫上簡短的注釋說明思路關(guān)鍵步驟加上備注。第三大失誤是不寫復(fù)雜度分析。很多筆試題目的加分項(xiàng)是復(fù)雜度分析特別是算法題時間復(fù)雜度和空間復(fù)雜度寫出來能證明你真正理解了代碼的性能特征。我在數(shù)組扁平化去重那題寫了O(n)時間O(n)空間的分析在深拷貝那題也寫了遞歸深度的復(fù)雜度分析。第四大失誤是時間分配不合理。一開始就在一道難題上耗太久導(dǎo)致后面的簡單題沒時間做。我當(dāng)時的策略是先瀏覽一遍所有題目把有把握的題先做掉拿到基礎(chǔ)分再回頭啃難題。這個策略在時間緊湊的筆試中很實(shí)用。4.2 高頻追問面試官會如何深挖這些題筆試過后是面試環(huán)節(jié)筆試中寫的代碼會被面試官拿出來逐一追問。提前考慮哪些點(diǎn)會被深挖筆試時把注釋寫好面試就輕松不少。以數(shù)組扁平化去重那道題為例面試官大概率會問如果數(shù)組里出現(xiàn)循環(huán)引用怎么辦如果元素中有對象怎么判斷兩個對象是否“相同”如果數(shù)組非常大內(nèi)存和性能怎么取舍我當(dāng)時沒有寫出循環(huán)引用的處理方案面試時被問到確實(shí)有點(diǎn)緊張?,F(xiàn)在回頭看在筆試階段就應(yīng)該把這些邊界情況寫在注釋里哪怕不實(shí)現(xiàn)也表明你考慮過這層。防抖節(jié)流那題面試官喜歡追問防抖和節(jié)流的適用場景怎么分如果你要在搜索框里做聯(lián)想用防抖還是節(jié)流如果用戶一直在輸入停不下來怎么辦還有怎么讓防抖函數(shù)同時支持取消和立即執(zhí)行這些擴(kuò)展點(diǎn)在筆試代碼里提前想好面試時就能從容應(yīng)對。事件發(fā)布訂閱那題面試官追問的典型問題是如果同一個事件名有100個訂閱者某個訂閱者拋異常了會影響其它訂閱者執(zhí)行嗎如果emit和off同時發(fā)生怎么處理once的語義是在所有訂閱者里只回調(diào)一次還是只對該訂閱者只回調(diào)一次這些問題都是日常開發(fā)中真正會遇到的邊界場景。深拷貝那題最容易追問的點(diǎn)是JSON.parse(JSON.stringify())有什么局限循環(huán)引用怎么處理Symbol和函數(shù)怎么處理拷貝Map、Set、WeakMap時怎么辦現(xiàn)在的JavaScript數(shù)據(jù)類型越來越多深拷貝的完備性本身就值得展開思考。我把這些高頻追問整理成了一個表格方便對照復(fù)習(xí)筆試題高頻追問準(zhǔn)備建議原型鏈實(shí)例到Object.create(null)之間發(fā)生了什么手畫完整原型鏈結(jié)構(gòu)圖閉包計數(shù)器怎么在循環(huán)中正確使用閉包掌握var和let的區(qū)別IIFE閉包方案this綁定箭頭函數(shù)和普通函數(shù)在綁定上的本質(zhì)區(qū)別掌握定義期綁定與調(diào)用期綁定宏任務(wù)/微任務(wù)async/await的輸出順序用Node實(shí)際跑代碼驗(yàn)證HTTP緩存強(qiáng)緩存失效后瀏覽器如何處理會畫完整的緩存判斷流程圖事件發(fā)布訂閱回調(diào)拋異常會影響后續(xù)執(zhí)行嗎理解try-catch與事件循環(huán)的關(guān)系深拷貝JSON深拷貝的局限和循環(huán)引用會寫完整版的deepClone函數(shù)防抖節(jié)流用戶一直輸入停不下來怎么處理熟悉maxWait手寫實(shí)現(xiàn)4.3 真實(shí)復(fù)盤這套筆試題對準(zhǔn)備春招的參考價值站在現(xiàn)在的角度回看這套題它反映出的Web前端春招考察趨勢很清晰基礎(chǔ)越來越重要工程化越來越受重視片面追求框架API背誦越來越行不通。如果你想認(rèn)真準(zhǔn)備接下來的春招筆試有幾個方向值得投入JavaScript的每個核心概念都要能解釋“為什么”不能只知道“是什么”。原型鏈到底解決了什么問題、閉包為什么會造成內(nèi)存泄漏、事件循環(huán)為什么分為宏任務(wù)和微任務(wù)這些根源性的問題想清楚后你寫代碼的思維方式會不一樣。手寫代碼題一定要掐時間練。筆試的節(jié)奏通常是比較快的45分鐘兩道題還要留時間檢查平時不練速度就很難跟得上。我是在考前一周每天做一套手寫題專門訓(xùn)練鍵盤速度和思路組織速度。工程化方面的準(zhǔn)備不能停留在看文章。自己把Vite或者Webpack的配置從頭寫一遍遇到報錯自己排查一遍比看十篇腳手架教程都有用。筆試?yán)锍霈F(xiàn)的工程化題目基本都是實(shí)踐場景的抽象。面試官深挖的能力在筆試階段就要開始儲備。寫代碼的時候多問自己幾個為什么多考慮幾個邊界情況這些思路寫到答題卡上就是加分項(xiàng)。從我個人的經(jīng)驗(yàn)來說春招筆試最難的其實(shí)不是題目本身而是如何在有限時間內(nèi)把自己會的東西充分展示出來。有時候你明明知道某個概念但就是在考場上緊張或者時間緊了就寫不出來。所以有針對性的模擬練習(xí)特別重要。我當(dāng)時把??途W(wǎng)的題庫刷了幾百道還專門找了往年的真題來限時模擬。最后再分享一個我自己用起來很順的復(fù)習(xí)方法把做錯的題和不會的知識點(diǎn)整理成一份錯題筆記。注意這不是簡單地記題目和答案而是要把自己當(dāng)時的錯誤思路、正確的解題思路、以及相關(guān)知識點(diǎn)都寫清楚??记耙惶觳恍枰偎㈩}只需要反復(fù)翻錯題筆記把薄弱點(diǎn)過一遍。這套方法在春招期間幫我穩(wěn)住了很多分?jǐn)?shù)。