
1. 這一輪筆試的出題風格企業軟件廠商想篩出什么樣的前端用友2019校招web前端筆試題四是當年那一輪校招里我印象最深的一套題。前面幾套卷子多少還會考一些HTML標簽語義、CSS選擇器優先級、HTTP狀態碼這類常識性內容到了這一套重心幾乎全壓在JavaScript最核心的機制上作用域、閉包、事件循環、手寫代碼再加上一道瀏覽器從輸入URL到頁面展示的完整鏈路大題。整套題做完最大的感受是它不考你會不會用某個框架的API而是考你對一門語言和瀏覽器運行機制的理解有多深。文章適合兩類人看正在準備用友或其他企業級軟件廠商前端校招的同學以及想系統補一補JavaScript基礎、對手寫代碼題沒底的人。1.1 用友這類廠商和互聯網公司前端考察的差異很多人備考前端校招習慣性按互聯網大廠的節奏刷算法題這套思路放在用友這類企業服務軟件廠商身上并不完全適用。用友的核心產品是ERP、財務、人力、供應鏈等企業軟件前端每天面對的是復雜表單、數據表格、報表展示、權限控制、流程審批這類頁面。這些頁面有一個共同特點單個頁面里JavaScript交互邏輯密度極高狀態多、數據量大、兼容性要求嚴格。筆試題出什么往往就是這些真實業務倒推出來的能力要求。所以對比下來用友這套卷子和互聯網公司有明顯區別大廠可能拿一道hard級算法題來篩選用友更傾向于在JavaScript語言機制、手寫代碼、瀏覽器渲染原理這些前端基本功上做文章。題目本身不偏不怪但覆蓋面很寬從變量提升到事件循環從防抖節流到深拷貝從DOM操作到渲染性能全都要懂。如果你只會框架用法而說不清底層原理在這套卷子里拿不到高分。1.2 第四套卷在整個校招筆試流程里的定位四這個編號通常代表題庫里的第四套平行卷。校招高峰期企業會把多套試卷隨機分發給不同批次的候選人題目難度做過均衡所以這套卷可以看作是當年web前端方向筆試題庫的一個切片。從內容編排來看它承擔的是第二輪篩人任務第一輪筆試往往考通識包括HTML/CSS、計算機網絡、基礎算法到了這一輪就要看候選人能不能在JavaScript語言層面獨立解決實際問題。這套卷里沒有太多死記硬背的概念題更多是給你一段代碼說出輸出結果和給你一個需求把代碼補全這種形式。換句話說它篩的不是看過多少書的人而是真寫過代碼、踩過坑、理解運行機制的人。后面幾章我會把這套卷里出現頻次最高的幾個考點逐項拆開每一類都給出典型題面、完整推導和答題要點。2. 作用域、閉包與事件循環JS輸出題的兩個層級輸出題是這套卷的第一道大菜也是很多人在考場上最容易翻車的部分。它的考察方式很簡單給你一段代碼讓你寫出控制臺輸出順序。但代碼里往往同時埋了作用域、閉包、宏任務、微任務、async/await這些機制任何一環理解不透輸出結果就會錯。2.1 第一層級var、let和閉包共享變量先說最經典的閉包陷阱題目我在當年這套卷里見過它的多個變形版本。for (var i 0; i 5; i) { setTimeout(function () { console.log(i); }, 100); }答案不是0 1 2 3 4而是5 5 5 5 5。原因在于var聲明的i是函數作用域整個循環共用同一個變量setTimeout的回調函數形成閉包引用的是同一個i。當100毫秒后回調執行時循環早已結束i已經變成5所以五個回調打印出來的全是5。題目改成let聲明之后輸出就變成了0 1 2 3 4。底層原因是let在for循環的每一次迭代中都會創建一個新的詞法環境每個閉包綁定的是當次迭代的i互不干擾。除了改成let用IIFE也能達到同樣效果for (var i 0; i 5; i) { (function (j) { setTimeout(function () { console.log(j); }, 100); })(i); }這里利用立即執行函數把每次的i作為參數傳入新的函數作用域閉包捕獲的是參數j自然就隔離了。這道題有一個容易踩的延伸坑如果把var改成const呢在普通的for循環里const聲明循環變量會在第二次迭代執行i時拋錯因為常量不允許重新賦值。只有for...of或for...in中的const才會為每次迭代創建新綁定。這個細節在考卷里出現得不多但答對很能體現功底。2.2 第二層級宏任務、微任務與async/await執行順序如果第一層級只是開胃菜那第二層級直接進入事件循環機制。這套卷的進階版輸出題長這樣console.log(start); setTimeout(function () { console.log(timeout); }, 0); Promise.resolve().then(function () { console.log(promise); }); console.log(end);輸出順序是start、end、promise、timeout。這里涉及事件循環里最核心的規則同步任務先執行微任務在當前宏任務執行完后、下一個宏任務開始前清空setTimeout的回調屬于宏任務要排在微任務后面。用生活化的方式類比宏任務是排隊的顧客微任務是顧客結賬時順手提出的小要求。收銀員必須把當前顧客的所有小要求處理完才叫下一位顧客。所以即使setTimeout的延遲時間設成0它也要等當前宏任務和全部微任務執行完才有機會運行。進階版本會把async/await也攪進來async function async1() { console.log(async1 start); await async2(); console.log(async1 end); } async function async2() { console.log(async2); } console.log(script start); async1(); setTimeout(() console.log(timeout), 0); new Promise(resolve { console.log(promise); resolve(); }).then(() console.log(then)); console.log(script end);正確的輸出順序是順序輸出說明1script start同步代碼2async1 startasync1函數體同步執行3async2await右側的函數調用同步執行4promisenew Promise的executor同步執行5script end同步代碼收尾6async1 endawait之后的部分作為微任務繼續7thenPromise的then回調微任務8timeoutsetTimeout回調宏任務這里最容易出錯的是async1 end和then的順序。await async2()的意思是先同步執行async2()然后把async1 end這段后續邏輯掛到微任務隊列而代碼里new Promise的then也是微任務。這里有個容易忽略的細節async2()作為await右側的表達式它內部的console.log(async2)是同步執行的但await之后的代碼會被推遲。微任務隊列里async1 end先被放入所以它比then先輸出。2.3 輸出題答題時的推導方法與常見丟分點這類題在卷面上是選擇題或填空題但答題時千萬不要憑感覺選。我的習慣是在草稿紙上畫一條時間線把同步代碼、微任務隊列、宏任務隊列分別列出來每執行一步就劃掉一步。這樣雖然慢但能保證萬無一失。常見丟分點主要是三個第一忽略new Promise的executor是同步執行的很多人以為Promise相關代碼全是異步導致promise的輸出位置錯第二混淆微任務和宏任務的優先級以為setTimeout先于Promise.then執行第三沒有注意到await右側的表達式是同步求值的只記住了await后面是異步的這句口訣結果把async2的輸出位置放錯。備考建議是把這幾道經典變形題動手跑一遍Node環境或者瀏覽器控制臺都行看輸出結果和預期是否一致。跑通之后再做變形例如把Promise.resolve().then換成多個嵌套的then把setTimeout的延遲時間改成不同值多錯幾次自然就理解事件循環了。3. 手寫代碼題防抖、節流、深拷貝這么寫才穩手寫代碼題是這套卷的重頭戲分數占比很高。用友這類企業軟件的前端開發日常工作中最常用到的不是排序算法而是防抖節流、深拷貝、數組去重、事件代理這些基本功。手動實現一遍比背十道概念題都有用。3.1 為什么這套卷必考手寫題企業級頁面里搜索框輸入要防抖滾動加載要節流表單編輯前要把原對象深拷貝一份用來做撤銷這些需求幾乎是每天都要寫的代碼。筆試考手寫題其實是在模擬真實工作場景考察候選人有沒有真正寫過、理解過這些功能而不只是知道名詞。代碼題還有一個隱藏優勢能看出代碼風格。閱卷人會看你變量命名是否清晰、邊界條件有沒有考慮、代碼有沒有多余的重復邏輯。這些細節在校招候選人里差別很大寫過項目的人和只刷過題的人代碼一眼就能分辨。3.2 防抖與節流的完整實現與場景辨析防抖的實現思路是事件觸發后不立即執行而是等待一段空閑時間如果期間再次觸發就重置計時器。核心代碼如下function debounce(fn, delay 300, immediate false) { let timer null; return function (...args) { const callNow immediate !timer; clearTimeout(timer); timer setTimeout(() { timer null; if (!immediate) { fn.apply(this, args); } }, delay); if (callNow) { fn.apply(this, args); } }; }有幾個細節必須注意第一fn.apply(this, args)不能省this要指向實際調用者event對象等參數要完整透傳第二定時器回調里要把timer置為null否則immediate模式下第二次觸發時callNow判斷會出錯第三immediate參數控制是否立即執行一次搜索框請求場景經常需要這個能力。節流實現思路是限制函數在固定時間間隔內最多執行一次。基于時間戳的實現如下function throttle(fn, interval 300) { let last 0; return function (...args) { const now Date.now(); if (now - last interval) { last now; fn.apply(this, args); } }; }防抖和節流的配套題通常是問各自適用場景。防抖適合連續操作結束后執行一次的場景比如輸入框搜索、窗口大小調整節流適合固定頻率執行的場景比如滾動加載、按鈕點擊防連點。答題時如果能把這兩個場景區分清楚再結合頁面里的具體例子分數會比只寫代碼高不少。3.3 深拷貝的實現和閱卷人藏在后面的考點深拷貝是手寫題里的分水嶺。只寫JSON.parse(JSON.stringify(obj))能得一半分但拿不到滿分。原因是JSON序列化方案有一堆硬傷undefined、函數、Symbol會被直接丟棄Date會被轉成字符串循環引用會直接拋錯。企業級業務對象里這些情況太常見了所以出題人真正期待的是一個處理了邊界情況的完整實現。參考實現如下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 clone Array.isArray(target) ? [] : {}; map.set(target, clone); Reflect.ownKeys(target).forEach(key { clone[key] deepClone(target[key], map); }); return clone; }這份代碼里的每一個判斷都對應一個考點。用WeakMap解決循環引用問題是因為WeakMap中的鍵是弱引用不會造成內存泄漏用Reflect.ownKeys遍歷鍵是因為它能拿到Symbol鍵和不可枚舉的屬性Object.keys拿不到先判斷Date和RegExp是因為這兩個對象有內部槽普通遍歷會得到空對象。閱卷人看這道題通常先掃一眼有沒有做循環引用處理再確認特殊對象是否處理最后看遍歷方式。能寫出Reflect.ownKeys這個API的人說明對ES6特性掌握得比較系統這一項在評價里很加分。如果覺得上面版本太復雜至少也要寫出處理循環引用和數組的基本版本比純JSON方案高一個檔次。4. 瀏覽器渲染鏈路一道簡答題值不值得展開成一篇小論文這套卷的簡答題部分最典型的一道是從在瀏覽器地址欄輸入URL到頁面展示中間發生了什么。這是一個經典面試題但很多人答得只覆蓋了網絡請求部分渲染部分三句話就帶過了分數自然不高。4.1 標準流程該怎么答完整的流程可以分成網絡鏈路和渲染鏈路兩段。網絡鏈路是DNS解析域名得到IP地址建立TCP連接并完成三次握手如果是HTTPS還要額外進行TLS握手然后發送HTTP請求服務器返回響應報文。渲染鏈路是瀏覽器拿到HTML文本后邊解析邊構建DOM樹解析過程中遇到CSS就構建CSSOM樹遇到JavaScript會阻塞解析執行完腳本才繼續之后把DOM和CSSOM合并成渲染樹計算布局最后繪制到屏幕上。答題時最容易犯的毛病是答得像背書點到解析HTML、渲染頁面就結束。想拿高分至少要體現出三個層次網絡請求和渲染是先后銜接的兩個階段渲染樹不等于DOM樹它只包含可見元素JavaScript會阻塞HTML解析所以有了async和defer兩種加載方式。4.2 渲染性能優化從理論到用友復雜表格場景這套卷在簡答題后面通常會跟一個小問結合具體業務場景說說你會做哪些渲染優化。這里的業務場景不會說得太細但結合用友的產品特征答題時可以往復雜數據表格方向靠。假設頁面上有一個幾千行數據的表格每一行都有按鈕交互直接全部渲染會導致頁面卡頓。優化思路有幾個方向分頁或者虛擬滾動只渲染可視區域的行數據更新時避免頻繁操作真實DOM用DocumentFragment批量操作對不變的數據做緩存避免重復渲染。這類場景化答題比泛泛而談減少重排重繪要具體得多也更能打動閱卷人。重排和重繪的區分也是高頻考點。改變元素的width、height、left、top等布局屬性會觸發重排重排必然導致重繪只改變color、background等外觀屬性則只會觸發重繪。優化手段包括用transform代替top/left做動畫、讀寫分離避免頻繁強制同步布局、用class批量修改樣式而不是逐條修改style屬性。4.3 緩存與網絡層面的加分回答如果想在這個題里繼續加分可以補上網絡層面的優化靜態資源用CDN分發通過強緩存和協商緩存減少重復請求HTTP/2多路復用降低連接開銷資源文件壓縮和按需加載減小首屏體積。這一串知識點并不難但能把網絡和渲染串起來回答的人不多。回答這類題的策略是先把主干流程分階段說清楚再挑兩三個細節深入展開最后落到業務場景給優化方案。這樣的答案框架完整、層次分明比想到哪寫到哪的答案得分要高。5. Vue與工程化這套卷里占比被低估的選項題用友2019年前端團隊的主力技術棧是Vue所以這套卷的客觀題部分出現了不少Vue相關內容。雖然單題分值不高但數量多加起來占比不可忽視。備考時如果只顧著刷JS題而忽略框架題這部分很容易白丟分。5.1 v-if/v-show與computed/watch的答題模板v-if和v-show的區別是Vue題里出現頻率最高的一道。先說結論v-if是真正的條件渲染值為false時元素不會被渲染到DOM中v-show則是無論如何都會渲染再用display: none隱藏。切換開銷上v-if有更高的切換開銷因為它涉及組件的銷毀和重建v-show有更高的初始渲染開銷因為所有元素都要先渲染一遍。使用場景的標準答法是頻繁切換用v-show切換頻率低或者條件在初始渲染后基本不會變的用v-if。答題時可以補充一點v-if在切換時會觸發組件的beforeDestroy和destroyed生命周期這在真實業務中可能會導致事件監聽器沒有清理引發布內元素的事件泄漏所以高頻切換場景優先選v-show。computed和watch的對比也經常出現在同一道題里。computed有緩存只有依賴的響應式數據變化時才會重新計算適合用于派生狀態比如根據商品數量計算總價watch適合執行異步操作或開銷較大的操作比如監聽路由變化觸發數據請求。答題時可以套用一句話能寫在computed里的邏輯就不放在watch里watch只做它擅長的事。5.2 組件通信、生命周期與webpack的常見考法組件通信這道題用友的考法比較務實給一個父子組件場景問數據怎么傳遞。基礎答案是props和$emit進階答案要補充provide/inject、$refs、事件總線以及Vuex。給出場景分析更穩妥跨多層級組件用provide/inject復雜狀態管理用Vuex簡單狀態沒必要全局管理。生命周期題目主要考created和mounted的區別。created時數據已經初始化但DOM還沒生成所以不能在這里操作DOMmounted之后組件被掛載到DOM上可以拿到真實DOM元素。數據請求放在created還是mounted都有道理但更標準的答法是初始化數據并需要盡快展示時放created依賴DOM完成的操作放mounted。工程化方面webpack的loader和plugin區別是高頻題。loader用于對模塊的源代碼進行轉換比如把ES6轉成ES5、把SCSS轉成CSSplugin解決的是loader無法實現的其他事比如打包優化、資源管理、環境變量注入。答題時用一個例子說明就夠了例如babel-loader和HtmlWebpackPlugin分別負責什么。6. 復盤時間分配、答題踩坑和筆試后的準備技術點說完了聊聊實戰層面的事。校招筆試不光是考知識儲備還是一場時間管理考試。以這套卷子90分鐘的時長來算合理的分配策略很重要。6.1 全程時間分配與題目優先級我的建議是選擇題和填空題控制在20分鐘內不要在任何一道題上糾結超過兩分鐘簡答題控制在20到25分鐘重點把框架寫清楚手寫代碼題留出30分鐘以上這是整套卷分值最集中、也最看得出水平的部分最后留15分鐘檢查重點回看輸出題有沒有粗心選錯。碰到不會的選擇題先標記跳過不要浪費時間。手寫代碼題如果第一遍寫得不完整也先跳過做后面的最后再回頭補邊界情況。這套卷的計分方式里代碼題的邊界情況處理通常占了相當比例的分值寧可多花一點時間在補全deepClone的循環引用判斷上也不要浪費在糾結某個CSS屬性的取值上。6.2 那些讓我丟分的低級錯誤復盤當年的答題過程有幾個低級錯誤很典型。第一個是輸出題沒在草稿紙上推演直接憑印象選結果因為微任務和宏任務的順序判斷失誤丟分。第二個是手寫防抖時只寫了核心邏輯忘記處理this指向和參數透傳因為這種代碼在本地環境跑不出來閱卷人一看就知道缺了這兩項。第三個教訓是簡答題寫得太教科書。回答輸入URL到頁面展示時如果只條目化地寫DNS解析、TCP連接、HTTP請求、渲染沒有把每一階段的關鍵機制解釋清楚給人感覺就是背過但沒有真正理解。后來我調整了答法每一條都配一兩句原理說明得分明顯提高。6.3 筆試答案如何變成面試素材筆試結束后不要直接撒手不管。我在等面試通知的那幾天把筆試里的輸出題全部在Node環境跑了一遍核對自己的答案和實際輸出是否一致把深拷貝實現里用到的每個API都查了一遍確保面試被追問時能講清楚為什么這樣設計。事實證明這一步非常值。用友的面試官在面試時會直接追問筆試中的手寫代碼題比如你剛才寫了防抖如果我想讓第一次點擊立即執行你會怎么改或者你的深拷貝是怎么處理循環引用的。因為提前復盤過這些問題回答得很順。最后的經驗是筆試不只是筆試它是在為面試整理自己的知識檔案。考完立刻把題目轉成文檔標出自己答錯的、模糊的、完全不熟悉的點按優先級補齊。這套方法我后來在別的公司校招中也沿用了效果一直不錯。能把這些題吃透后面面試遇到類似問題心里完全有底。