
百度2018校招Web前端工程師筆試卷第三批這套卷子在當年的求職圈里討論度相當高。很多人拿到卷子的第一反應是“題量真大”第二反應是“基礎(chǔ)知識覆蓋面太廣了”。我當年刷完這套題之后對照答案重新梳理了一遍知識點發(fā)現(xiàn)它表面上是一份筆試卷實際上是一張非常清晰的前端能力圖譜HTML/CSS、JavaScript核心機制、網(wǎng)絡(luò)與瀏覽器原理、以及一點工程化和框架認知幾乎涵蓋了校招級別前端工程師應該具備的全部底層能力。就算放到現(xiàn)在這些考點依然是大廠面試的主干。這篇文章我不打算貼一份標準答案就完事而是從“出題人想考察什么”的角度把這份卷子拆開揉碎講一遍。你如果正在準備校招或者剛轉(zhuǎn)行前端想系統(tǒng)補基礎(chǔ)又或者工作兩三年后想查漏補缺這篇文章都能幫你把零散的知識串成體系。我會結(jié)合當年的答題思路、踩過的坑以及這幾年在工作里回顧這些知識點的新理解盡量做到既講清楚題目本身也講清楚背后的原理和實戰(zhàn)場景。1. 試卷整體拆解百度的前端筆試到底在篩什么人1.1 題量與題型分布藏著怎樣的信號2018年百度校招第三批前端筆試題題型基本是單選、多選、填空和編程題的組合整體時間一般是90到120分鐘。單純看題量不算特別夸張但它的坑在于覆蓋面太寬。你不會遇到特別冷門的偏題怪題可每個方向都會出幾道你要是平時只刷框架不重視基礎(chǔ)很容易在選擇題上翻車。從題型分布來看出題人明顯在強調(diào)“基礎(chǔ)廣度優(yōu)先關(guān)鍵深度兜底”。選擇填空覆蓋了HTML標簽語義、CSS屬性細節(jié)、JavaScript語言特性、瀏覽器緩存、HTTP狀態(tài)碼、數(shù)組字符串API這些散點編程題則集中在手寫代碼上比如數(shù)組去重、防抖節(jié)流、深拷貝、事件委托、簡單的算法題。這種搭配傳遞出來的信號很直接百度要的校招生不需要你有多深的框架源碼功底但你必須把JavaScript和瀏覽器底層搞明白因為這是后續(xù)所有上層框架的基礎(chǔ)。另外多選題也很有殺傷力。多選題少選不得分多選也不得分這意味著你不能靠模糊記憶蒙混過關(guān)。比如一道題問哪些操作會觸發(fā)回流選項里有修改DOM樣式、讀取offsetWidth、添加class、修改瀏覽器窗口大小如果你只記得“修改樣式會觸發(fā)回流”而不知道“讀取offsetWidth也會強制同步布局”這道題就妥妥丟分。這種細節(jié)恰恰是筆試篩選的意義所在它能把“用過”和“理解”的人區(qū)分開。1.2 從試卷反推百度前端工程師的能力模型順著試卷往前推你會發(fā)現(xiàn)當年百度前端校招的用人標準非常清晰可以總結(jié)成四層能力模型。第一層是HTML/CSS的布局與還原能力。這一層對應的是頁面能不能寫出來、能不能在不同屏幕下站穩(wěn)。第二層是JavaScript語言機制的深入理解。原型鏈、閉包、上下文、事件循環(huán)這些不是靠背概念就能答對的必須真正寫過、debug過才能理解。第三層是瀏覽器與網(wǎng)絡(luò)原理。緩存、渲染、跨域、HTTP這些決定了你寫的頁面上線后性能如何、穩(wěn)定性如何。第四層是工程化與團隊協(xié)作意識。模塊化、組件化、構(gòu)建工具、代碼規(guī)范這些決定了你能否在真實團隊里高效產(chǎn)出。這套能力模型到今天依然適用。ES6、React/Vue、TypeScript、工程化工具鏈雖然迭代了好幾輪但底層的語言機制和瀏覽器原理沒有變。你看現(xiàn)在面經(jīng)里的高頻題什么事件循環(huán)輸出順序、什么閉包陷阱、什么瀏覽器緩存策略本質(zhì)上和2018年這套筆試卷是同源的。所以這套卷子不只是歷史考古它值得反復做一遍。2. CSS核心考點盒模型、布局方案與渲染細節(jié)2.1 盒模型和box-sizing一道必拿分的基礎(chǔ)題CSS部分幾乎必考盒模型。標準盒模型里width只包含content寬度padding和border都要額外計算IE盒模型里width包含content、padding、border??挤ㄍǔJ墙o出一個div的width、padding、border問實際渲染寬度是多少。很多人會在這里丟分因為平時寫代碼都用box-sizing: border-box把兩種模型統(tǒng)一了反倒忘了默認的content-box到底怎么算。簡單記法標準盒模型的內(nèi)容寬度固定加padding和border會把盒子撐大border-box盒模型的總寬度固定加padding和border會壓縮內(nèi)容區(qū)。如果題目給的是盒子的總寬度問你content區(qū)多寬你就需要用總寬度減去左右padding和左右border。這道題的本質(zhì)是考察你對CSS尺寸計算鏈路是否清楚。實際開發(fā)中我建議全局統(tǒng)一用border-box因為絕大多數(shù)場景下我們想要的都是“設(shè)定一個固定寬度的容器往里加padding不會溢出布局”。但筆試面試時你必須能脫離這個習慣默認值準確說出兩種模型的差異。另外注意margin是不算進盒子的實際渲染寬度的它影響的是外部占位。盒模型周圍的兄弟元素計算、負margin的邊距合并這些衍生題也值得順帶復習一遍margin collapse也是??键c。2.2 布局題從圣杯、雙飛翼到Flex和Grid布局操作題在當年筆試里的出鏡率極高。最常見的問法是“如何實現(xiàn)左側(cè)固定寬度、右側(cè)自適應”或者“如何實現(xiàn)左右固定、中間自適應三欄布局”。這類題目官方答案通常要求寫出多種方案至少兩三種。當年大家最愛寫的是float加margin的方案。左側(cè)固定寬度200px左浮動右側(cè)容器margin-left: 200px就能實現(xiàn)右自適應。這個方法簡單可靠但有一個坑右側(cè)容器內(nèi)部再創(chuàng)建BFC或者清浮動否則高度計算會出問題。進階版本是圣杯布局和雙飛翼布局它們解決的核心問題都是“中間列優(yōu)先渲染”。思路都是讓中間列寬度100%并浮動在第一位然后利用負margin把左右兩列移動到正確位置。區(qū)別是圣杯布局用父容器padding預留位置雙飛翼布局用中間列內(nèi)部子元素的margin來騰位置。現(xiàn)在寫代碼當然首選Flex和Grid。Flex實現(xiàn)左右固定中間自適應的代碼非常簡潔.container { display: flex; } .left { width: 200px; } .right { flex: 1; }Grid更猛一行聲明搞定.container { display: grid; grid-template-columns: 200px 1fr; }但筆試如果只寫flex一種方案閱卷人會認為你知識面偏窄。我建議你準備一套“組合拳”先寫floatmargin再寫flex再寫grid如果空間允許還可以補充positionmargin的方案。寫的時候順手說明每種方案的優(yōu)缺點比如float方案有清除浮動的麻煩flex方案在低版本瀏覽器兼容性不好grid對瀏覽器版本要求更高。這樣答題既展示深度又展示工程意識。2.3 BFC一個高頻但常被誤解的概念BFC塊級格式化上下文幾乎每份前端筆試卷都會碰到。但很多人對BFC的理解停留在“可以清除浮動”這一個結(jié)論上一旦題目換一個角度就懵了。BFC本質(zhì)上是一種獨立的渲染區(qū)域它內(nèi)部元素的布局不會影響外部元素。觸發(fā)BFC的條件包括float不為none、overflow不為visible、display為inline-block/flex/grid/table-cell等、position為absolute或fixed。BFC最常見的三個應用場景清除浮動、防止margin穿透、防止元素被浮動元素覆蓋。筆試里喜歡考第一種場景。比如父元素內(nèi)有一個浮動子元素父元素高度塌陷問如何修復。正確答案可以是父元素加overflow: hidden也可以是父元素自身也創(chuàng)建BFC或者用clearfix。這里有個小細節(jié)值得注意overflow: hidden能觸發(fā)BFC但它同時也可能隱藏掉你想溢出的內(nèi)容比如下拉菜單、tooltip之類就不能用。實際工作中我更推薦clearfix方案既不影響可見性又穩(wěn)定.clearfix::after { content: ; display: block; clear: both; }還有一道經(jīng)典衍生題兩個垂直相鄰的div各自margin都是20px最終間距是20px而不是40px為什么這就是margin collapse根因是同一個BFC內(nèi)相鄰塊元素的margin會發(fā)生折疊。解法也簡單把其中一個包一層新的BFC容器讓它們不在同一個BFC里。2.4 垂直居中一道能看出基本功的“小抄題”垂直居中在筆試和面試中出現(xiàn)頻率極高題目一般是“一個不定高的盒子如何在一個容器里水平垂直居中寫出盡可能多的方案”。這道題考驗的就是你對CSS布局方案的積累量。我整理一下我常用的六種方案從老到新第一利用table-cell和vertical-align: middle父容器display: table-cellvertical-align: middle子元素就能垂直居中。第二利用絕對定位加負margin前提是知道子元素寬高。第三利用絕對定位加margin: auto子元素設(shè)置top/left/right/bottom都為0然后margin: auto在絕對定位下這種寫法能實現(xiàn)居中而且不需要知道寬高。第四利用transform: translate(-50%, -50%)配合絕對定位不要求寬高已知。第五flex布局加align-items和justify-content。第六grid布局類似。每種方案適用場景不同。負margin方法在已知寬高的場景下性能好、兼容性好transform方法雖然方便但會創(chuàng)建一個層疊上下文可能影響z-index行為flex是當下主流但對IE10以下不友好。你如果筆試時能把六種方案都寫出來再補一句“實際生產(chǎn)中通常用flex舊項目根據(jù)兼容性要求選table或position”這道題基本穩(wěn)了。3. JavaScript核心機制原型、上下文、閉包與異步3.1 原型與原型鏈一張必背的底層知識網(wǎng)JavaScript筆試題里原型鏈基本是必考項。常見的考法有兩類一類是判斷輸出結(jié)果給你一串對象和繼承關(guān)系問obj.xxx的輸出另一類是手寫實現(xiàn)繼承。先說第一類。必須搞清楚幾個關(guān)鍵結(jié)論函數(shù)的prototype屬性指向它的原型對象通過new調(diào)用構(gòu)造函數(shù)創(chuàng)建的實例其__proto__指向構(gòu)造函數(shù)的prototypeprototype對象本身也有__proto__逐級往上最終指向Object.prototype再往上就是null。訪問一個屬性時JS會沿著實例的__proto__一路向上找直到找到或者到達null。這就是原型鏈的查找機制。一個很容易錯的細節(jié)函數(shù)的__proto__和prototype不是一回事。普通函數(shù)是Function的實例所以函數(shù)的__proto__指向Function.prototype而函數(shù).prototype是函數(shù)作為構(gòu)造函數(shù)時的原型對象。類繼承extends本質(zhì)也是把子類原型的__proto__指向父類原型。第二類手寫繼承組合寄生式繼承是標準答案它既避免了共享屬性的坑又能保持原型鏈完整function inherit(Child, Parent) { Child.prototype Object.create(Parent.prototype); Child.prototype.constructor Child; }Object.create創(chuàng)建了一個以Parent.prototype為原型的對象這樣Child實例就能通過原型鏈訪問Parent原型上的方法而修改Child.prototype不會影響Parent.prototype。同時把constructor指回Child避免原型上的constructor指向錯誤。這段代碼值得默寫幾乎年年考。3.2 this的指向一道多選題能刷掉一批人this指向是多選題高頻考點。規(guī)則并不復雜但大部分人容易記混。核心邏輯一句話this的指向取決于函數(shù)調(diào)用時的調(diào)用方式而不是定義方式。分情況拆開說。普通函數(shù)直接調(diào)用非嚴格模式下this指向全局對象嚴格模式下是undefined。對象方法調(diào)用this指向調(diào)用者。setTimeout回調(diào)里的函數(shù)this指向全局對象所以在回調(diào)里用this會拿不到對象實例需要用箭頭函數(shù)或者提前保存var self this。構(gòu)造函數(shù)通過new調(diào)用this指向新創(chuàng)建的實例。箭頭函數(shù)本身不創(chuàng)建this它的this繼承自外層詞法作用域。call、apply、bind可以顯式指定this。實踐中最容易踩坑的是事件處理器里。比如一個按鈕綁定click事件回調(diào)函數(shù)里的this指向當前target元素而不是你定義回調(diào)時所在的對象。如果回調(diào)里需要訪問組件實例的數(shù)據(jù)直接寫this.data會拿到undefined。當年的筆試題如果出“給對象綁定事件回調(diào)里訪問this.foo輸出什么”很多人都會在這里翻車。解法就是把this提前存下來或者直接用箭頭函數(shù)。順手把call、apply、bind的區(qū)別也鞏固一下。call和apply都是立即執(zhí)行差異只在于參數(shù)傳遞方式call是展開傳參apply是數(shù)組傳參bind不立即執(zhí)行而是返回一個新函數(shù)并且新函數(shù)的this永久綁定。有一個冷門細節(jié)bind返回的函數(shù)還可以再次bind但this無法再改變因為第一次bind的this已經(jīng)鎖死。3.3 閉包為什么for循環(huán)里setTimeout輸出全是最大值閉包的經(jīng)典考題基本長這樣for (var i 0; i 5; i) { setTimeout(function() { console.log(i); }, 1000); }輸出是什么很多人眼睛掃過覺得是0、1、2、3、4實際輸出是5個5。原因在于var聲明的i是函數(shù)作用域循環(huán)結(jié)束后i已經(jīng)變成5setTimeout回調(diào)執(zhí)行時讀取的是已經(jīng)結(jié)束循環(huán)后的同一個變量i。這個題目考察的核心就是作用域和閉包的關(guān)系回調(diào)函數(shù)并沒有保存“每一次循環(huán)時的i”而是保存了對i這個變量的引用而變量i的最終值就是5。解法有三種。第一種是借助函數(shù)作用域隔離用IIFE把每一輪的i作為參數(shù)傳入for (var i 0; i 5; i) { (function(j) { setTimeout(function() { console.log(j); }, 1000); })(i); }第二種是用閉包返回一個函數(shù)。第三種是直接改用letlet的塊級作用域會在每一輪循環(huán)創(chuàng)建新的綁定所以setTimeout讀取到的是每輪獨立的i?,F(xiàn)在的主流寫法當然是let但筆試如果只寫let不解釋為什么有的面試官還會追問底層原理所以你要能解釋清楚let在循環(huán)里做了哪些事情。閉包本身也是面試必問。我能給出的最直白解釋是一個函數(shù)在定義時捕獲了它外層作用域的變量即使外層函數(shù)已經(jīng)執(zhí)行完畢那些變量也沒有被銷毀而是被這個函數(shù)引用著。本質(zhì)上就是“函數(shù) 函數(shù)定義時所在的作用域綁定”。閉包能幫我們封裝私有變量實現(xiàn)模塊化但也可能造成內(nèi)存泄漏比如事件監(jiān)聽器里引用了一個大對象事件一直不解除大對象就永遠無法被回收。3.4 事件循環(huán)異步輸出順序題背后的運行機制異步代碼的輸出順序題這幾年越來越火2018年百度這套試卷里也有類似題型。給你一段混合了setTimeout、Promise、async/await的代碼讓你寫出輸出順序。很多初學者用“誰先到時間誰先執(zhí)行”來推完全推錯。正確的分析工具是事件循環(huán)機制。JavaScript是單線程的所有同步任務直接執(zhí)行遇到異步任務瀏覽器會把它交給對應的Web API處理完成后把回調(diào)放進任務隊列。事件循環(huán)的規(guī)則是先把當前調(diào)用棧里的同步代碼執(zhí)行完畢然后處理微任務隊列把微任務隊列清空后再取一個宏任務執(zhí)行執(zhí)行完再去清空微任務隊列如此反復。宏任務包括setTimeout、setInterval、I/O、UI交互事件微任務包括Promise的then和catch回調(diào)、queueMicrotask、MutationObserver。async/await本質(zhì)上是Promise的語法糖await后面的代碼相當于被放進了.then里所以也是微任務。一個典型的判斷是console.log(1); setTimeout(() console.log(2), 0); Promise.resolve().then(() console.log(3)); console.log(4);輸出順序是1、4、3、2。同步代碼1和4先執(zhí)行Promise微任務在宏任務setTimeout之前執(zhí)行所以3在2前面。有一個細節(jié)很關(guān)鍵setTimeout的第二個參數(shù)是0并不是立即執(zhí)行而是最少要等當前事件循環(huán)的宏任務執(zhí)行完還要考慮瀏覽器層面的4ms到8ms的最小延遲。所以哪怕代碼里寫的是0它的執(zhí)行時機也一定晚于同步代碼和微任務。這道題做好之后還能延展出一個更深的考點async函數(shù)里的await到底暫停了什么??催@段代碼async function foo() { console.log(start); await bar(); console.log(end); } function bar() { console.log(bar); return Promise.resolve(); } foo(); console.log(global);先輸出start再輸出bar然后輸出global最后才輸出end。因為await后面的bar()是同步執(zhí)行的只有await的后續(xù)代碼會被推遲到微任務里。理解了這個細節(jié)輸出順序題基本都能做對。4. 網(wǎng)絡(luò)與瀏覽器從URL到頁面渲染的完整鏈路4.1 HTTP緩存強緩存和協(xié)商緩存一道必須答全的題前端筆試試卷里HTTP緩存幾乎是必考??疾禳c集中在“強緩存和協(xié)商緩存有什么區(qū)別、分別有哪些字段、請求流程是什么樣”這幾個維度。強緩存就是瀏覽器在緩存過期之前直接使用本地緩存不發(fā)請求到服務器。實現(xiàn)方式有兩種Expires和Cache-Control。Expires是HTTP/1.0時代的字段指定一個絕對時間Cache-Control是HTTP/1.1的字段可以設(shè)置max-age相對時間。兩者同時存在時Cache-Control優(yōu)先級更高。協(xié)商緩存則是瀏覽器發(fā)現(xiàn)本地緩存過期了帶著標識去服務端驗證服務端說“內(nèi)容沒變”返回304瀏覽器就繼續(xù)用本地緩存如果內(nèi)容變了服務端返回200和新的數(shù)據(jù)。協(xié)商緩存的字段有Last-Modified和ETag。Last-Modified是最后修改時間ETag是內(nèi)容指紋。ETag的優(yōu)先級高于Last-Modified。一個筆試??嫉募毠?jié)是瀏覽器從緩存里讀資源時到底走哪些順序。完整的判斷路徑是先查強緩存命中就直接用沒命中再走協(xié)商緩存帶上If-None-Match或If-Modified-Since看服務端返回304還是200。如果后端設(shè)計緩存策略一般會同時返回Cache-Control和ETag兩者配合能兼顧命中率和準確性。實際開發(fā)里我經(jīng)常遇到一個坑更新了前端靜態(tài)資源用戶還是看到舊版本。原因就是靜態(tài)資源的強緩存設(shè)置得時間太長max-age設(shè)成一年甚至更長。正確的做法是給文件名加hash指紋內(nèi)容變化時文件名跟著變這樣新頁面引用新文件名就不會命中舊緩存。這個思路在工程化構(gòu)建工具里已經(jīng)自動化了但理解它的原理對排查線上問題非常有幫助。4.2 跨域原理、方案和面試官想聽的重點跨域題的經(jīng)典問法是“有哪些解決跨域的方法請詳細說明”。先說為什么要跨域瀏覽器出于安全考慮默認遵守同源策略協(xié)議、域名、端口任一不同瀏覽器就會攔截跨域的請求響應。但前后端分離架構(gòu)下接口和頁面往往部署在不同域名必須想辦法繞過同源策略。最常用的方案是CORS跨域資源共享。服務端在響應頭里加Access-Control-Allow-Origin等字段告訴瀏覽器“我允許你這個源來讀數(shù)據(jù)”。瀏覽器發(fā)現(xiàn)響應帶了這個頭就不會攔截。需要注意CORS不是瀏覽器自動放行而是需要服務端配合。還有復雜請求需要先發(fā)一次預檢請求OPTIONS預檢通過后才會發(fā)真正的請求。這一塊當年筆試偶爾會考問“什么情況下會觸發(fā)OPTIONS預檢”答案是非簡單請求比如自定義請求頭、請求方法是PUT或DELETE、或者Content-Type不是常規(guī)的幾種。另一個經(jīng)典方案是JSONP。它的原理是利用script標簽不受同源策略限制的特點動態(tài)創(chuàng)建一個script標簽把回調(diào)函數(shù)名傳給服務端服務端返回一段形如callback(data)的JavaScript代碼瀏覽器執(zhí)行它。JSONP只支持GET請求沒有CORS靈活但兼容性好在一些老系統(tǒng)中還在用。還有一種方案是代理轉(zhuǎn)發(fā)讓前端請求同域的后端服務再由后端去請求真正的目標服務因為后端之間不存在同源限制這種方案在開發(fā)環(huán)境和生產(chǎn)環(huán)境都很常用。我在面試回答這道題時習慣先講清楚“瀏覽器攔截的是響應不是請求”這樣能體現(xiàn)對底層機制的理解。然后按場景給方案優(yōu)先CORS服務端可改就直接上服務端改不了就考慮代理老瀏覽器兼容要求高再考慮JSONP。這種“先原理再場景后選型”的回答結(jié)構(gòu)面試官反饋一直不錯。4.3 瀏覽器渲染流水線重排、重繪與合成從輸入URL到頁面渲染這個經(jīng)典題在2018年試卷中屬于重頭戲回答的顆粒度決定了面試官對你網(wǎng)絡(luò)和瀏覽器知識儲備的印象。完整鏈路大致是DNS解析、TCP連接、發(fā)送HTTP請求、收到響應、解析HTML構(gòu)建DOM樹、解析CSS構(gòu)建CSSOM樹、合并成渲染樹、布局、繪制、合成、顯示。筆試可能不要求你寫這么全但至少得把鏈路中的關(guān)鍵節(jié)點寫對。尤其值得展開的是渲染階段的幾個概念。重排也叫回流是當元素的尺寸、位置、可見性等影響布局的屬性發(fā)生變化時瀏覽器需要重新計算元素的位置和幾何信息。重繪是元素的外觀發(fā)生變化但布局不變比如顏色改變?yōu)g覽器只需重新繪制。合成是新增的獨立圖層直接合成不需要重繪整個頁面transform和opacity相關(guān)的動畫走的就是合成通道性能比改left/top好得多。相關(guān)的優(yōu)化建議在筆試里通常是問答題如何減少重排重繪我一般會回答四條。第一避免頻繁讀取會強制同步布局的屬性比如offsetWidth、clientTop這些把它們緩存到變量里。第二合并多次DOM修改用DocumentFragment一次性插入。第三需要動畫的元素采用transform而不是改left/top。第四給元素設(shè)置will-change或者使用translateZ(0)進行圖層提升但要控制數(shù)量圖層過多反而消耗內(nèi)存。還有一個高頻細節(jié)樣式放在head腳本放在body底部。因為CSS會阻塞渲染但不會阻塞DOM解析腳本放在底部可以避免腳本阻塞DOM解析和渲染。script標簽的async和defer屬性也是??键casync加載完立馬執(zhí)行不保證順序defer等整個文檔解析完再執(zhí)行按順序執(zhí)行。理解這個區(qū)別對優(yōu)化首屏很有實際意義。4.4 從筆試題看性能優(yōu)化怎么答才不顯得“只會背題”2018年的前端筆試卷里性能優(yōu)化往往會出一道簡答題比如“頁面加載慢如何分析有哪些優(yōu)化手段”這道題沒有標準答案考察的是有沒有性能分析意識和優(yōu)化手段的儲備。如果是我來回答我會分領(lǐng)域展開。網(wǎng)絡(luò)層面減少請求數(shù)、合并文件、壓縮資源、CDN分發(fā)、開啟Gzip、合理設(shè)置緩存。渲染層面減少DOM數(shù)量、減少重排重繪、圖片懶加載、首屏優(yōu)先加載關(guān)鍵CSS和關(guān)鍵JS。代碼層面減少作用域鏈查找、使用事件委托、避免長時間占用主線程。監(jiān)控層面通過Performance面板查看首屏時間與白屏時間用Lighthouse審計頁面甚至可以埋點上報。光列優(yōu)化點還不夠最好能配合一個實際案例。比如一個頁面首屏加載3秒通過DevTools的Network面板發(fā)現(xiàn)有兩個大體積圖片塞在首屏繼續(xù)查看發(fā)現(xiàn)圖片沒有壓縮且沒有懶加載于是把首屏外的圖片改成懶加載并把首屏圖用壓縮后的WebP格式。再配合CDN加速首屏降到1.2秒。這種表述能體現(xiàn)你真的做過優(yōu)化而不是背了一份清單。筆試閱卷人偏好看到動手做過優(yōu)化的痕跡哪怕項目很小也比空泛堆概念強。5. 框架與工程化當年的考察方式與現(xiàn)在的變化5.1 框架部分的題當年考察的其實是基礎(chǔ)2018年的百度前端筆試試卷里框架的題目占比并不重。即使出現(xiàn)Vue或者React相關(guān)題考得也很淺主要集中在組件生命周期、數(shù)據(jù)綁定、組件通信這些使用層面極少直接讓手寫框架源碼。但這一塊對考生來說恰恰是“看似簡單實則拉開差距”的地方。比如Vue問“v-if和v-show的區(qū)別”很多人只答“一個刪DOM一個隱藏DOM”這算基礎(chǔ)分。如果補一句“v-if是真正條件渲染初始為false的話不會渲染v-show是CSS切換始終渲染適合頻繁切換的場景”再進一步說“v-if有更高的切換開銷v-show有更高的初始渲染開銷”這就能體現(xiàn)你真正考慮過應用場景。再比如組件通信題。父子通信用props和事件跨層級通信用provide/inject任意兩個組件通信用事件總線或者狀態(tài)管理工具。當年筆試如果出這種題其實是在考察你對“數(shù)據(jù)流”的理解。React強調(diào)的是單向數(shù)據(jù)流狀態(tài)提升到公共父組件Vue雖然有v-model這種語法糖底層依然是單向數(shù)據(jù)流加事件回傳。理解了這一點無論框架怎么換你都很難被問倒。5.2 組件化與狀態(tài)管理從筆試到工作的進化框架題背后真正想考察的是組件化和狀態(tài)管理思維。2018年那會兒Vuex和Redux已經(jīng)比較流行筆試偶爾會出現(xiàn)在“多個組件共享一份數(shù)據(jù)怎么設(shè)計”這類場景題。我的回答思路是先判斷共享數(shù)據(jù)的變化頻率。低頻共享比如用戶基本信息放在一個全局對象里就夠了中頻共享比如購物車列表用狀態(tài)管理工具比較合適高頻共享比如實時協(xié)作的文檔內(nèi)容就需要更精細的狀態(tài)設(shè)計甚至接入WebSocket。筆試答題時先分析狀態(tài)種類再選擇方案這會比直接說“用Vuex”顯得更有思考深度?,F(xiàn)代框架里的狀態(tài)管理已經(jīng)有了更多選擇Pinia、Zustand、Recoil還有React 18的useSyncExternalStore等新特性。但底層邏輯沒變狀態(tài)盡量放在離使用者最近的位置避免全局狀態(tài)泛濫狀態(tài)更新應該可預測盡量避免在多個地方直接改同一個狀態(tài)。這套設(shè)計理念從Redux到Pinia都是一脈相承的。5.3 前端工程化module、構(gòu)建工具與代碼規(guī)范工程化在2018年的筆試中通常是概念性考察比如“webpack的基本概念”“模塊化開發(fā)的理解”。放到今天的語境里這些依然是基礎(chǔ)中的基礎(chǔ)。ESModule的導入導出、CommonJS的require和module.exports、兩者的差異是筆試??键c。ESModule是靜態(tài)分析編譯時就能確定導入導出關(guān)系所以支持tree-shakingCommonJS是動態(tài)的在運行時才能確定所以無法做靜態(tài)分析與搖樹優(yōu)化。這是兩者最本質(zhì)的區(qū)別?;卮稹皐ebpack是如何工作的”時我習慣用一句話概括webpack把各種資源視為模塊通過loader做轉(zhuǎn)換通過plugin做各種附加操作最終把所有模塊打包成瀏覽器能識別的靜態(tài)資源。構(gòu)建工具本身迭代很快從webpack到vite底層思想從“先打包再啟動”變成了“基于原生ESModule的按需加載”。但萬變不離其宗模塊化、依賴圖、構(gòu)建產(chǎn)物、代碼分割、緩存優(yōu)化這些核心概念在webpack文檔里能啃透換任何工具都不慌。筆試如果考概念不用追求最新的工具版本號只要把原理講清楚就夠了。5.4 那些年容易答錯的“送命題”與應對策略框架與工程化部分有一類看似簡單的題稍不留神就答錯。比如“Vue生命周期里created和mounted誰先執(zhí)行如果我想在頁面加載完成后請求數(shù)據(jù)該放哪個鉤子”標準答案是created在實例創(chuàng)建時同步執(zhí)行mounted在DOM掛載后執(zhí)行。如果請求數(shù)據(jù)不依賴DOM放created可以減少等待時間如果依賴DOM尺寸或位置就必須放mounted。答題時給出這個“分情況討論”的結(jié)論比單純說放哪個好更穩(wěn)妥。另一個常見送命題是“React的setState是同步還是異步”。在React 18以前React事件處理和生命周期鉤子里的setState是異步批量更新的但在setTimeout或原生DOM事件里它是同步的。這個反直覺的答案讓很多人中招。React 18的自動批處理把更多場景統(tǒng)一成異步批量更新但批量更新機制背后的“多次狀態(tài)更新合并成一次渲染”思想才是這類題真正考察的東西。應對這類題的方法是做對比梳理。把容易混淆的知識點列成一張表比如v-if和v-show、created和mounted、computed和watch、props和state。每次復習都自己先講一遍差異卡殼的地方就是知識盲區(qū)。這樣整理完之后框架題的準確率會高很多。6. 從2018到現(xiàn)在的面試準備策略6.1 核心知識點優(yōu)先級先抓重點再鋪廣度復盤完整套試卷之后我給正在準備大廠前端校招的同學一個優(yōu)先級建議。最高優(yōu)先級是JavaScript底層機制包括作用域、閉包、原型鏈、this指向、事件循環(huán)、Promise原理這一塊是筆試題和大廠面試題里出現(xiàn)頻率最高的必須做到能講、能寫、能推導輸出結(jié)果。第二優(yōu)先級是網(wǎng)絡(luò)與瀏覽器原理緩存、跨域、渲染流程、重排重繪、HTTPS握手這一塊直接決定了你寫的頁面能不能跑得快、跑得穩(wěn)。第三優(yōu)先級是CSS布局與瀏覽器兼容盒模型、BFC、flex、grid、垂直居中方案。第四優(yōu)先級才是框架與工程化把一家框架用熟理解組件通信和狀態(tài)管理即可不需要深啃源碼。最后才是各種零散API的背誦。為什么是這個順序因為大廠筆試篩選的底層邏輯是基本功。框架可以入職后學項目經(jīng)歷可以包裝但JavaScript語言機制和瀏覽器原理是裝不出來的懂就是懂不懂就是不懂。我見過很多簡歷上寫“精通Vue/React”的候選人被一道setTimeout輸出題問倒這種反差反而比基礎(chǔ)一般更扣分。6.2 刷題的正確姿勢輸出、復述、變形刷題不是把答案背下來就完事。我自己的經(jīng)驗是每一道題至少過三遍。第一遍限時完成模擬筆試環(huán)境不看答案。第二遍對照參考答案把錯題的知識點整理到自己的知識地圖里不是抄答案而是問自己“出題人為什么這么問”找到題背后的知識點。第三遍隔兩周不看答案重新做一遍這次要能在不寫代碼的情況下把解題思路口頭復述出來。這個方法是從費曼學習法變過來的。能講清楚才是真懂。比如原型鏈題你如果只是記住“實例的__proto__等于構(gòu)造函數(shù)的prototype”這句話碰到變種題還是會錯。但如果你能把整個原型鏈的查找過程畫出來順便解釋Object.create和new的區(qū)別那你遇到任何原型鏈題都能應對。變種題其實就是出題人換了個場景核心知識點沒變。6.3 時間分配一場筆試里哪些題該搶、哪些題該放我在實際筆試里養(yǎng)成了一個習慣拿到卷子先花兩分鐘掃一遍所有題目評估難度分布?;A(chǔ)選擇題和填空題會做的直接做拿不準的先跳過不要在一道多選題上死磕。編程題優(yōu)先做有把握的哪怕解法不是最優(yōu)寫出來能跑過簡單用例也比空著強。如果最后還剩時間再回頭做沒把握的選擇題。前端筆試題量普遍不小時間分配直接決定你能不能把會做的分全拿住。一般筆試時間是90到120分鐘我的分配建議是選擇和填空控制在四十分鐘左右編程題留至少五十分鐘。編程題哪怕寫不全也要把思路、偽代碼和關(guān)鍵函數(shù)寫出來很多筆試系統(tǒng)是按測試用例通過的百分比給分的部分通過也能拿分。還有一點很實用編程題注意輸入輸出的格式要求很多同學思路對了結(jié)果因為讀錯輸入格式丟掉一大半用例分特別可惜。6.4 面試環(huán)節(jié)的延伸筆試之后面試官還會問什么筆試不是終點它更像一張技術(shù)雷達圖。面試官會拿著你的答卷針對錯題和薄弱點深入追問。比如你筆試里BFC那道題答錯了面試就可能追問“那你知道overflow: hidden和overflow: auto在創(chuàng)建BFC時有什么區(qū)別嗎”“有哪些場景下會看不出overflow hidden內(nèi)容”這些追問才是真正的考察重心。在這種情況下我建議你在筆試結(jié)束后立刻復盤錯題把每道錯題背后的知識點系統(tǒng)復習一遍。不要只背正確選項要把整個知識鏈條捋清楚。比如你錯了一道“哪些屬性會觸發(fā)重排”的多選題你就應該順便把強制同步布局、偏移屬性緩存、圖層提升這些相關(guān)概念全部復習一遍。這樣面試官無論從哪個角度追問你都能接得住。另外面試官還可能讓你現(xiàn)場寫代碼寫法和筆試會有區(qū)別。筆試更看重結(jié)果對不對面試更看重思路和溝通?,F(xiàn)場寫題時不要悶頭寫先說明思路再動手寫的過程中遇到邊界情況要說出來比如“這里要處理參數(shù)為空的情況”。這種溝通習慣本身就是加分項。7. 回顧這套試卷的感受與技術(shù)復盤回到2018年那套百度第三批筆試卷我最大的感受是它考察的不是“你會不會用某個框架”而是“你有沒有形成完整的前端知識體系”??蚣軙^時API會升級但原型鏈、事件循環(huán)、緩存策略、渲染機制這些底層知識不會變?,F(xiàn)在看當年覺得很難的題目很多已經(jīng)成為初級前端工程師應該具備的基本素養(yǎng)。我自己后來在帶新人和面試候選人時偶爾還會把當年的筆試思路拿出來當參考。判斷一個候選人有沒有潛力不看他說自己會多少框架而是隨便抽一個基礎(chǔ)概念看他能不能講出“是什么、為什么、怎么用、有什么坑”。這套思考方式其實就是從當年刷百度的卷子開始養(yǎng)成的。如果你也在準備校招不妨把這份卷子的每個考點吃透它會是你前端之路上非常堅實的一塊基石。