
小紅書2020校招前端筆試題卷二在當年的校招群里流傳度非常高。我記得好幾個學弟學妹輪番來問我同一個問題這套卷子該怎么準備我翻完流傳出來的題目版本之后最大的感受是它沒有一句廢話——閉包、this指向、事件循環、跨域、渲染機制、手寫代碼幾乎把前端基礎里最容易出錯的硬骨頭都點名了一遍。哪怕現在已經不是2020年前端技術棧一直在變但這套卷對準備校招、跳槽和想系統性自查基礎的人來說依然是一份很好的體檢清單。它能幫你快速找到那些“我以為是會的其實根本不懂”的地方。如果你正在準備前端崗位的筆試面試或者工作兩三年后感覺自己基礎不牢都可以拿這套題做一次自測。下面我會按模塊拆解卷子里最容易考、也最容易丟分的幾個方向把核心原理、答題思路和當時同學踩過的坑一起說清楚。1. 試卷結構與考察思路拆解1.1 卷二在整輪筆試中的定位小紅書2020校招前端筆試實際投放的不止一套卷子卷二屬于典型的基礎綜合卷邏輯是在簡歷篩選之后、面試之前先用機器或者人工快速篩一遍候選人的下限。這類卷子不會故意出偏題怪題但普遍題量大、覆蓋廣限時內想拿高分并不輕松。當時我拿到流傳出來的版本印象最深的是題目層級很分明大致可以分成三類選擇題負責考察概念辨析和語法易錯點包括單選和多選簡答題負責考察理解深度比如讓你解釋某個機制或者某個方案編碼題則直接要求手寫實現比如防抖節流、數組去重、深淺拷貝等。選擇題測的是“知不知道”簡答題測的是“理不理解”編碼題測的是“能不能寫出來”。這三個層次層層遞進比單純刷算法題更能反映一個前端候選人的真實水平。所以如果你只是在??汀eetCode上狂刷題忽視了對知識體系的理解卷二這類試卷很容易讓你原形畢露。1.2 校招筆試真正想看到的答案長什么樣很多同學容易陷入一個誤區簡答題寫得越多越好最好把所有相關名詞都堆上去。其實打分的人更想看到“先說什么、后說什么、為什么這樣設計”的邏輯鏈條。舉一個高頻簡答題為例“跨域有哪些解決方式”。如果只是列出來JSONP、CORS、postMessage、代理轉發那只能算及格。真正能拿高分的答法是先點出跨域的本質是同源策略對“協議、域名、端口”的限制再分別說明每種方案適合什么場景最后給出當前項目里的推薦做法和注意事項。卷二里的簡答題占比不低答法不同分數差距會很大。我建議平時復習時不要只背面經可以試著把每個知識點當成一個“小報告”先定義、再解釋原理、最后給場景這樣無論題目怎么變形你都能答到點子上。1.3 考點分布速查表考察模塊常見題型推薦優先級JavaScript 基礎選擇、簡答、編碼極高瀏覽器與網絡選擇、簡答高CSS 與布局選擇、編碼高Vue/React 框架選擇、簡答中高手寫代碼編碼極高這個優先級參考了近幾年的校招筆試題和面經統計不一定完全對應某一次考試但大方向是穩定的。考生復習時應該優先保證JavaScript和手寫代碼的正確率因為它們幾乎每套卷都會出現而且一旦寫錯基本沒有蒙對的概率。2. JavaScript 基礎閉包、作用域與this指向2.1 閉包經典考法從輸出結果到修復代碼卷二在JavaScript部分最常出現的是閉包相關題目。閉包的經典考法有兩種一種是問輸出結果另一種是讓你修復代碼中的問題。問輸出結果幾乎是繞不開這個場景的for (var i 0; i 5; i) { setTimeout(function() { console.log(i); }, 1000); }這段代碼最終不是打印0、1、2、3、4而是一秒后連續打印五個5。原因有兩層第一var聲明的i是函數級作用域循環體里外共用同一個變量第二setTimeout的回調是在循環完全結束之后才執行的此時i已經自增到了5。只要抓住“變量作用域 異步執行時機”這條主鏈類似題目基本不會再錯。考試里還會繼續追問改成let i為什么就對了因為let在每次迭代中都會創建一次新的詞法環境綁定閉包捕獲的是當前這一次的i而不是循環結束之后的最終值。另一個常見修法是用立即執行函數把i作為參數傳進新函數作用域for (var i 0; i 5; i) { (function(j) { setTimeout(function() { console.log(j); }, 1000); })(i); }如果考簡答大概率會讓你解釋閉包是什么、為什么存在、有什么坑、怎么解決。要答得漂亮最好講清楚三點閉包能在函數外部訪問到其內部作用域的變量它能讓變量長期駐留所以常被用來封裝私有狀態但如果使用不當也可能造成內存泄漏或循環引用。2.2 this指向不要背口訣要會看調用方式this指向問題是前端筆試的常青樹卷二里有個很經典的嵌套函數版本var name window; var obj { name: obj, getName: function() { return function() { return this.name; }; } }; console.log(obj.getName()());這段代碼最終打印的是window而不是obj。原因是函數調用時this指向取決于調用者。嵌套的那個匿名函數是獨立調用的沒有對象前綴所以this指向全局對象瀏覽器環境就是window。這類題目經常搭配三種修法來考把外層this保存到一個變量里比如var self this改用箭頭函數因為箭頭函數沒有自己的this它從定義時的外層作用域繼承或者在返回函數時用bind(this)。很多同學喜歡死背“誰調用就指向誰”的口訣但它的例外實在太多。我建議用更可靠的判定方法也就是看函數的調用表達式屬于哪一種fn()直接調用非嚴格模式下this指向全局對象。obj.fn()方法調用this指向obj。new fn()構造調用this指向新創建的對象。fn.call(obj)/fn.apply(obj)/fn.bind(obj)顯式綁定this指向指定的obj。把這四種情況分清楚再遇到任何this題都能慢慢推出來。卷二里還常把this和原型鏈結合出一道輸出題所以先復習好調用方式后面再遇到更復雜的場景也不容易翻車。2.3 作用域提升與塊級作用域最容易被忽略的送分題除了閉包和this作用域提升也是一道常見的選擇題。典型的題目是console.log(a); var a 1;控制臺輸出的是undefined而不是報錯。原因在于var聲明會被提升到當前作用域頂部但賦值并沒有被提升所以執行到console.log(a)時a已經存在值卻是undefined。這里需要注意如果換成let a控制臺就會直接報錯因為let存在暫時性死區在聲明之前訪問變量是不允許的。這些知識點單獨拎出來都不難但在限時筆試的高壓環境下很多同學會憑第一印象選出錯誤答案。我自己的習慣是遇到這類題先在草稿紙上把“聲明提升、賦值執行、作用域鏈查找”三個過程畫出來順序捋清楚再選正確率會高很多。3. 瀏覽器與網絡從渲染原理到跨域方案3.1 渲染流程、重排重繪與性能優化瀏覽器原理中渲染流程絕對是高頻考點。簡化的過程是HTML解析成DOM樹CSS解析成CSSOM樹兩者結合生成渲染樹然后計算布局、繪制合成。這個流程本身不難背難的是把它和相關問題串起來。比如卷子里可能問“為什么操作DOM會影響性能”答案不是簡單說“會重排重繪”而是要說明任何改變DOM大小、位置、結構的行為都可能觸發重新布局而布局計算會阻塞渲染線程所以頻繁操作DOM累積下來就會非??D。更深入的追問可能是“如何減少重排”。比較靠譜的答案包括批量修改DOM可以用documentFragment先積攢再一并插入用CSS類一次性替換多個樣式而不是逐條修改內聯樣式對動畫元素使用transform或opacity讓它獨立成合成層減少對主線程布局的觸發。這些實戰技巧在筆試里很加分因為閱卷人一眼就能看出你是不是真的處理過性能問題。3.2 跨域的源頭與主流解決方式跨域在卷二里出現頻率很高很多考生能列舉JSONP、CORS、postMessage但一到簡答題就說不清為什么會有跨域。它的根源是瀏覽器的同源策略目的是防止一個源隨意讀取另一個源的數據。同源要求協議、域名、端口三者完全一致任何一項不同都算跨域。理解了這個前提再去區分方案就清晰很多JSONP利用的是script標簽不受同源限制的特性只能發GET請求適合接口比較簡單的老系統。CORS是通過后端在響應頭里聲明允許的源這是現代Web應用的標準方案。需要區分簡單請求和非簡單請求非簡單請求會先觸發OPTIONS預檢。代理轉發適合開發環境和同源部署場景讓前端請求打到同源后端再由后端轉發到目標接口這樣瀏覽器層面就不存在跨域問題。選擇題里常見的坑是混淆“跨域能不能發請求”和“跨域能不能讀到響應”。實際上非簡單請求會先發出預檢請求真正的報錯往往發生在響應被攔截階段而不是請求沒有發出去。只要把這一點理解到位跨域選擇題的準確率能提升不少。3.3 事件循環與微任務宏任務別在嵌套里算錯順序瀏覽器部分大概率會有一道事件循環題。核心知識點是JavaScript是單線程的同步代碼先執行遇到異步任務會交給瀏覽器其他線程處理異步任務完成后回調會被放進任務隊列在執行棧清空后瀏覽器先處理微任務隊列再取出一個宏任務執行循環往復。經典的輸出題形如console.log(1); Promise.resolve().then(() console.log(2)); setTimeout(() console.log(3), 0); console.log(4);結果是1、4、2、3。原因就是Promise的then屬于微任務setTimeout的回調屬于宏任務微任務會在下一個宏任務之前先執行。如果再混入async/await會更容易算錯async function test() { console.log(1); await Promise.resolve(); console.log(2); } console.log(3); test(); console.log(4);這里的輸出順序是3、1、4、2。注意看調用test()后函數體里的console.log(1)是同步執行的遇到await才把后續代碼異步化所以主線程繼續往下執行了console.log(4)然后才輪到微任務里的console.log(2)。我的笨辦法是拿到題目先標記同步代碼再把宏任務和微任務分別列成小隊列最后按順序執行基本不會錯。3.4 HTTP緩存與存儲選擇題里的隱形考點除了渲染和跨域瀏覽器部分還經常穿插HTTP緩存和存儲相關的選擇題。緩存方面要能清晰區分強緩存和協商緩存強緩存直接通過Cache-Control或Expires判斷緩存是否有效有效期內不會發請求協商緩存則需要帶上Last-Modified或ETag去服務器驗證服務器返回304則繼續用緩存。存儲方面cookie、localStorage、sessionStorage、IndexedDB四者的區別也是高頻題考試時經常拿“容量限制”“是否隨請求發送”“是否自動過期”這幾個維度做混淆選項。這些知識看起來零散實際上背清楚一張對比表就能穩穩拿到分。4. CSS與布局BFC、flex與經典布局實戰4.1 BFC一個獨立渲染小世界CSS部分卷二對BFC的偏愛非常明顯。BFC全稱是塊級格式化上下文你可以把它理解為一個獨立的渲染小世界在這個小世界里元素的布局不會影響外部外部也不會影響它內部。哪些情況會觸發BFC浮動、絕對定位、inline-block、overflow不為visible的塊級元素、flex容器等都會觸發。卷子里??嫉念}目是“如何清除浮動”答案里一定會提到overflow: hidden。但你要能解釋它背后的原理給父容器創建了一個BFC讓浮動元素也參與父容器的高度計算從而把子元素“包”起來。另一個高頻考點是“如何防止margin合并”同樣可以通過在父容器上創建BFC讓相鄰元素的垂直margin被隔離在獨立的上下文中。這里我把容易混淆的觸發條件整理成一個清單float不是none。position是absolute或fixed。display是inline-block、table-cell、flex或grid。overflow不是visible??荚嚂r問到某個元素是否創建了BFC直接按這幾個條件套就能判斷。4.2 盒模型一個必拿的基礎分盒模型雖然簡單但錯的人非常多。標準盒模型里width只包含內容區而IE盒模型box-sizing: border-box里width包含了內容、padding和border。卷二選擇題特別喜歡給一組場景比如“給一個元素設置width: 100px; padding: 20px; border: 5px標準模型里的真實占位寬度是多少”很多人一緊張就算錯。其實答案很簡單標準模型下真實占位寬度是100 20*2 5*2 150px如果是border-box真實占位寬度就是100px。這類題沒有任何難度只要在讀題時先確認一下是否設置了box-sizing就能穩拿分。4.3 垂直居中與兩欄布局高頻編碼實操布局題也是卷二的高頻題尤其是“垂直居中”和“兩欄/三欄布局”。垂直居中看似簡單但不同場景對應不同最佳解。父容器寬高未知子元素尺寸也未知最推薦flex方案.parent { display: flex; justify-content: center; align-items: center; }如果要求兼容老瀏覽器可以用絕對定位加負margin但前提是子元素尺寸已知。還有基于transform: translate(-50%, -50%)的方案它的百分比是相對于元素自身尺寸計算的所以不需要知道具體寬高這也是它適合當作通用方案的原因。兩欄布局里經典做法是左側固定寬度右側自適應。用flex實現很簡單左邊flex: 0 0 240px右邊flex: 1。三欄布局則??际ケ季趾碗p飛翼布局目的是讓中間欄先渲染再通過負margin或padding把兩側讓出來。要理解這類布局核心是吃透“負margin會改變元素在文檔流中的占位位置”剩下就是組合游戲。死記代碼很容易忘但把原理想明白之后任何變體都能應對。5. 框架知識Vue/React 核心機制5.1 Vue的響應式原理從Object.defineProperty到Proxy2020年的筆試題框架部分默認你已經會Vue或React。Vue高頻題是“響應式原理是什么”要答清楚它不是魔術而是數據劫持加發布訂閱。2.x版本用Object.defineProperty遍歷對象屬性并定義getter/setter頁面讀取數據時收集依賴數據變化時派發通知觸發重新渲染。3.0版本改成用Proxy它能攔截更多操作比如屬性的新增和刪除同時性能更好。卷子里如果出這類題很可能搭配一個實操問題給Vue 2的數組新增元素為什么頁面不更新原因在于Object.defineProperty無法攔截通過索引設置新值這種操作所以需要借助$set或者重寫過的數組方法來觸發更新。很多同學只背了“數組要用$set”這個結論卻不知道背后的邊界在哪里。我的建議是復習時親手寫一個小例子在Vue 2和Vue 3里分別改數組親眼看看輸出差異這比背十篇面經都更牢固。5.2 虛擬DOM與diff為什么key不能亂用indexReact和Vue都會聊到虛擬DOM。它到底是什么可以理解為用JavaScript對象來描述DOM結構一次界面渲染對應一棵虛擬節點樹。為什么需要它因為直接操作真實DOM成本高而操作JavaScript對象是純內存操作非常快再加上diff算法能找出前后兩棵樹的差異只更新真正變化的部分整體性能體驗更好。卷子里??嫉牟⒉皇莇iff的每行代碼而是它的基本策略同層比較、tag不同直接替換、key用于盡可能復用節點。關于key高頻追問是“為什么列表渲染時不能濫用index作為key”。因為在數組頭部插入或刪除元素時index會整體變化導致原本被復用的節點錯亂輕則渲染狀態不對重則出現難以排查的交互問題。這個場景不需要高深技術用一個todo列表自己操作一遍就能理解。5.3 生命周期與組件通信框架部分的簡答題還經常圍繞生命周期出題。Vue里最常見的追問是created和mounted的區別created時數據已經初始化但DOM還沒掛載適合在這里發請求或初始化數據mounted時DOM已掛載適合做依賴真實DOM的操作。React那邊則常問函數組件里useEffect如何對應類組件的生命周期以及為什么要清理副作用。組件通信也是必考方向Vue里的props/emit、provide/inject、VuexReact里的狀態提升、Context、Redux都要能說出各自的適用場景。答這類題不需要長篇大論核心點出“單向數據流”和“狀態管理解決什么問題”就夠了。6. 編碼題實戰手寫實現與算法基礎6.1 手寫防抖與節流三個細節決定分數編碼題部分手寫防抖和節流幾乎是保留節目。防抖的核心思想是事件觸發后等待一段時間再執行如果在這個等待時間里再次觸發就重新計時。節流的核心思想是在一段時間內只允許執行一次。它們分別適合什么場景輸入框實時搜索適合防抖因為用戶連續輸入時不需要頻繁請求滾動監聽和按鈕防連擊適合節流因為需要保證一定的執行頻率。防抖的經典實現是這樣的function debounce(fn, delay) { let timer null; return function(...args) { const context this; clearTimeout(timer); timer setTimeout(() { fn.apply(context, args); }, delay); }; }這里有三個細節很容易丟分。第一要保留this指向所以用了apply第二要透傳參數所以用了...args第三是否能說出“什么是立即執行版本、什么場景需要立即執行”這決定了你是背代碼還是真掌握了原理。節流的實現也有時間戳和定時器兩種常見版本考試時能說出兩者區別第一次是否立即執行、最后一次是否補執行會非常加分。6.2 數組去重與扁平化從一行API到進階追問數組去重是編碼題的另一個??妥詈唵蔚陌姹臼莄onst unique arr Array.from(new Set(arr));一行代碼就能解決。但你能講講它的局限嗎Set去重使用類似全等的規則對基本類型沒問題對對象類型則只能比較引用所以兩個內容相同但引用不同的對象都會被保留。如果要求對象數組按某個字段去重就需要用Map來記錄。再往下考數組扁平化也有多層考法基礎版是直接調用flat()方法進階版是手寫遞歸再進一層是支持指定深度。這些題目表面看是考API實際是考你能否在特定限制下寫出清晰可靠的代碼。6.3 深拷貝、手寫call/apply/bind卷二的手寫題還可能涉及深拷貝和手寫call/apply/bind。深拷貝的面試題很考驗功底基礎版是遞歸遍歷屬性進階版需要處理數組、Date、RegExp等特殊類型還要用WeakMap緩存引用避免循環引用導致死循環。手寫call的思路則是利用臨時屬性改變this指向Function.prototype.myCall function(context, ...args) { context context || window; const fn Symbol(fn); context[fn] this; const result context[fn](...args); delete context[fn]; return result; };這里用Symbol是為了避免覆蓋context上已有的同名屬性。其實這類手寫題對日常業務開發幫助不大但它在筆試里能快速檢驗一個人對JavaScript底層機制的理解程度。準備方式是理解思路后親手敲三遍而不是把代碼背下來。6.4 算法題策略別輸在“看得懂卻寫不出”算法題在卷二里的難度一般不會達到LeetCode困難題更多是鏈表反轉、二叉樹層序遍歷、最長不重復子串這類中等偏下題目。很多同學的問題不是沒有思路而是沒有把邊界條件、空值處理、循環終止條件寫完整。我建議刷題時不要只看題解要動筆把主路徑寫出來再補邊界。筆試時間有限遇到算法題先花兩分鐘確認輸入輸出再寫一個能跑通的主路徑最后補邊界這個順序比追求最優解更實際。7. 常見問題與排查技巧實錄7.1 考生最容易丟分的三個習慣我翻過不少同學的筆試復盤發現丟分點往往不在知識本身而在表達和代碼習慣。第一個壞習慣是簡答題寫太短只給結論不給過程比如“跨域用CORS”卻沒有說明場景和限制。第二個壞習慣是代碼題一上來就封裝各種工具函數結果主功能反而沒寫完正確策略是先實現核心流程保證基本用例通過再考慮封裝擴展。第三個壞習慣是不檢查邊界比如防抖函數沒有處理this、數組去重沒有考慮空數組這些小問題會讓閱卷人對工程素養打個問號。7.2 時間分配與答題順序建議根據卷二這類基礎綜合卷的題量和難度我的建議是拿到卷子先快速掃一遍全部題目估算每種題型的大概耗時。選擇題不要猶豫太久先按第一直覺選不確定的做標記簡答題控制在每題十分鐘以內寫出核心邏輯鏈編碼題給足時間最好留出二十分鐘做檢查。做題順序上建議先做自己有把握的模塊把分數先拿到手再回頭啃硬骨頭。筆試往往按總分篩選穩拿的分比鉆牛角尖更有價值。7.3 常見問題速查表問題常見原因解決辦法事件循環輸出順序算錯混淆微任務與宏任務先列同步代碼再分別收集兩個隊列閉包打印全是循環末值沒理解var作用域與異步時機改用let或IIFE垂直居中布局失效父容器或子元素尺寸約束不一致確認flex三要素檢查子元素是否被壓縮跨域請求報錯忽略預檢請求或響應頭配置分清簡單請求與非簡單請求數組增刪后列表渲染錯亂key用了index改用業務唯一id這張表是我根據同學常問的問題整理出來的不一定覆蓋全部但能解決八成筆試現場出現的低級失誤。7.4 手寫題的一個小技巧最后分享一個我自己用過很多次的小技巧手寫題如果一時想不起具體API不要空著。先把函數簽名寫出來把注釋寫好再用偽代碼表達思路。閱卷者通常會給“思路正確但實現有瑕疵”的答案額外同情分。比如防抖你忘了clearTimeout那就寫“清除之前的定時器”這種注釋至少能讓閱卷人知道你的方向是對的。卷面整潔、命名規范在同等條件下真的能拉開差距。小紅書這套2020年的前端筆試題卷二我后來給身邊準備跳槽的同事也用過。大家的共同感受是題目不算難但很能暴露基本功。閉包、事件循環、BFC、跨域這些知識點平時寫業務的時候感覺都用不上可一旦被問到才發現自己只是眼熟而不是掌握。準備這類筆試最忌諱的就是只看面經背答案。我的體會是把每個考點都自己動手驗證一遍比如在瀏覽器控制臺跑一遍this指向的代碼看看輸出到底是什么把防抖節流的調用方式寫進一個真實的小項目里感受它們在網絡請求上的區別。這樣學一遍比考前突擊三十道題都管用。希望這篇拆解能幫你在下一次筆試里少踩幾個我已經替你踩過的坑。