
“前端筆試”這四個字對于2020年那會兒的應屆生來說基本等同于“八股文面試”的同義詞。但其實回過頭看像樂信這種體量、業務又偏金融科技的公司前端筆試題反而不是純粹考背書的它更看重你能不能把手上的JS和CSS用明白能不能用工程化的思路去解決一個看似簡單的頁面問題。我花了點時間把這類校招筆試題的常見套路、出題邏輯以及背后真正想考察的能力點重新梳理了一遍結合我自己當年刷題和后來面人的經驗寫成這篇詳細點的拆解。無論是正在準備校招的應屆生還是想查漏補缺的初級前端希望這份復盤能幫到你。1. 筆試想考什么從樂信的招聘畫像倒推考點很多同學準備筆試有個誤區拿到題就刷刷完就忘完全沒去想過“出題人到底想考察什么”。校招筆試題不像社招那樣直接考察項目經驗和解決復雜問題的能力它更像一個漏斗先篩掉基礎不扎實、代碼習慣差、思維模式有硬傷的人。樂信2020年前端筆試的題型分布其實很能反映當時主流互聯網公司對校招生的期望畫像。從整體結構來看這套筆試題可以拆成四大塊計算機基礎與網絡、JavaScript語言特性與手寫代碼、瀏覽器與前端工程化、框架應用與場景設計。其中JavaScript和網絡是占分最大的兩塊。為什么這兩塊占大頭因為前端這個崗位往上走的瓶頸從來不是CSS寫得多漂亮而是你對語言本身的理解深度以及對瀏覽器這個運行環境的掌控能力。尤其是JavaScript校招筆試題特別喜歡在“原型鏈”“異步”“閉包”“作用域”這幾個經典考點上做文章。這些東西不是背下來就行而是要通過寫代碼的方式考察你能否跳出API層面看到語言設計背后的邏輯。再往深一層說樂信的業務是金融科技前端要處理大量表單、交易狀態、數據可視化圖表。所以筆試里一定會出現跟“異步流程控制”“數據不可變”“性能優化”“邊界情況處理”相關的題目。這些考點不是單純為了難為你而是為了模擬真實業務里可能出現的狀態管理混亂、接口競態、大數據量渲染卡頓等問題。還有個容易被忽略的點就是筆試題的“時間密度”。2020年這套題我記得總時長大概在90到120分鐘之間題量卻不小。如果你對某個知識點不熟在那兒卡了10分鐘后面的題基本就寫不完了。這就考察了一個很重要的能力在有限時間內如何合理分配精力先拿穩拿分、再攻難題。很多同學栽在筆試題上不是不會做而是時間沒分配好前面一道手寫題寫了40分鐘后面兩道框架題直接放棄。所以準備這類筆試題我的建議是不要只刷題要“帶題復習”??吹揭坏李}先想它對應哪個知識模塊再想這個模塊在真實業務里解決什么問題最后才是寫代碼。這樣才能把知識串成體系而不是碎片。2. JavaScript異步與手寫代碼最容易被拉開差距的板塊JavaScript的異步和手寫代碼是前端筆試里拉開差距的關鍵。有的同學能拿滿分有的同學看起來什么都懂一動手寫就漏洞百出。這一章我挑幾個樂信筆試里出現頻率最高、也最容易出錯的類型詳細講一下解題思路和踩坑點。2.1 事件循環與宏任務/微任務的執行順序這幾乎是前端筆試必考的一類題樂信2020年也不例外。題目通常給出一段包含setTimeout、Promise、async/await的代碼讓你寫出輸出順序。這類題的核心考點就是瀏覽器的事件循環機制。你得徹底理解一個事實JavaScript是單線程的但瀏覽器不是。JS主線程執行代碼時遇到異步任務不會傻等而是會把它們交給瀏覽器的其他線程去處理等結果準備好之后再把回調放進任務隊列。主線程空閑了再從任務隊列里取任務執行。任務隊列又分宏任務macrotask和微任務microtask。宏任務setTimeout、setInterval、I/O、UI渲染、事件回調微任務Promise.then/catch/finally、MutationObserver、async/await后面的代碼執行順序的規則是先執行一個宏任務整個script其實也算一個宏任務執行完這個宏任務之后會清空當前所有的微任務。等微任務清空了如果需要渲染就渲染然后再從宏任務隊列里取下一個宏任務執行。只看這句話可能還是會亂我拆個例子console.log(1); setTimeout(() { console.log(2); Promise.resolve().then(() { console.log(3); }); }, 0); Promise.resolve() .then(() { console.log(4); setTimeout(() { console.log(5); }, 0); }) .then(() { console.log(6); }); console.log(7); // 輸出順序是什么解這類題有個固定的思維路徑第一步執行同步代碼輸出1和7。 第二步分析異步任務setTimeout回調進宏任務隊列記為macro1Promise.resolve().then()進微任務隊列記為micro1第三步同步代碼執行完清空微任務隊列先執行micro1輸出4然后micro1的then鏈把6也加入微任務隊列繼續執行輸出6。同時micro1內部又注冊了一個setTimeout進宏任務隊列記為macro2第四步微任務清空后取第一個宏任務macro1執行輸出2它內部的Promise.resolve().then()創建了微任務micro2執行輸出3第五步宏任務macro1執行完再清空微任務micro2已執行完然后取下一個宏任務macro2輸出5最終輸出順序就是1, 7, 4, 6, 2, 3, 5。這里最容易錯的地方是把micro1內部注冊的setTimeout當成宏任務macro1之后的處理。實際上微任務執行是“全部清空”的你在微任務里往宏任務隊列塞任務得等當前所有微任務清空之后才輪到它。筆試題里還有一個常見變體就是async/awaitasync function async1() { console.log(async1 start); await async2(); console.log(async1 end); } async function async2() { console.log(async2); } console.log(script start); async1(); console.log(script end);這里只要記住await的語義await后面的代碼相當于被放到了.then()里也就是微任務。所以上面代碼輸出是script start async1 start async2 script end async1 endasync1 end放在最后因為await等待async2()執行完但async2()本身是同步執行完的執行完async2之后async1函數體剩余部分就被推遲到微任務里了。實戰小貼士遇到async/await的題先把await翻譯成Promise.then再執行就不容易錯。2.2 手寫Promise.all / 手寫防抖節流手寫題是筆試題里的重頭戲。樂信2020年筆試里出現過手寫Promise.all還有手寫防抖函數。這類題看似簡單但要在紙上寫得又快又對還是需要平時練過。先看手寫Promise.all。這道題的核心考點是你對Promise構造器、resolve/reject的調用時機、以及異步任務收集的理解。function myPromiseAll(promises) { return new Promise((resolve, reject) { if (!Array.isArray(promises)) { throw new TypeError(promises must be an array); } const results new Array(promises.length); let count 0; if (promises.length 0) { resolve(results); return; } promises.forEach((promise, index) { Promise.resolve(promise) .then((value) { results[index] value; count 1; if (count promises.length) { resolve(results); } }) .catch((err) { reject(err); }); }); }); }這里有三個容易踩的坑第一空數組的情況。Promise.all([])返回的應該是一個已經resolve的 Promise結果是空數組。很多人寫的時候沒做這個判斷直接進去循環count 永遠是0永遠不會觸發 resolve整個 Promise 就掛起了。第二Promise.resolve(promise)這一步很關鍵。因為傳入的數組里可能混有普通值而不是 Promise 實例。Promise.all的特性是會把數組里的非 Promise 值直接當作已決議的值處理所以包一層Promise.resolve是最穩妥的做法。第三結果的順序必須和輸入順序一致而不是完成的先后順序。所以不能用results.push(value)而是要用results[index] value這種方式按索引賦值。如果你用 push一旦前面某個 Promise 卡住后面先完成的就會占錯位置。再看手寫防抖函數。防抖的核心思路是在事件被觸發后延遲wait毫秒再執行回調如果在這段時間內又被觸發就重新計時。典型的應用場景是搜索框輸入、窗口 resize。function debounce(fn, wait 300, immediate false) { let timer null; let isInvoked false; return function (...args) { const context this; if (timer) { clearTimeout(timer); } if (immediate !isInvoked) { fn.apply(context, args); isInvoked true; return; } timer setTimeout(() { fn.apply(context, args); isInvoked false; timer null; }, wait); }; }這里有個容易被忽略的點this的綁定。如果你在debounce內部直接調用fn(...args)那么this就會指向undefined嚴格模式下或全局對象非嚴格模式下。所以必須用fn.apply(context, args)把原函數的this傳進去。這是閉包加高階函數題目里最常見的考點。至于節流throttle核心思路是控制執行頻率保證在一段時間內至少執行一次。跟防抖的區別在于防抖是“最后一擊”節流是“勻速釋放”。筆試里這兩者常被拿來對比考概念手寫的話用時間戳版本比較不容易出錯function throttle(fn, interval 300) { let lastTime 0; return function (...args) { const context this; const now Date.now(); if (now - lastTime interval) { fn.apply(context, args); lastTime now; } }; }時間戳版的缺點是第一次會立即執行最后一次觸發不會執行因為間隔不夠就丟掉了。面試時如果能答出這個缺點并補充一個定時器版本的實現會是個很好的加分點。2.3 深拷貝與類型判斷手寫深拷貝也是筆試常客。樂信2020年筆試里出現過一道拷對象數據、要求不改變源數據的題本質上就是在考察深拷貝。深拷貝的考點其實分三層第一層基礎版本能處理對象和數組用遞歸實現第二層考慮循環引用用WeakMap或Map保存已拷貝的對象第三層考慮Date、RegExp、Function、Map、Set等特殊類型筆試一般不會要求非常完整的版本但至少要做到安全處理循環引用否則一遇到循環引用就爆棧。function deepClone(target, map new WeakMap()) { if (target null || typeof target ! object) { return target; } if (target instanceof Date) { return new Date(target); } if (target instanceof RegExp) { return new RegExp(target.source, target.flags); } if (map.has(target)) { return map.get(target); } const cloneTarget Array.isArray(target) ? [] : {}; map.set(target, cloneTarget); Reflect.ownKeys(target).forEach((key) { cloneTarget[key] deepClone(target[key], map); }); return cloneTarget; }這里用Reflect.ownKeys而不是Object.keys是為了連不可枚舉屬性和 Symbol 鍵也能拷貝到。但筆試如果用這個方法需要對Reflect比較熟悉所以平時得練過。關于類型判斷有一個經久不衰的面試題typeof和Object.prototype.toString的區別。筆試里可能會給你一堆變量讓你判斷類型。typeof只有幾個返回值undefined、boolean、string、number、bigint、symbol、object、function。它的局限是null返回object數組、對象、正則都返回object沒法區分具體類型。最穩妥的方式是Object.prototype.toString.call(target)它返回類似[object Array]、[object Date]這樣的字符串。這個方法基本能區分所有內置類型。筆試里如果需要精確判斷類型建議封裝一個getType函數function getType(target) { return Object.prototype.toString.call(target).slice(8, -1).toLowerCase(); }slice(8, -1)會去掉[object 前綴和最后的]得到Array、Date這樣的字符串。這個函數在手寫深拷貝的時候非常實用可以作為類型分支判斷的依據。3. HTTP與瀏覽器緩存看似基礎實則細節密集如果說 JavaScript 手寫題是拉開分數差距的“主觀題”那 HTTP 和瀏覽器相關的題就是決定你能不能過線的“客觀題”。這部分分數拿起來相對容易因為它的考點非常固定。但失分點也很集中主要是概念混淆比如把緩存策略的Cache-Control和Expires搞混把ETag和Last-Modified的驗證邏輯搞反。3.1 強緩存與協商緩存的完整鏈路關于瀏覽器緩存只要記住一句話強緩存是“不問你直接用”協商緩存是“問一下再用”。這個“問一下”就是發一次 HTTP 請求帶上驗證字段讓服務器判斷資源有沒有變。強緩存相關的頭部主要是Expires和Cache-Control。Expires是 HTTP/1.0 時代的產物是一個絕對時間。但因為服務器時間和客戶端時間可能不一致所以現在基本不用了。Cache-Control是 HTTP/1.1 引入的用相對時間max-age來控制比如Cache-Control: max-age3600表示資源在3600秒內可以直接用緩存不用發請求。如果Cache-Control和Expires同時存在Cache-Control的優先級更高。協商緩存的核心機制是當強緩存過期后瀏覽器帶著驗證字段去問服務器資源有沒有更新服務器根據字段判斷Last-Modified/If-Modified-Since服務器返回資源最后修改的時間下次請求時瀏覽器把這個時間放到If-Modified-Since頭里服務器比對。如果資源沒變返回304 Not Modified瀏覽器繼續用緩存。ETag/If-None-MatchETag是資源內容生成的唯一標識通常是對文件內容做哈希資源變了標識就變。瀏覽器下次請求時帶上If-None-Match服務器比對。如果一致返回304。這兩個字段有個優先級問題ETag的優先級高于Last-Modified。因為Last-Modified只能精確到秒如果同一秒內文件被修改了但沒有內容變化或者修改后又改回去了Last-Modified就沒法精確感知而ETag是內容哈希更準確。筆試題里常見的坑是讓你設計一套緩存策略適合一個版本化資源比如app.js?v1.0.0和一個非版本化資源比如index.html。正確的做法是版本化資源使用強緩存設置超長的max-age比如一年。因為文件名帶v1.0.0只要版本變了URL 就變了永遠不會命中舊緩存。index.html使用協商緩存或者設置成no-cache。因為 HTML 是入口文件如果它被強緩存了用戶可能永遠拿不到最新的 JS/CSS 引用。所以 HTML 必須每次“問一下”服務器拿到最新的資源引用。這個思路在真實業務里非常重要。當年踩過的坑是上線后用戶打開頁面還是舊版本排查半天發現index.html被設置了Cache-Control: max-age86400瀏覽器走強緩存壓根沒有問服務器要新頁面。3.2 從URL輸入到頁面展示中間發生了什么幾乎是前端筆試必考的開放題。樂信2020年筆試也有一道類似的不過問得更具體從輸入URL到頁面展示中間經過了哪些步驟哪些步驟可能成為性能瓶頸這道題的標準鏈路是URL 解析判斷是搜索詞還是合法 URL補全協議頭DNS 解析把域名解析成 IP。這里涉及瀏覽器 DNS 緩存、操作系統 DNS 緩存、本地 hosts 文件、LDNS 遞歸查詢建立 TCP 連接三次握手確認雙方收發能力正常發送 HTTP 請求瀏覽器發起請求可能帶緩存驗證走協商緩存服務器處理請求并返回涉及負載均衡、后端處理、資源組裝瀏覽器接收響應處理狀態碼、響應頭觸發緩存策略解析 HTML 構建 DOM 樹解析 CSS 構建 CSSOM 樹渲染合并 DOM 和 CSSOM 成渲染樹進行布局Layout和繪制Paint加載子資源遇到script、link、img等標簽繼續發起請求筆試答這道題盡量別只寫骨架最好在幾個關鍵節點加上一句“為什么”。比如在 DNS 解析那里說明為什么用 CDN 可以加速——因為 CDN 廠商會在 DNS 層面做智能解析讓你就近訪問節點在 TCP 連接那里說明為什么 HTTP/2 有優勢——因為它可以在一個 TCP 連接上多路復用多個請求減少了多次連接的開銷。樂信這道題還有個追問哪些步驟可能造成性能瓶頸這個可以從多個層面回答DNS 解析慢DNS 服務器響應慢、沒開 DNS 預解析TCP 連接慢網絡環境差、TCP 握手延遲HTTP/2 多路復用可以緩解服務器處理慢后端接口響應慢、沒有做緩存資源下載慢資源體積大、沒有開啟 CDN、沒有做壓縮渲染阻塞render-blocking的 CSS/JS 太多、腳本過大導致主線程長時間占用這個問題答得好不好其實能看出你平時是真的關心過頁面性能還是只在面試前背過一道題。3.3 跨域同源策略下的常見解決思路跨域是前端筆試里躲不開的考點。因為前后端分離架構下幾乎每個項目都會遇到。先理解為什么會有跨域瀏覽器的同源策略協議、域名、端口三者一致才算同源限制了頁面腳本不能訪問不同源的資源。這個限制是瀏覽器實現的不是服務器實現的。所以跨域這個問題核心思路是“讓服務器配合瀏覽器放行”或者“繞過瀏覽器的限制”。常見的解決方式CORS跨域資源共享最正統的方案。服務器在響應頭里加Access-Control-Allow-Origin來允許指定域名訪問。如果要帶 cookie還得加Access-Control-Allow-Credentials: true并且不能把Allow-Origin設為*必須指定明確的域名。JSONP利用script標簽不受同源策略限制這個特性動態創建script標簽加載一個帶回調參數的外部腳本。這是老方案只支持 GET 請求現在用得少了但筆試里經常考原理。Nginx 反向代理把前端請求代理到后端瀏覽器只跟同源的 Nginx 通信由 Nginx 轉發請求。這是生產環境里最常用、最穩定的方案。WebSocket不受同源策略限制。postMessage用于 iframe 頁面之間的跨域通信。筆試的經典題目是寫一個 JSONP 的實現或者描述 JSONP 的執行流程。function jsonp(url, params, callback) { return new Promise((resolve, reject) { const script document.createElement(script); const random callback_${Date.now()}_${Math.random().toString(16).slice(2)}; window[random] function (data) { resolve(data); delete window[random]; script.remove(); }; const queryString Object.keys(params) .map((key) ${encodeURIComponent(key)}${encodeURIComponent(params[key])}) .join(); const callbackKey callback || callback; script.src ${url}?${queryString}${callbackKey}${random}; script.onerror function () { reject(new Error(JSONP request failed)); delete window[random]; script.remove(); }; document.body.appendChild(script); }); }這里有個很重要的細節回調函數名不能寫死要用動態生成的隨機名。因為如果兩個 JSONP 請求同時進行都用了同一個回調名后面注冊的會覆蓋前面的導致第一個請求永遠等不到回調。這是一個很經典的邊界問題。4. 框架與組件設計從使用到原理的認知升級前端校招筆試的另一個重頭戲是框架題。樂信2020年筆試的時候Vue 2.x 還是絕對的主流React 16.x 也有一批項目在用所以筆試題基本圍繞這兩個框架出。這部分題不像手寫題那樣需要寫大量代碼而是更側重考察“你用了這個框架這么久有沒有想過它底層是怎么工作的”。4.1 響應式原理Object.defineProperty 與 Proxy 的對比Vue 2.x 的響應式原理是 Object.definePropertyVue 3.x 換成了 Proxy。筆試題目通常會問Vue 2 的響應式原理是什么有哪些局限Vue 3 用 Proxy 解決了什么。Vue 2 的核心邏輯是在初始化時遍歷 data 對象的每個屬性用 Object.defineProperty 把它們全部轉換成 getter/setter。當組件讀取某個屬性時觸發 getter把當前 watcher 收集進依賴里依賴收集當屬性被修改時觸發 setter通知之前收集的 watcher 去更新派發更新。這個機制的局限在于新增屬性不是響應式的。因為 Object.defineProperty 是在屬性已經存在的情況下做攔截的你后來給對象新增一個屬性它壓根沒有被 defineProperty 處理過所以修改它不會觸發更新。Vue 2 的解決辦法是Vue.set或this.$set來手動把新屬性變成響應式。數組的某些操作無法被攔截。直接通過索引修改數組元素比如this.list[0] xxx是觸發不了 setter 的。Vue 2 只好重寫了數組的push、pop、shift、unshift、splice、sort、reverse這些方法讓它們操作完之后再手動通知更新。需要遞歸遍歷所有屬性初始化性能有損耗。對象嵌套越深遞歸成本越高。Object.defineProperty 本身只能攔截屬性不能攔截整個對象所以刪除屬性的操作也無法被感知。Vue 3 用 Proxy 重寫了整個響應式系統。Proxy 可以代理整個對象不管是新增屬性、刪除屬性、讀取屬性還是修改屬性都能被攔截所以就不需要$set了。同時Vue 3 的響應式還做了懶收集不像 Vue 2 初始化時就把所有屬性都遞歸遍歷一遍而是等真正用到了才去收集依賴性能上也有提升。筆試答這道題建議畫一個兩列對比表格把“數據初始化時發生了什么”“新增屬性時怎么處理”“數組變更時怎么處理”“性能表現”這幾個維度列出來。這能有效展示你理解得夠深。4.2 組件通信父子、兄弟、跨層級這題說白了就是考察你實際寫項目時有沒有遇到過組件間數據傳遞的場景。樂信筆試里出現過一道場景題有 A、B 兩個兄弟組件用戶點擊 B 組件里的按鈕需要讓 A 組件里的數據發生變化問怎么實現。這道題沒有唯一標準答案它考察的是你對多種通信方案的理解以及方案選型能力。優秀的回答應該先列出所有方案再說出你在什么場景下會用哪種方案。常見方案場景方案父傳子props子傳父$emit觸發自定義事件兄弟組件通過共同的父組件中轉子B$emit父組件監聽父組件把數據通過 props 傳給子A跨層級 / 任意組件Vuex狀態管理庫、Provide/Inject、EventBusReact 場景Context、Redux/Zustand回答這題時可以順便講講 Vuex 的架構和核心概念。state是全局狀態數據getters類似 Vue 實例里的 computed用于派生狀態mutations是唯一能修改 state 的地方而且是同步的actions里可以寫異步邏輯可以提交 mutation。為什么 mutation 必須是同步的因為 Vuex 的 devtools 要記錄狀態變化的快照如果 mutation 里混了異步操作時間線就沒法追蹤了調試起來會很痛苦。4.3 生命周期從創建到銷毀的關鍵時刻生命周期是框架題的??统鲱}方式通常是給一段代碼問在哪個生命周期里發請求、在哪個生命周期里銷毀定時器、在哪個生命周期里訪問 DOM。Vue 2 的生命周期可以歸納為四個階段創建階段beforeCreate實例初始化之前此時拿不到 data 和 methods、created實例創建完成data 和 methods 可用但 DOM 還沒掛載掛載階段beforeMount模板編譯完成但還沒有插入真實 DOM、mountedDOM 掛載完成可以訪問this.$el更新階段beforeUpdate數據變化后DOM 更新前、updatedDOM 更新完成銷毀階段beforeDestroy實例銷毀前定時器、事件監聽還活著適合清理、destroyed實例銷毀后答生命周期題建議記住一個核心結論發起數據請求放在created或mounted都行但放在created更好因為created階段在掛載之前數據如果提前拿到可以避免 DOM 渲染完再等數據造成的不必要閃爍。定時器和全局事件監聽一定在beforeDestroy里清理。樂信筆試這道題的陷阱是問“在created里能不能訪問 DOM”。答案是不能。因為created階段實例剛創建模板還沒編譯DOM 還沒存在訪問this.$el是undefined。如果你需要在初始化階段操作 DOM比如讓某個元素獲得焦點那就得放在mounted或$nextTick回調里。5. 版本控制與工程化基礎考察你的職業素養前幾年校招筆試里版本控制Git和工程化相關的題占比不高但偶爾會出現在附加題或簡答題里。樂信2020年的筆試就有一道 Git 操作場景題。這類題你說它難它真的不難但如果你是第一次見可能連命令都寫不完整。5.1 Git 協作場景沖突解決與分支管理常見筆試題目是你正在一個分支上開發功能另一個同事改了同一個文件的同一個地方你把他的分支合并到你的分支時發生了沖突怎么解決正確的操作路徑先提交你當前分支的工作切換到目標分支比如 main/master拉取最新代碼切回你的開發分支執行git merge main或git rebase mainGit 提示沖突CONFLICT打開沖突文件你會看到類似這樣內容 HEAD 你的代碼 對方的代碼 feature/xxx手動編輯文件刪掉、、這些標記決定保留哪部分代碼或都保留保存后執行git add把文件加入暫存區執行git commit完成沖突解決。這里要補充一個概念對比merge和rebase的區別。merge會保留完整的合并歷史生成一個新的 merge commit分支圖會分叉rebase是把你的提交“變基”到目標分支之后重新排列提交歷史是一條直線更干凈。筆試如果問“你們團隊用 merge 還是 rebase”比較好的回答是團隊協作中尤其是公共分支盡量用 merge 來保留歷史個人開發分支可以用 rebase 整理提交記錄讓歷史更線性。5.2 Webpack 核心概念loader、plugin、性能優化樂信這類偏工程化的公司筆試里很可能會考 Webpack 的基本概念。核心考點就三個loader 是什么、plugin 是什么、兩者區別在哪里。簡單來說loader 是“轉換器”它把 Webpack 不認識的文件轉換成 JS 模塊。比如 babel-loader 把 ES6 語法編譯成 ES5css-loader 處理 CSS 文件里的import和url()style-loader 把 CSS 代碼注入到style標簽里。plugin 是“擴展器”它可以玩更高級的把控。從打包優化到文件處理到環境變量注入都能通過 plugin 實現。常見的有HtmlWebpackPlugin自動生成 HTML 并注入打包后的腳本、MiniCssExtractPlugin把 CSS 提取成獨立文件、TerserPlugin壓縮 JS。loader 和 plugin 最常見的混淆點很多人分不清“轉換某個文件類型”到底應該用 loader 還是 plugin。記住一句話loader 只做文件級轉換plugin 做構建流程級別的控制。舉個直觀例子處理.vue文件用的是vue-loader它把單文件組件拆成模板、腳本、樣式三部分再分別處理而你想要在打包結束之后自動上傳產物到 CDN那就是UploadPlugin自定義 plugin的事了。如果筆試再加問 Webpack 性能優化可以從兩個維度答打包體積開啟 tree-shaking去除沒用到的模塊代碼分割splitChunks把公共依賴抽出來按需加載路由懶加載。構建速度用cache-loader或 Webpack 自帶的持久化緩存用thread-loader開啟多進程構建減少 loader 的include范圍別讓node_modules里的文件也被 Babel 去處理。5.3 一個簡單的筆試場景題如何設計一個可復用的彈窗組件這類題在筆試題里被歸為“設計題”不寫完整代碼只考設計思路。出題意圖非常明確看你會不會抽象組件、會不會考慮公共邏輯、會不會預留擴展點。一個合格的彈窗組件應該至少包含以下設計點對外暴露的 API通過 props 控制彈窗標題、寬度、內容、是否顯示底部按鈕通過事件如visible-change通知外部彈窗關閉??刂骑@隱外部通過v-if或visible屬性控制組件內部在點擊遮罩層、點擊關閉按鈕時觸發關閉事件。插槽slot設計支持默認插槽自定義內容區域支持具名插槽如 footer自定義按鈕區域。功能細節點擊遮罩層是否關閉需可配置close-on-click-overlay是否顯示右上角關閉按鈕需可配置是否可拖拽需可配置組件卸載時是否重置數據。層級管理彈窗的z-index應該是動態遞增的防止多個彈窗互相遮擋。一般會有一個全局變量記錄當前最大z-index每次打開彈窗時加1。定位與布局彈窗居中遮罩層覆蓋全屏滾動鎖死打開彈窗時禁止 body 滾動?;卮鹪O計題時最好口述一個“從使用到實現”的完整鏈路最外層是一個遮罩層position: fixed覆蓋全屏半透明背景中間是彈窗主體position: fixedleft: 50%; top: 50%; transform: translate(-50%, -50%)實現居中通過 TeleportVue 3或 Portal 把彈窗掛載到body下避免被父組件的overflow: hidden裁掉。能講到 Teleport/Portal 這層基本就比大多數候選人高出一個段位了。因為這證明你不僅知道怎么用組件還知道組件在 DOM 結構中的位置會影響樣式表現。6. 從筆試到實戰那些刷題之外更重要的功夫講完這幾類典型題型我知道很多讀者更關心的是我到底該怎么準備才能在有限時間里最高效地通過筆試我自己的經驗是筆試前一天不要把大量時間花在刷“新題”上而是做三件效率最高的事。第一把高頻手寫題默寫一遍。防抖、節流、深拷貝、Promise.all、數組去重、數組扁平化、call/apply/bind 的實現。這些題是有標準模板的你只要平時默寫過三遍以上考場上基本是肌肉記憶幾分鐘就能寫完還能騰出時間給后面的難題。第二把框架的核心原理用自己的話說一遍。Vue 響應式、生命周期、組件通信、虛擬 DOMReact 的函數組件渲染流程、hooks 的原理。能用自己的話說清楚說明你是真理解而不是背的八股文。筆試簡答題最忌諱的就是把網上的原話抄上去閱卷人一眼就能看出你是背的還容易因為表述不嚴謹被扣分。第三看看你們學?;蚰繕斯就甑墓P試真題搞清楚出題風格。有些公司偏愛手寫題有些公司偏愛基礎選擇題有些公司會加一道邏輯題或場景設計題。投了不同公司策略完全不同。這里還想多說幾句關于“刷題心態”的問題。我發現很多應屆生準備筆試時容易陷入一種焦慮覺得題量巨大、知識無邊似乎永遠準備不完。實際上校招筆試是個合格性考試不是選拔性考試它不是要你考滿分而是要看你在重點模塊上有沒有達到一個“能干活”的最低標準。所以與其在所有角落撒網不如把最高頻的模塊吃透。就拿樂信這套題來說JavaScript 基礎、手寫代碼、瀏覽器緩存、跨域、Vue 原理、Git 常規操作這幾塊占了 80% 的分數。把這些搞定筆試過關基本沒有問題。還有一點做題時一定要先易后難。開考后先花 5 分鐘把整張卷子掃一遍標記出哪些題是送分題哪些題是模棱兩可的哪些題是完全沒思路的。先把送分題寫完再做中等難度的最后有時間再攻克難題。很多同學喜歡按順序做結果手寫題卡了半小時后面的簡答題沒時間寫白白丟分。最后聊一個隱藏技能筆試里的代碼書寫規范。手寫題雖然不要求真的跑起來但閱卷人會看你的代碼習慣。變量命名是不是有語義有沒有處理邊界情況格式是不是整潔這些都會影響印象分。我見過有些同學手寫代碼時連分號都不寫函數名用a、b、fn代替雖然邏輯可能是對的但給閱卷人的感覺很不好。筆試不僅僅是考你會不會也是在模擬你未來在工作里寫代碼的樣子。把一個數組去重寫成兩行簡潔、有注釋的代碼和寫十行邏輯混亂的代碼最后得分差距會很大。我自己面過不少校招生刷掉的人不一定是技術最差的但大概率是代碼習慣最差的。所以從現在開始每次在紙上或白板上寫代碼都把它當成一次正式的代碼審查注意格式、命名、邊界條件。這個習慣養成了不管什么筆試都會受益。準備筆試的過程其實是很枯燥的但也是提升最快的階段。等你真正把原型鏈、事件循環、響應式原理這些硬骨頭啃下來再回頭看之前覺得難的項目會發現腦海里的圖景完全不一樣。技術這條路有時候就是這樣你以為是應付考試實際上是在給自己打更深的地基。