
每年校招季各家公司筆試題目總會在學(xué)生群里被翻來覆去地討論。歡聚時代2018年校招的web前端A卷就是當(dāng)時討論熱度很高的一份。我印象最深的一點(diǎn)是整張卷子并不靠偏題怪題來刁難人它的命題思路非常貼近一線開發(fā)場景——不考你背了多少API而是看你在寫頁面、寫JS的時候有沒有真正理解瀏覽器在替你做什么、JS引擎在怎么執(zhí)行你的代碼。這份卷子適合兩類人一是正在準(zhǔn)備前端校招的應(yīng)屆生可以把題目當(dāng)成知識盲區(qū)對照表二是入行一兩年、想系統(tǒng)查漏補(bǔ)缺的前端工程師。整張卷子分為選擇題、簡答題和手寫編程題三大模塊覆蓋范圍基本落在四個方向JavaScript語言特性、瀏覽器工作原理、HTML/CSS布局、網(wǎng)絡(luò)協(xié)議與Web安全。下面我按這幾個方向把命題邏輯拆開結(jié)合我當(dāng)時備考和實(shí)際面試的經(jīng)驗來聊。1. 卷子結(jié)構(gòu)與考察方向拆解1.1 整體命題邏輯為什么考這些不考那些先給沒參加過校招的朋友科普一下筆試設(shè)計的基本邏輯。校招筆試不是用來挑“最強(qiáng)天才”的而是用來做第一輪粗篩的。面試官一天要面幾十個人筆試必須用盡量少的題目把“基礎(chǔ)不牢靠”和“基礎(chǔ)扎實(shí)有潛力”這兩類人快速分開。所以在有限時間內(nèi)命題人會優(yōu)先選擇覆蓋面廣、區(qū)分度高的知識點(diǎn)而不是某個小眾框架的上層用法。歡聚時代這份A卷的題型分布在當(dāng)年很有代表性模塊題型題量占比考察目標(biāo)JavaScript語言特性單選/多選/手寫約40%語法理解、運(yùn)行機(jī)制、編碼能力瀏覽器原理單選/簡答約20%渲染過程、緩存、異步事件HTML/CSS布局單選/手寫約20%布局能力、盒模型、選擇器網(wǎng)絡(luò)與安全單選/簡答約10%HTTP協(xié)議、緩存、跨域與XSS算法基礎(chǔ)手寫約10%數(shù)組/字符串處理、邏輯思維從這個配比能看出來JavaScript是絕對核心這也是正常的。前端崗位的日常工作本質(zhì)就是跟JS打交道框架可以換語言層面的功底騙不了人。瀏覽器原理占20%因為它決定了你對線上問題的排查能力——為什么頁面白屏、為什么滾動卡頓、為什么資源重復(fù)加載這些都在瀏覽器原理的知識范疇里。歡聚時代本身是做直播和音視頻業(yè)務(wù)的對實(shí)時交互體驗要求很高。所以這份卷子里異步編程、事件循環(huán)、性能優(yōu)化相關(guān)的題占比明顯高于一般公司。這也是命題邏輯里很有意思的一層企業(yè)的業(yè)務(wù)形態(tài)會直接影響考點(diǎn)側(cè)重。備考時候如果能提前了解目標(biāo)公司的業(yè)務(wù)方向大致能猜出它更重視哪一塊。1.2 難度定位基礎(chǔ)題的“細(xì)節(jié)陷阱”A卷的整體難度屬于當(dāng)年的主流水平?jīng)]有特別刁鉆的題但細(xì)節(jié)題很密。什么叫細(xì)節(jié)題就是看起來每個選項都認(rèn)識但考的是你平時會不會真正去思考的那些點(diǎn)。舉個例子JS基礎(chǔ)經(jīng)常出現(xiàn)這種題判斷typeof null、typeof function(){}、typeof []分別是什么。如果你平時寫代碼沒想過這類問題光靠感覺答題很容易在“object”和“function”之間猶豫。再比如0.1 0.2 0.3的結(jié)果為什么是false這背后是浮點(diǎn)數(shù)精度問題屬于計算機(jī)基礎(chǔ)范疇但很多前端同學(xué)直到工作兩三年都沒搞明白。細(xì)節(jié)題的價值在于它不是死記硬背能答好的它反映的是候選人平時會不會深入思考。面試官真正想看的不是你記住了多少結(jié)論而是你有沒有建立“遇到問題追到原理層面”的習(xí)慣。2. JavaScript核心考點(diǎn)扎實(shí)功底是分水嶺2.1 閉包、作用域與this指向必考且拉開差距JavaScript這部分幾乎可以確定會出現(xiàn)閉包和作用域的題。最經(jīng)典的代碼題長這樣for (var i 0; i 5; i) { setTimeout(function() { console.log(i); }, 100); }問輸出結(jié)果是什么。答案不是0、1、2、3、4而是連續(xù)5個5。考點(diǎn)有兩個一是var沒有塊級作用域循環(huán)里創(chuàng)建的i是同一個變量二是setTimeout的回調(diào)是宏任務(wù)要等同步代碼執(zhí)行完才運(yùn)行而那時循環(huán)已經(jīng)結(jié)束i已經(jīng)變成5。這道題的改法在當(dāng)年是手寫題的??透某蒷et聲明或者用閉包傳參for (var i 0; i 5; i) { (function(j) { setTimeout(function() { console.log(j); }, 100); })(i); }用IIFE把每次循環(huán)的i值作為參數(shù)固化到閉包里。后來ES6普及l(fā)et成了首選方案。但筆試如果只寫let建議把原理講清楚單純換成let說明你只是知道結(jié)論不一定理解背后的作用域機(jī)制。this指向的題也幾乎是必考的。常見考法var name window; var obj { name: obj, getName: function() { return this.name; } }; var fn obj.getName; console.log(fn()); console.log(obj.getName());第一行輸出window第二行輸出obj。原因在于this的綁定規(guī)則誰調(diào)用函數(shù)this就指向誰。fn()是全局調(diào)用this指向windowobj.getName()是對象方法調(diào)用this指向obj。當(dāng)年還有一道擴(kuò)充題把所有情況串起來普通函數(shù)調(diào)用、對象方法調(diào)用、call/apply/bind調(diào)用、new調(diào)用、箭頭函數(shù)。每寫一個調(diào)用方式后面就跟一個輸出結(jié)果。這類題考察的是你對this綁定優(yōu)先級有沒有完整的理解——new bind call/apply 對象方法調(diào)用 默認(rèn)綁定。箭頭函數(shù)則特殊它沒有自己的this往外層作用域找。2.2 原型鏈與繼承手寫題的決定性題目原型和繼承是2018年前后前端筆試的“壓軸常客”。選擇題考instanceof的判斷結(jié)果手寫題大概率讓你實(shí)現(xiàn)一個繼承。先看選擇題經(jīng)典function Parent() { this.name parent; } function Child() { this.age 1; } Child.prototype new Parent(); var child new Child(); console.log(child instanceof Parent); console.log(child instanceof Child); console.log(child.constructor Parent);前兩個輸出都是true因為child的原型鏈上既有Child.prototype也能通過Child.prototype內(nèi)部的[[Prototype]]找到Parent.prototype。第三個容易丟分child.constructor此時指向的是Parent。原因在于Child.prototype被整個重新賦值為new Parent()這個對象身上的constructor屬性繼承自Parent.prototype指向Parent。手寫繼承題當(dāng)時最穩(wěn)妥的回答是“組合繼承”和“寄生組合繼承”。我建議優(yōu)先掌握寄生組合繼承它兼容了原型鏈繼承和構(gòu)造函數(shù)繼承的優(yōu)點(diǎn)也是ES6 class繼承在Babel轉(zhuǎn)譯后的實(shí)際實(shí)現(xiàn)方式function Parent(name) { this.name name || parent; this.colors [red, blue]; } Parent.prototype.sayHello function() { console.log(Hello this.name); }; function Child(name, age) { Parent.call(this, name); this.age age; } Child.prototype Object.create(Parent.prototype); Child.prototype.constructor Child; Child.prototype.sayAge function() { console.log(this.age); };這里核心是Object.create(Parent.prototype)它創(chuàng)建一個以父類原型為新對象原型的對象再賦給Child.prototype。這樣避免了直接new Parent()帶來的多余屬性也保證了instanceof判斷正確。最后一定要補(bǔ)Child.prototype.constructor Child修復(fù)constructor指向否則后面判斷對象構(gòu)造函數(shù)會出問題。2.3 事件循環(huán)與異步機(jī)制直播業(yè)務(wù)公司的重點(diǎn)題前面提到歡聚時代做直播異步處理在業(yè)務(wù)里非常重要。所以這套卷子里事件循環(huán)相關(guān)的輸出題占比不低而且難度逐年上升。基礎(chǔ)題是經(jīng)典的宏任務(wù)/微任務(wù)輸出順序console.log(A); setTimeout(function() { console.log(B); }, 0); Promise.resolve().then(function() { console.log(C); }); console.log(D);輸出順序是A、D、C、B。原因是同步代碼先全部執(zhí)行完然后執(zhí)行微任務(wù)隊列Promise.then回調(diào)最后才是宏任務(wù)隊列setTimeout回調(diào)。這個知識點(diǎn)在當(dāng)年很多人會搞反以為setTimeout的0毫秒是立即執(zhí)行實(shí)際上0毫秒只是表示“盡快加入宏任務(wù)隊列”并不能插隊到微任務(wù)前面。進(jìn)階版會疊加async/awaitasync function test() { console.log(start); await Promise.resolve(); console.log(end); } test(); console.log(sync);這里的輸出需要理解await的語義。await右側(cè)的表達(dá)式會立即執(zhí)行遇到await時test函數(shù)暫停后續(xù)代碼作為微任務(wù)排隊。所以輸出是start、sync、end。遇到這類題我建議在草稿紙上畫一下任務(wù)隊列先寫同步任務(wù)再畫一個微任務(wù)隊列和一個宏任務(wù)隊列遇到Promise.then和await后面代碼就往微任務(wù)隊列塞遇到setTimeout就往宏任務(wù)隊列塞。按這個流程走基本不會錯。而且這個思路本身也是實(shí)際排查線上問題時需要的能力——比如為什么某個接口回調(diào)比另一個后執(zhí)行為什么圖表渲染在滾動事件之后才更新本質(zhì)都是任務(wù)隊列的順序問題。3. 瀏覽器原理與網(wǎng)絡(luò)一線開發(fā)的基本功3.1 從輸入URL到頁面渲染簡答題的經(jīng)典框架這類簡答題幾乎每個前端筆試都會出現(xiàn)A卷也不例外。題目一般長這樣“描述在瀏覽器地址欄輸入網(wǎng)址并回車到頁面完整顯示中間經(jīng)歷了哪些步驟”完整的回答框架是URL解析瀏覽器判斷輸入是合法URL還是搜索關(guān)鍵詞補(bǔ)全協(xié)議和路徑。DNS解析把域名解析成IP地址依次查找瀏覽器緩存、系統(tǒng)緩存、路由器緩存、根DNS服務(wù)器。建立TCP連接三次握手客戶端發(fā)送SYN、服務(wù)器返回SYNACK、客戶端回復(fù)ACK。發(fā)起HTTP請求構(gòu)造請求行、請求頭、請求體通過TCP發(fā)送。服務(wù)器處理并返回后端處理請求返回響應(yīng)報文和響應(yīng)體。瀏覽器解析渲染解析HTML構(gòu)建DOM樹、解析CSS構(gòu)建CSSOM樹、兩者合成渲染樹、計算布局、繪制并合成圖層。斷開連接短連接四次揮手或者保持長連接復(fù)用。我當(dāng)時復(fù)習(xí)這道題時專門整理了一段口訣“解析域名、建立連接、發(fā)送請求、解析響應(yīng)、構(gòu)建渲染、執(zhí)行腳本”。回答時要注意兩點(diǎn)一是有邏輯層次從網(wǎng)絡(luò)層面到渲染層面逐步展開二是要提到關(guān)鍵細(xì)節(jié)比如渲染過程中遇到script標(biāo)簽會阻塞DOM解析所以腳本一般放body底部或加defer/async。這個細(xì)節(jié)在筆試?yán)锖苋菀妆缓雎缘∏∈敲嬖嚬倥袛嗄恪罢娴睦斫狻边€是“背過答案”的分界線。3.2 回流重繪與性能優(yōu)化面試加分項渲染原理延伸出來的高頻題就是回流reflow/layout和重繪repaint。筆試選擇題會這么考以下哪些操作會觸發(fā)回流選項一般包括修改元素寬度、修改顏色、讀取offsetWidth、刪除DOM節(jié)點(diǎn)、改變窗口大小、添加類名。這里有個容易踩坑的點(diǎn)很多人以為“讀取”不會觸發(fā)回流但offsetWidth、clientWidth、getComputedStyle這類讀取操作會強(qiáng)制瀏覽器同步完成布局計算然后才返回值。原因是瀏覽器為了性能會合并多個樣式修改延遲一次統(tǒng)一回流但當(dāng)你讀取布局屬性時它沒辦法給個“過期”的值只能先算一遍。關(guān)于回流的優(yōu)化手段我在實(shí)際項目中驗證有效的有幾條把需要多次修改的DOM操作合并用document.createDocumentFragment()或者一次性改class。用transform代替top/left做動畫因為transform走合成器不觸發(fā)回流重繪。對需要高頻觸發(fā)的事件如scroll、resize做防抖或節(jié)流。需要讀取布局屬性時盡量一次性讀取緩存不要反復(fù)讀。用will-change或contain屬性減少渲染范圍。這些優(yōu)化思路在筆試?yán)锟梢宰鳛楹喆痤}的補(bǔ)充答案在面試中則是實(shí)打?qū)嵉募臃猪?。尤其是“transform為什么比top高性能”這個追問能答出“合成器在GPU上處理不經(jīng)過布局和繪制”就算過關(guān)。3.3 HTTP緩存機(jī)制與狀態(tài)碼細(xì)節(jié)決定成敗網(wǎng)絡(luò)協(xié)議部分A卷側(cè)重緩存機(jī)制。核心考點(diǎn)是強(qiáng)緩存和協(xié)商緩存的完整鏈路。強(qiáng)緩存有兩個頭字段ExpiresHTTP/1.0和Cache-ControlHTTP/1.1。Cache-Control: max-age3600表示從請求時刻起緩存1小時。而Expires是絕對時間如果客戶端時間和服務(wù)器時間不一致緩存就會失效所以現(xiàn)代項目基本以Cache-Control為主。協(xié)商緩存也有兩個頭字段Last-Modified/If-Modified-Since按文件修改時間判斷ETag/If-None-Match按文件內(nèi)容指紋判斷。兩者區(qū)別在于修改時間只能精確到秒同一秒內(nèi)多次修改就判斷不出來ETag是內(nèi)容哈希更精確但計算成本更高。服務(wù)器返回304表示“你可以用本地緩存”這次請求不返回響應(yīng)體。緩存題還有一種變體無緩存刷新CtrlF5和普通刷新F5有什么區(qū)別普通刷新會帶上If-Modified-Since或If-None-Match走協(xié)商緩存強(qiáng)制刷新則會繞過強(qiáng)緩存和協(xié)商緩存直接向服務(wù)器發(fā)新請求。狀態(tài)碼的考點(diǎn)集中在200成功、301永久重定向、302臨時重定向、304緩存命中、403禁止訪問、404找不到資源、500服務(wù)器內(nèi)部錯誤、502網(wǎng)關(guān)錯誤。還有個容易混淆的301和302的區(qū)別一個是永久遷移搜索引擎會更新鏈接權(quán)重一個是臨時跳轉(zhuǎn)。4. CSS/HTML應(yīng)用題布局細(xì)節(jié)最容易翻車4.1 經(jīng)典布局的實(shí)現(xiàn)與對比CSS部分A卷還是老套路盒模型、居中布局、兩欄/三欄布局、flex布局。這些題目本身不難但很多人因為平時寫代碼依賴框架自己手寫的時候反而不利索。盒模型的考點(diǎn)是標(biāo)準(zhǔn)盒模型和IE盒模型的區(qū)別。標(biāo)準(zhǔn)盒模型的width只包含內(nèi)容區(qū)padding和border是額外加上的IE盒模型的width包含內(nèi)容、padding和border。box-sizing: border-box就是把元素設(shè)置為IE盒模型這在移動端布局里非常實(shí)用因為子元素設(shè)置百分比寬度后再加padding不會超出父容器。垂直水平居中方案是手寫題常客。當(dāng)年我給出的標(biāo)準(zhǔn)答案至少要有四種/* 方案一flex */ .parent { display: flex; justify-content: center; align-items: center; } /* 方案二絕對定位 transform */ .parent { position: relative; } .child { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } /* 方案三margin: auto 配合絕對定位 */ .child { position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto; } /* 方案四table-cell */ .parent { display: table-cell; text-align: center; vertical-align: middle; }筆試要求寫兩到三種面試則希望你能說出每種方案的局限性和適用場景。比如flex方案要求父容器高度確定絕對定位transform要求父容器是定位父級table-cell適合舊瀏覽器兼容。能講出這些才算真正掌握了。三欄布局是CSS布局題里的“定番題”。圣杯布局和雙飛翼布局的思路都是左右兩欄定寬、中間自適應(yīng)區(qū)別在于圣杯用padding騰位置雙飛翼用中間欄內(nèi)部的margin騰位置。我當(dāng)時練習(xí)時做了個判斷雙飛翼的邏輯更直接不容易寫錯但兩者原理都要會說。2023年以后flex和grid已經(jīng)全面普及這類傳統(tǒng)方案在筆試中的出現(xiàn)頻率有所下降但一旦出現(xiàn)如果答不上來會比較尷尬。4.2 選擇器優(yōu)先級與BFC細(xì)節(jié)里的高分題選擇器優(yōu)先級計算是選擇題高發(fā)區(qū)。記一個權(quán)重模型內(nèi)聯(lián)樣式1000id選擇器100類/屬性/偽類10元素/偽元素1。比較時按位相加大的優(yōu)先。!important優(yōu)先級最高但慎用因為它會破壞級聯(lián)規(guī)則后期維護(hù)很難覆蓋。BFC塊級格式化上下文是這一部分的另一個考點(diǎn)。選擇題一般問你哪個屬性會觸發(fā)BFC或者BFC能解決什么問題。觸發(fā)BFC的條件有根元素、float不為none、position為absolute或fixed、display為inline-block或table-cell、overflow不為visible、display: flow-root。BFC解決的核心問題有三個清除浮動子元素浮動導(dǎo)致父容器高度塌陷時給父容器創(chuàng)建BFC它能包含浮動子元素。防止margin合并垂直方向上相鄰元素的margin會合并把其中一個元素包進(jìn)BFC可以阻止合并。阻止元素被浮動元素覆蓋BFC區(qū)域不會與浮動元素重疊。我當(dāng)時手寫過一段清除浮動的兼容寫法.clearfix::after { content: ; display: block; clear: both; }這個方案比給父容器加overflow: hidden更安全因為后者在某些場景下會裁掉下拉菜單之類的溢出內(nèi)容。筆試簡答題里能寫出這種細(xì)節(jié)是加分項。5. 手寫編程題解題過程與邊界處理5.1 防抖與節(jié)流必考手寫題要注意邊界手寫題第一道高頻題就是防抖debounce和節(jié)流throttle。題目有時候只讓你寫“實(shí)現(xiàn)防抖函數(shù)”有時候更雞賊要求“實(shí)現(xiàn)防抖函數(shù)并說明它和節(jié)流的區(qū)別”。防抖的核心邏輯是事件觸發(fā)后設(shè)定一個等待時間等待時間內(nèi)再次觸發(fā)則重新計時只有停止觸發(fā)后才執(zhí)行一次。function debounce(fn, delay) { let timer null; return function(...args) { if (timer) clearTimeout(timer); timer setTimeout(() { fn.apply(this, args); timer null; }, delay); }; }我當(dāng)時寫這段時容易漏兩個點(diǎn)一是this的綁定問題回調(diào)里直接用fn()會導(dǎo)致this丟失必須用fn.apply(this, args)把this傳進(jìn)去二是clearTimeout之后timer要置null防止連續(xù)調(diào)用次數(shù)的累積污染。節(jié)流的核心邏輯是固定時間間隔內(nèi)只執(zhí)行一次。function throttle(fn, interval) { let last 0; return function(...args) { let now Date.now(); if (now - last interval) { last now; fn.apply(this, args); } }; }還有一種用定時器實(shí)現(xiàn)的版本區(qū)別在于末尾是否多執(zhí)行一次。筆試時如果能寫出“第一次觸發(fā)立即執(zhí)行結(jié)束后再補(bǔ)充執(zhí)行一次”的版本并解釋兩種時間的差異基本就是滿分答案。5.2 數(shù)組去重與扁平化一題多解展示功底數(shù)組去重是另一道高頻手寫題。直接用Set是最簡潔的const unique (arr) [...new Set(arr)];但只寫一行可能讓面試官覺得你只會API所以建議同時寫出filter加indexOf的方案const unique (arr) arr.filter((item, index) arr.indexOf(item) index);這道題想拿高分關(guān)鍵在于主動說清楚不同方案的適用場景。比如Set方案無法區(qū)分1和1其實(shí)不是Set使用SameValueZero比較1和1是不同的值NaN會被正確去重這是它比indexOf方案強(qiáng)的地方。但如果數(shù)組元素是對象Set做的是引用比較無法按對象內(nèi)容去重這時候需要自己寫reduce加Map組合方案。數(shù)組扁平化也是一道經(jīng)典題。ES6有Array.prototype.flat(depth)但手寫實(shí)現(xiàn)要考察遞歸能力function flatten(arr) { return arr.reduce((prev, cur) { return prev.concat(Array.isArray(cur) ? flatten(cur) : cur); }, []); }注意遞歸方案對嵌套特別深的數(shù)組可能導(dǎo)致調(diào)用棧溢出。如果面試追問可以用?;虻鷣韮?yōu)化把深度控制在O(n)空間。我在寫這段時會把concat和展開運(yùn)算符[...prev, ...]的性能差異也提一下這個細(xì)節(jié)能體現(xiàn)你對代碼性能的敏感度。5.3 手寫發(fā)布訂閱Event Bus的常規(guī)操作發(fā)布訂閱模式是這類筆試?yán)锉容^有區(qū)分度的手寫題。題目通常說實(shí)現(xiàn)一個簡單的EventEmitter支持on、once、off、emit四個方法。class EventEmitter { constructor() { this.events new Map(); } on(name, fn) { if (!this.events.has(name)) { this.events.set(name, []); } this.events.get(name).push(fn); } once(name, fn) { const wrapper (...args) { fn.apply(this, args); this.off(name, wrapper); }; this.on(name, wrapper); } off(name, fn) { if (!this.events.has(name)) return; if (!fn) { this.events.delete(name); return; } const fns this.events.get(name); const index fns.indexOf(fn); if (index -1) fns.splice(index, 1); } emit(name, ...args) { const fns this.events.get(name); if (fns fns.length) { fns.slice().forEach(fn fn.apply(this, args)); } } }這題有幾個容易丟分的細(xì)節(jié)once注冊的回調(diào)執(zhí)行后要立即解綁所以外面包了一層wrapperoff時必須能用wrapper從列表中刪除emit時遍歷回調(diào)數(shù)組要用slice()淺拷貝一份防止回調(diào)內(nèi)部調(diào)用off導(dǎo)致正在遍歷的數(shù)組被修改off如果不傳具體函數(shù)應(yīng)該清空該事件的所有回調(diào)。這些邊界情況在筆試?yán)锊灰欢ㄒ笕慷紝懗鰜淼茉诎准埳蠈懲暾f明你的工程意識是有的。6. 筆試常見丟分點(diǎn)與復(fù)習(xí)路線建議6.1 我觀察到的三大丟分原因先說丟分點(diǎn)。我身邊參加過這份筆試的同學(xué)回來說得最多的不是“題不會”而是“會但是沒寫全”。這與校招筆試的評分方式密切相關(guān)手寫題往往是按點(diǎn)給分的少了邊界處理、少了原理說明扣分都很正常。第一個丟分點(diǎn)是手寫題沒有處理邊界條件。比如實(shí)現(xiàn)防抖函數(shù)時不處理this綁定實(shí)現(xiàn)EventEmitter時不考慮once回調(diào)的自動解綁數(shù)組去重時忽略NaN的去重需求。這些邊界情況在真實(shí)項目中很重要筆試評分時也是區(qū)分“背過模板”和“真正理解”的關(guān)鍵判據(jù)。第二個丟分點(diǎn)是選擇題里模棱兩可的選項過于糾結(jié)導(dǎo)致時間分配失衡。一張卷子選擇題大概30道建議每道不超過1分半不會的先跳過把時間留給手寫題。手寫題分值高、區(qū)分度大寫不全基本就等于丟了這部分分?jǐn)?shù)。第三個丟分點(diǎn)是簡答題只寫條目不解釋。問“瀏覽器渲染過程”如果只寫“構(gòu)建DOM樹、構(gòu)建CSSOM樹、合成渲染樹”三行分?jǐn)?shù)一定不高。閱卷人想看的是你對每一步的理解比如構(gòu)建DOM樹遇到script怎么處理、CSSOM樹會不會阻塞渲染、合成樹和渲染樹的區(qū)別。解釋得越細(xì)越能證明你真的做過頁面性能排查。6.2 針對性的復(fù)習(xí)路線從筆試到面試的延續(xù)備考前端校招我的建議是分三條線并行走。第一條線是語言基礎(chǔ)。把JavaScript的核心機(jī)制過一遍重點(diǎn)不是背API而是理解原理作用域鏈、閉包、原型鏈、this綁定規(guī)則、async/await和Promise的執(zhí)行順序。推薦的方式是每個知識點(diǎn)都自己寫一個小例子在控制臺驗證輸出再嘗試解釋原因。第二條線是瀏覽器和網(wǎng)絡(luò)。推薦動手做一個“輸入URL到頁面渲染”的完整實(shí)驗在網(wǎng)絡(luò)面板里觀察強(qiáng)緩存和協(xié)商緩存的請求頭變化打開Performance面板看回流重繪和長任務(wù)。這些實(shí)踐能幫你把抽象概念和真實(shí)現(xiàn)象對應(yīng)起來。第三條線是刷手寫題。防抖節(jié)流、數(shù)組去重、數(shù)組扁平化、實(shí)現(xiàn)Promise、深拷貝、EventEmitter這些是前端筆試的常青題目。我的建議是每個題至少手寫三遍第一遍看答案后默寫第二遍閉卷獨(dú)立寫第三遍嘗試優(yōu)化和擴(kuò)展。還有一條面試層面的建議筆試題目通常會在面試中被追問。答完一道手寫題面試官很可能會問“這段代碼有什么問題”“如果并發(fā)調(diào)用會怎么樣”“內(nèi)存方面有沒有考慮”。所以復(fù)習(xí)時不要只滿足于寫出來要有意識地思考代碼的邊界和優(yōu)化空間。寫在最后回看歡聚時代這份2018年的web前端A卷我覺得它最大的參考價值不是題目本身而是它對基礎(chǔ)知識的重視程度。很多人在校招季容易陷入一種焦慮拼命追最新框架、最新特性卻被一張考察閉包和事件循環(huán)的卷子教做人。事實(shí)是技術(shù)棧會變框架會過時但JavaScript語言的底層機(jī)制、瀏覽器的工作原理、HTTP協(xié)議的交互邏輯這些才是前端工程師真正的護(hù)城河。如果你正在準(zhǔn)備校招建議把這份卷子當(dāng)作一面鏡子逐個考點(diǎn)自查哪一塊不熟悉就補(bǔ)哪一塊。我在實(shí)際備考中感受到最有效的學(xué)習(xí)方式就是把每一個考點(diǎn)講給別人聽講不清楚的地方就是你的盲區(qū)。希望這份拆解能幫你少走一些我當(dāng)時走過的彎路。