跳動前端筆試題復盤JS核心考點與面試演進邏輯)
2018年秋招那會兒我正好在準備前端方向的校招字節(jié)跳動的筆試印象特別深。第二批次的題和第一批不太一樣但考察的核心思路是一致的不看你背了多少API而是看你對JS這門語言的理解深度、對瀏覽器運行機制的認識程度以及工程化實踐里踩過多少坑。現(xiàn)在回過頭來看這套題很多考點依然是面試高頻區(qū)甚至可以說當年這些題目劃定了一個前端工程師基本功的考察范圍。這篇文章把第二批次涉及的核心問題、解題思路和面試延伸展開聊聊給正在準備大廠前端面試的同學做個參考。1. 當年那場筆試整體結構與考察風向1.1 2018年前端技術棧的“時代背景”為什么這決定了題目風格2018年是前端工程化大爆發(fā)但又沒完全塵埃落定的一年。Vue 2.x已經(jīng)非常流行React 16也有了Fiber架構TypeScript開始進入主流視野但還沒完全統(tǒng)治Webpack 4剛發(fā)布不久HTTP/2在國內(nèi)大廠逐步落地但還沒全面普及。當時的前端校招筆試題不會像現(xiàn)在讓你直接寫TS類型體操也不會考你微前端怎么設計因為那時候連微前端這個概念都還沒在工程里普遍應用。那會考察的重心很明確ES6核心語法promise、箭頭函數(shù)、解構、class、模塊化、瀏覽器事件循環(huán)機制、HTTP緩存策略、閉包與作用域鏈、原型鏈與繼承這些基礎中的基礎。這套題之所以到現(xiàn)在還有參考價值是因為它構建了一個“基礎能力”的考察坐標系——不管框架怎么變、工具鏈怎么演進JS語言本身的特性、瀏覽器的工作機制、網(wǎng)絡協(xié)議的基本原理一直是前端工程師能不能走遠的分水嶺。回頭看第二批次這套題整體題型大致分兩類一類是選擇題覆蓋語法細節(jié)、輸出順序、作用域陷阱這類題特別考驗對語言特性的精確掌握另一類是編程題要求手寫實現(xiàn)一些常見功能。選擇題的坑挖得很細不是那種一眼能看出答案的題而是需要你在腦子里把代碼執(zhí)行過程完整走一遍才能答對。1.2 題型分布與考察模塊占比從總體來看第二批次題目的考察模塊分布大概可以歸納為下表考察模塊典型題型大概占比JS語言核心閉包、this、原型鏈、作用域選擇題、代碼輸出題30%異步編程事件循環(huán)、Promise、setTimeout選擇題、代碼輸出題25%瀏覽器與網(wǎng)絡緩存、渲染機制、跨域選擇題、簡答題20%框架與工程化Vue/React基礎、Webpack選擇題15%手寫編程題防抖、深拷貝、數(shù)組去重、事件總線編程題10%這個分布很有意思。語言核心和異步編程加在一起超過了一半說明字節(jié)當年的思路就是先把語言功底篩出來再談別的。框架和工程化的占比反而不高因為校招生入職后會有很長的學習期框架可以現(xiàn)學但JS基礎和異步編程的理解深度一旦欠缺后面補起來非常吃力。這個考察邏輯放到現(xiàn)在的面試里依然是大廠的主流思路。2. 筆試重點題型拆解每一道題背后的考點是什么2.1 JS語言核心類題目閉包、this、原型鏈的經(jīng)典陷阱第二批筆試的JS語言核心類題目出題風格非常典型——給一段代碼讓你判斷輸出結果或選擇描述正確的選項。這類題表面上看是考語法實際上考的是“你對JavaScript執(zhí)行機制的理解”。舉個例子閉包類題目經(jīng)常會這樣出for (var i 0; i 5; i) { setTimeout(function() { console.log(i); }, 100); }答案是輸出5個5。如果你不理解var沒有塊級作用域、以及閉包捕獲的是變量引用而不是值這道題就會答錯。而如果你知道改成let聲明或者用IIFE立即執(zhí)行函數(shù)包一層就能正確輸出0到4并說出背后的原理這道題才算真正吃透。this指向問題同樣是一個高發(fā)區(qū)。經(jīng)典的考察方式是var obj { name: byte, getName: function() { return this.name; } }; var fn obj.getName; console.log(fn());這里的坑在于當你把obj.getName賦值給一個變量再調(diào)用時函數(shù)內(nèi)部的this就不再指向obj了而是指向全局對象。究其原因this的指向取決于函數(shù)調(diào)用方式而不是定義位置。這種題在面試中還會以多種變體出現(xiàn)比如嵌套對象里的方法調(diào)用、箭頭函數(shù)和普通函數(shù)的this對比。再說原型鏈的考察。2018年那會很愛考下面這類題function Person(name) { this.name name; } Person.prototype.sayHello function() { console.log(Hello, this.name); }; var p new Person(ByteDancer); p.sayHello(); console.log(p instanceof Person); console.log(p.constructor Person);這道題表面很簡單但面試官往往會在筆試之后的面談環(huán)節(jié)繼續(xù)追問new操作符到底做了什么事如果我把Person.prototype.sayHello改成箭頭函數(shù)會怎樣這其實已經(jīng)把考察從“知不知道”拉到了“能不能說清楚底層機制”的層面。建議你在準備時一定要能把new的過程用代碼描述出來——創(chuàng)建一個新對象、把新對象的__proto__指向構造函數(shù)的prototype、將構造函數(shù)的this綁定到新對象上、如果構造函數(shù)沒有顯式返回對象則返回這個新對象——這四步缺一不可。2.2 異步編程類題目事件循環(huán)與Promise的“輸出題”套路異步編程是第二批筆試的重頭戲也是很多同學失分最嚴重的地方??嫉牟皇悄隳懿荒軐懗鯬romise而是給你一段混合了setTimeout、Promise、async/await的代碼讓你判斷輸出順序。這類題的底層邏輯基于JavaScript的事件循環(huán)機制同步代碼先執(zhí)行然后是微任務隊列最后是宏任務隊列。但2018年的考題里有幾個容易踩坑的細節(jié)第一Promise.resolve().then(...)注冊的回調(diào)屬于微任務會在當前宏任務結束后立即執(zhí)行而setTimeout注冊的回調(diào)屬于宏任務要等到下一輪事件循環(huán)。所以“先微任務后宏任務”這個規(guī)則能幫你快速排除錯誤答案。第二async/await本質(zhì)上是Promise的語法糖await后面的代碼會作為微任務繼續(xù)執(zhí)行。如果要判斷輸出順序可以先把async函數(shù)轉換成Promise鏈的寫法轉換對了輸出順序基本就對了。舉個例子這類題的經(jīng)典形態(tài)是這樣的async function async1() { console.log(async1 start); await async2(); console.log(async1 end); } async function async2() { console.log(async2); } console.log(script start); setTimeout(function() { console.log(setTimeout); }, 0); async1(); new Promise(function(resolve) { console.log(promise1); resolve(); }).then(function() { console.log(promise2); }); console.log(script end);正確答案是script start - async1 start - async2 - promise1 - script end - async1 end - promise2 - setTimeout。如果你能一次答對說明對事件循環(huán)的層級關系已經(jīng)掌握得比較扎實。如果答錯了建議把整個流程手動畫一遍不要用畫圖工具就用紙筆一步一步記錄。為什么因為手畫的過程能逼著你把“當前執(zhí)行的是哪個任務任務執(zhí)行完之后隊列里還有什么”這個狀態(tài)理清楚。這個能力不僅應對筆試有用排線上問題的時候同樣重要。2.3 瀏覽器與網(wǎng)絡類題目緩存、跨域與渲染機制瀏覽器相關的題目在第二批次里占比不低最常見的兩個方向是HTTP緩存和跨域。HTTP緩存這部分考察的核心是強緩存和協(xié)商緩存的區(qū)別。選擇題經(jīng)常這么出如果服務端返回的響應頭里包含Cache-Control: max-age3600瀏覽器在接下來的一個小時內(nèi)再次請求該資源會怎樣答案是直接從本地緩存讀取不再發(fā)起網(wǎng)絡請求。但如果響應頭同時帶有ETag緩存過期后瀏覽器會帶上If-None-Match頭字段去服務端做驗證服務端返回304時瀏覽器繼續(xù)使用本地緩存這屬于協(xié)商緩存。你要真的吃透緩存體系不能只記字段還要理解瀏覽器為什么設計兩套緩存機制。強緩存的價值是免請求適合長期不變的靜態(tài)資源比如打包后的JS文件名帶hash內(nèi)容變了文件名就變天然適合強緩存。協(xié)商緩存的每一次驗證還需要一次網(wǎng)絡請求適合無法確定多長時間內(nèi)不變的資源。這兩種機制組合起來才能既保證更新及時又最大化利用緩存??缬騿栴}也是高頻考點。2018年那會兒的核心解法是JSONP和CORS現(xiàn)在的話還會考到代理轉發(fā)、postMessage等方式但原理是相通的瀏覽器的同源策略限制的是頁面腳本從一個源去請求另一個源的資源CORS本質(zhì)上是通過服務端返回Access-Control-Allow-Origin響應頭來告知瀏覽器允許跨域。理解這個原理之后后面的配置就水到渠成。2.4 手寫編程題那些“簡單但容易出錯”的實現(xiàn)編程題方面第二批次的題目大多圍繞數(shù)組和函數(shù)展開。常見的有數(shù)組去重、防抖節(jié)流、深拷貝、數(shù)組扁平化、事件總線。這些題看起來簡單但真要寫得無懈可擊還是有很多細節(jié)需要考慮。以深拷貝為例如果你只寫一個遞歸遍歷對象屬性的版本在筆試中能拿到一些分但距離滿分還有差距。一個完備的深拷貝實現(xiàn)需要處理循環(huán)引用用一個WeakMap緩存已拷貝的對象、數(shù)組類型、Date和RegExp等特殊對象、Symbol屬性。如果還想做得更完善可以處理函數(shù)的拷貝策略通常直接復用引用以及通過原型鏈繼承的屬性。在當前的前端面試中這道題的優(yōu)化空間依然很大值得花時間把不同版本都寫一遍。防抖和節(jié)流也是高頻手寫題。防抖的意思是事件觸發(fā)后等待一段時間再執(zhí)行如果在這段時間內(nèi)再次觸發(fā)則重新等待節(jié)流是限制一段時間內(nèi)最多執(zhí)行一次。這兩個工具函數(shù)的使用場景完全不同——搜索框建議是防抖因為用戶輸入完才去請求滾動加載建議是節(jié)流因為不想每個滾動幀都去執(zhí)行計算。寫實現(xiàn)的時候要特別注意this綁定和參數(shù)的透傳因為實際項目里防抖節(jié)流往往用在事件處理函數(shù)上this指向不能丟。3. 面試延伸從筆試題目到深挖原理的層層追問3.1 手寫Promise與實現(xiàn)一個簡單的Vue響應式系統(tǒng)筆試過了之后面試環(huán)節(jié)的深挖幾乎必然會從筆試題延伸出來。字節(jié)面試官非常擅長從一個簡單的考點發(fā)散到一連串相關問題你答到了哪個層面他就順著那個層面繼續(xù)往下問。拿Promise來說筆試可能只讓你判斷輸出順序但面試可能直接讓你手寫一個符合Promise/A規(guī)范的Promise。手寫Promise是個很好的分水嶺——它能檢驗你對狀態(tài)機、回調(diào)隊列、then鏈式調(diào)用、錯誤處理這些核心機制的理解。我當時寫的時候花了不少時間處理then的回調(diào)注冊順序和狀態(tài)已經(jīng)改變后的執(zhí)行邏輯寫完之后對Promise的執(zhí)行時序有了豁然開朗的感覺。Vue響應式系統(tǒng)的實現(xiàn)也是一個經(jīng)典深挖點。2018年在字節(jié)面試中出現(xiàn)頻率很高畢竟字節(jié)當時內(nèi)部也大量使用Vue。面試官往往從“Vue 2.x的響應式原理是什么”開始讓你講Object.defineProperty如何攔截屬性讀取和賦值然后追問數(shù)組的響應式為什么需要單獨處理再延伸到為什么Vue 3要改用Proxy。這一連串追問的底層邏輯其實是考察你有沒有真正理解“依賴收集”和“觸發(fā)更新”這八個字。你會不會實現(xiàn)不重要重要的是能不能用自己的語言把這個過程講清楚。3.2 Webpack構建流程與Loader、Plugin的區(qū)別工程化方面第二批次筆試中出現(xiàn)過Webpack相關的基礎選擇題比如loader和plugin的區(qū)別、常用的loader有哪些。這類題到了面試環(huán)節(jié)會變成更開放的問題給你一個場景讓你說說Webpack從入口文件開始經(jīng)歷了哪些階段最終產(chǎn)出bundle。如果你理解Webpack的整體流程這道題其實可以分幾個層次回答先解析配置找到入口文件然后從入口出發(fā)遞歸解析模塊依賴構建依賴圖在解析過程中遇到不同類型的文件交給對應的loader處理比如.vue文件交給vue-loader.css文件交給css-loader和style-loader然后經(jīng)過插件系統(tǒng)在特定生命周期鉤子中做優(yōu)化和資源處理最后輸出bundle。Loader和Plugin的區(qū)別也在這個流程中體現(xiàn)得很清楚loader負責轉換文件內(nèi)容Plugin負責在構建流程的特定節(jié)點做更復雜的操作。這里有一個很容易被忽視的細節(jié)loader的執(zhí)行順序是從右到左的。如果你寫use: [style-loader, css-loader]Webpack會先執(zhí)行css-loader把CSS文件解析成JS模塊再執(zhí)行style-loader把樣式注入到頁面。方向搞反了構建出來的頁面樣式就會出現(xiàn)問題。這類工程化的實操經(jīng)驗光背書是不行的得自己搭個項目跑一遍構建過程才能有深刻記憶。3.3 項目深挖面試官真正想從你的項目里看到什么字節(jié)的面試還有一個顯著特征非常看重項目經(jīng)驗的真實性。面試官會對你簡歷上寫的項目做“地毯式”追問從技術選型到遇到的具體問題再到解決方案的思考過程每個環(huán)節(jié)都會細問。這背后的邏輯其實很簡單項目經(jīng)歷能反映出一個人的技術判斷力和解決復雜問題的能力。如果你在項目里做過性能優(yōu)化他會問你怎么定位性能瓶頸、怎么量化優(yōu)化的收益如果你做過前端工程化工具他會問你為什么選Webpack而不選Rollup、遇到構建速度慢的問題是怎么處理的如果你做過組件庫他會問你怎么設計API才能兼顧靈活性和易用性。這類追問最忌諱的就是背答案。最有效的應對方式是在面試前把你做的項目從“整體架構”到“關鍵實現(xiàn)細節(jié)”完整梳理一遍尤其要想清楚你做的每個技術決策是出于什么考慮有沒有考慮過其他方案最終為什么選了當前這個如果你能回答出這些“為什么”項目深挖這一關基本就穩(wěn)了。4. 備戰(zhàn)復盤我踩過的坑和改進后的準備節(jié)奏4.1 時間規(guī)劃與刷題策略現(xiàn)在回頭看我當時準備校招時犯過兩個比較大的錯誤。第一個是前期過于追求刷題數(shù)量而忽略了總結同樣的題型換了個數(shù)字重新出現(xiàn)我還是會錯說明沒有從題目中提煉出共性的解題方法。第二個是筆試前沒有限時模擬導致真到了筆試的時候時間分配出現(xiàn)問題——前面的選擇題花的時間過多后面的編程題來不及寫完整。改進之后的節(jié)奏可以這樣安排提前三個月開始第一階段以系統(tǒng)復習為主把JS高級程序設計紅寶書里的原型鏈、閉包、異步、DOM、事件等章節(jié)過一遍配合MDN文檔補充細節(jié)第二階段轉向刷題以??途W(wǎng)的前端題庫和力扣的JavaScript相關題目為主每天固定時間刷一定數(shù)量的選擇題和編程題重點記錄錯題第三階段進入模擬沖刺嚴格按照筆試時間來刷整卷并練習手寫代碼的速度和準確性。有一個習慣我覺得特別值得推薦每做錯一道題不要急著看答案先把題目復制下來在旁邊寫清楚“我當時為什么這么想”和“正確答案的邏輯是什么”。等復習后期再翻這些錯題記錄你會發(fā)現(xiàn)自己的思維漏洞非常集中往往就是那么幾個點反復出錯這比盲目刷新題要高效得多。4.2 高頻失分點與避坑建議從第二批次和其他大廠筆試題來看有幾個失分點相當集中值得單獨拎出來提示一下。第一個是變量提升和暫時性死區(qū)。var聲明的變量會被提升到作用域頂部初始值為undefined而let和const聲明的變量也存在提升但在聲明之前訪問會進入暫時性死區(qū)直接報錯。很多同學知道“l(fā)et不會變量提升”這個結論但不知道為什么遇到具體代碼時容易混淆。這個知識點在筆試選擇題中出現(xiàn)的頻率很高建議把提升和暫時性死區(qū)這兩個概念徹底搞清楚包括它們在函數(shù)作用域和塊級作用域下的不同表現(xiàn)。第二個是數(shù)組方法是否改變原數(shù)組。map、filter、reduce不會改變原數(shù)組splice、push、pop、sort、reverse會改變原數(shù)組。聽起來很簡單但一旦和引用類型的淺拷貝混在一起錯誤率就會飆升。比如對一個二維數(shù)組執(zhí)行slice()得到的“新數(shù)組”里層元素仍然是原數(shù)組里對象的引用修改里層對象會影響到原數(shù)組。這種細節(jié)在筆試中經(jīng)常被用來出陷阱題。第三個是CSS的層疊上下文與BFC。雖然第二批次筆試中CSS占比不高但面試環(huán)節(jié)很容易被問出來。兩列布局怎么實現(xiàn)、position的幾種值有什么區(qū)別、float脫離文檔流之后父元素高度塌陷怎么解決這些經(jīng)典問題至今仍是面試題庫的???。4.3 學習路線的取舍廣度優(yōu)先還是深度優(yōu)先準備校招那段時間我一直在糾結一個問題是先把Vue、React這些框架的文檔都過一遍還是集中精力把JS基礎打牢。當時的實際經(jīng)驗告訴我對大廠校招來說深度優(yōu)先是更正確的策略。原因很簡單校招面試官默認你是一個可塑性很強但經(jīng)驗尚淺的候選人他考察的重心不是你會用多少框架API而是你有沒有能力在未來的工作中快速學習和成長。JS基礎、事件循環(huán)、瀏覽器原理、網(wǎng)絡協(xié)議這些底層知識是衡量你“學習能力”最可靠的參考指標??蚣軐用娴闹R只要基礎扎實上手新框架往往只需要一兩周。反過來如果基礎不牢就算背了一堆框架API面試官通過幾個深挖問題就能看出你的底子。所以我的建議是把70%的精力花在JS核心、瀏覽器和網(wǎng)絡上剩下30%分給框架和工程化。5. 這套題放到現(xiàn)在還有參考價值嗎5.1 歷久彌新的核心考點客觀來說2018年的這套筆試題放到2026年的今天其中一大部分考點依然是前端面試的必考內(nèi)容。JS的執(zhí)行機制、事件循環(huán)、閉包、原型鏈、HTTP緩存、跨域、深拷貝和防抖節(jié)流的手寫實現(xiàn)這些題目在今天的大廠面試中依然高頻出現(xiàn)。為什么這些考點能一直保持生命力因為它們觸及的是一個前端工程師賴以生存的“底層能力圈”。不管前端框架怎么迭代最終都要運行在JavaScript引擎和瀏覽器提供的運行時環(huán)境里不管工具鏈怎么演進網(wǎng)絡請求和資源加載的基本規(guī)則沒有本質(zhì)變化。這部分知識更新速度慢但價值密度高屬于“一年不學不會過期學一次收益多年”的類型。5.2 已經(jīng)發(fā)生變化的考察方向當然前端面試的考察方向也在變化2018年和2026年有一些明顯的差異?,F(xiàn)在的面試更注重TypeScript的類型設計與泛型推導這在2018年基本不是校招考察重點。微前端架構、性能監(jiān)控體系、前端工程化平臺的建設在新題庫中的出現(xiàn)頻率明顯提升。AI輔助編程工具的出現(xiàn)也讓面試官更關注候選人如何高效使用工具同時考察你是否具備在AI輔助下獨立判斷代碼質(zhì)量的能力。但這并不意味著2018年的題就過時了。它更像是一面鏡子照出了前端面試“變與不變”的部分。把不變的部分掌握扎實再跟進變化的部分準備校招的路線就清晰很多。5.3 從一套筆試看面試官的選人邏輯最后說說我從這套題里讀出的選人邏輯。字節(jié)這類大廠校招面試官真正想選的人不是“背了最多題解的人”而是“能在未知問題面前展示出清晰思考過程的人”。面試官問你一個不會的問題他想看的不是“你承認不會”而是你會怎么去推測、怎么去拆解、怎么去逼近問題的核心。這個能力很難通過短期刷題獲得需要在平時學習新知識時有意識地訓練。有個小方法可以分享平時讀技術文章或源碼的時候不要只記結論試著多問自己幾個“為什么這樣設計”“如果不這樣設計會怎樣”“有沒有其他實現(xiàn)方案”。這種思維習慣一旦養(yǎng)成面試時被問到?jīng)]準備過的問題你也能現(xiàn)場推導出一個合理的思路。這一點才是2018年這套題真正想篩選出來的東西。