到安全的全面?zhèn)淇贾改? alt=)
2019年那時(shí)候奇安信還沒(méi)有從360體系里完全獨(dú)立出來(lái)多久春招筆試的題目風(fēng)格其實(shí)已經(jīng)很有辨識(shí)度了。我印象里那套前端開發(fā)試題整體難度中等偏上不算變態(tài)但覆蓋面很廣基礎(chǔ)不扎實(shí)的人很容易在及格線附近徘徊。今天把這套題掰開揉碎講一講不只是給答案更重要的是講清楚每一類題目背后的考察邏輯和備考思路。不管你是準(zhǔn)備應(yīng)屆生校招還是社招想跳槽去安全行業(yè)的技術(shù)團(tuán)隊(duì)這套題里的知識(shí)點(diǎn)都值得過(guò)一遍。1. 試題整體印象怎么看懂一套前端筆試題的“出題人意圖”拿到任何一套筆試題先別急著埋頭做題。我習(xí)慣先花五分鐘把整張卷子掃一遍標(biāo)出題目類型和分值分布這時(shí)候出題人的“口味”基本就暴露了。1.1 奇安信2019春招前端卷的題型構(gòu)成從整體來(lái)看這套題大致分為四個(gè)板塊基礎(chǔ)理論題、代碼輸出題、手寫實(shí)現(xiàn)題和開放設(shè)計(jì)題。基礎(chǔ)理論題覆蓋HTML、CSS、JavaScript、瀏覽器原理和網(wǎng)絡(luò)基礎(chǔ)代碼輸出題主要考察JavaScript的語(yǔ)言特性比如作用域、閉包、異步順序手寫實(shí)現(xiàn)題則集中在數(shù)組去重、深拷貝、防抖節(jié)流這類經(jīng)典場(chǎng)景開放設(shè)計(jì)題會(huì)給你一個(gè)業(yè)務(wù)場(chǎng)景讓你描述技術(shù)方案。這種結(jié)構(gòu)說(shuō)明什么說(shuō)明奇安信的前端團(tuán)隊(duì)在招人時(shí)非??粗睾蜻x人的語(yǔ)言基本功和工程思維而不是看你背了多少框架API。那一年Vue和React已經(jīng)非常流行了但這套題里框架相關(guān)的考點(diǎn)占比并不高反而基礎(chǔ)能力占了大頭。這和當(dāng)時(shí)很多大廠的出題風(fēng)格是反著來(lái)的——當(dāng)時(shí)不少公司一上來(lái)就問(wèn)Vue生命周期、React diff算法的細(xì)節(jié)。后來(lái)我復(fù)盤了一下這個(gè)思路其實(shí)和奇安信的業(yè)務(wù)屬性高度相關(guān)。安全公司做的前端產(chǎn)品比如管理后臺(tái)、態(tài)勢(shì)感知大屏、終端管控界面絕大多數(shù)時(shí)候不是面向海量C端用戶的而是面向企業(yè)客戶和安全運(yùn)維人員的。這類產(chǎn)品對(duì)穩(wěn)定性、數(shù)據(jù)準(zhǔn)確性、安全性要求極高花里胡哨的交互反而是次要的。所以面試官更在意你能不能寫出靠譜的代碼而不是能不能背出某個(gè)框架的源碼細(xì)節(jié)。1.2 這套題的備考價(jià)值在哪兒哪怕你不是去奇安信面試這套題的參考價(jià)值也很大。原因很簡(jiǎn)單它考察的知識(shí)點(diǎn)全部是前端領(lǐng)域“保值率”最高的那部分。反而是那些當(dāng)年很熱門的框架題放到今天可能早就過(guò)時(shí)了。ES6語(yǔ)法、事件循環(huán)、原型鏈、HTTP緩存、跨域方案這些內(nèi)容今天依然是前端面試的絕對(duì)主力。所以如果你現(xiàn)在正在準(zhǔn)備前端面試與其漫無(wú)目的地刷題不如拿這套題當(dāng)一次自測(cè)。能把這套題做到80分以上至少說(shuō)明你的JS基礎(chǔ)是過(guò)關(guān)的面對(duì)大多數(shù)公司的筆試環(huán)節(jié)都不會(huì)太虛。2. HTML與CSS考點(diǎn)安全公司會(huì)在布局題里埋什么坑很多人對(duì)HTML和CSS題不以為然覺(jué)得就是背背標(biāo)簽、記記屬性。但實(shí)際上奇安信這類公司的前端筆試?yán)顲SS題的陷阱往往藏得很深。2.1 盒模型與BFC不只是概念題盒模型是CSS的第一課但能把content-box和border-box的區(qū)別講透的人并不多。標(biāo)準(zhǔn)盒模型content-box的width只包含內(nèi)容區(qū)而IE盒模型border-box的width包含內(nèi)容、內(nèi)邊距和邊框。實(shí)際開發(fā)里我們普遍通過(guò)box-sizing: border-box來(lái)統(tǒng)一全局盒模型因?yàn)檫@樣更符合人腦直覺(jué)——你設(shè)置多大的寬度元素就占多大的寬度。BFC塊級(jí)格式化上下文同樣是高頻考點(diǎn)。BFC可以理解為一個(gè)獨(dú)立的渲染區(qū)域內(nèi)部元素的布局不會(huì)影響外部。觸發(fā)BFC的方式有float非none、position為absolute或fixed、overflow非visible、display為inline-block或flex等。常見的應(yīng)用場(chǎng)景有兩個(gè)一是清除浮動(dòng)當(dāng)子元素全部浮動(dòng)導(dǎo)致父容器高度塌陷時(shí)給父容器設(shè)置overflow: hidden觸發(fā)BFC父容器就會(huì)重新包裹子元素二是防止 margin 折疊兩個(gè)兄弟元素的上下margin在垂直方向會(huì)取較大值合并但把它們各自放進(jìn)獨(dú)立的BFC里就可以避免折疊。這里有個(gè)實(shí)操經(jīng)驗(yàn)真正做后臺(tái)管理系統(tǒng)的時(shí)候overflow: hidden觸發(fā)BFC來(lái)清除浮動(dòng)這個(gè)方案在新老瀏覽器上表現(xiàn)都不錯(cuò)但也有副作用——如果子元素需要溢出顯示下拉菜單會(huì)被父容器裁掉。所以現(xiàn)代項(xiàng)目里我一般優(yōu)先用display: flow-root或者直接改用flex布局這兩種方式干凈利落。2.2 經(jīng)典布局題圣杯布局與雙飛翼布局圣杯布局和雙飛翼布局是CSS筆試的常青樹它們要解決的核心問(wèn)題是一樣的左右兩欄固定寬度中間主內(nèi)容欄自適應(yīng)寬度且中間欄在DOM結(jié)構(gòu)上優(yōu)先渲染這對(duì)首屏加載和SEO友好。圣杯布局的思路是三欄都用float: left中間欄寬度設(shè)為100%然后利用padding為左右欄騰出位置。左右兩欄通過(guò)負(fù)margin移動(dòng)到對(duì)應(yīng)位置——左欄margin-left: -100%讓它上移到中間欄的左側(cè)起點(diǎn)右欄margin-left: -自身寬度讓它上移到最右側(cè)。最后給三欄的父容器設(shè)置左右padding寬度等于左右欄的寬度。雙飛翼布局則換了個(gè)思路中間欄內(nèi)部再套一層div左右兩欄的定位方式和圣杯一樣但為中間欄內(nèi)容騰位置時(shí)不用父容器的padding而是給中間欄內(nèi)部那層div設(shè)置左右margin。這樣父容器寬度就是100%左右欄直接貼在兩側(cè)省去了相對(duì)定位的步驟。實(shí)際筆試?yán)镞@種題不一定要求你手寫完整代碼更多是讓你說(shuō)清楚實(shí)現(xiàn)思路和區(qū)別。但我的建議是哪怕不寫完整代碼也要把這個(gè)過(guò)程推導(dǎo)清楚同時(shí)提一句“現(xiàn)在都用flex或grid實(shí)現(xiàn)了”這樣面試官會(huì)覺(jué)得你既懂老方案也跟得上新趨勢(shì)。2.3 垂直水平居中的N種姿勢(shì)垂直水平居中幾乎每次筆試都會(huì)出現(xiàn)而且出題人常常會(huì)補(bǔ)一句“元素高度未知”。這個(gè)限制條件很關(guān)鍵它排除了line-height和絕對(duì)定位配合負(fù)margin的方案。我一般會(huì)給面試官按場(chǎng)景分類元素定寬高絕對(duì)定位 四角為0 margin: auto這個(gè)方案兼容性最好不定寬高絕對(duì)定位 transform: translate(-50%, -50%)用CSS3解決唯一注意父容器要相對(duì)定位flex方案父元素display: flex; align-items: center; justify-content: center;這是當(dāng)前項(xiàng)目里的首選代碼量最少grid方案父元素display: grid; place-items: center;更簡(jiǎn)潔。實(shí)話說(shuō)現(xiàn)在項(xiàng)目里我從來(lái)不給彈窗用transform居中而是直接用flex。因?yàn)閠ransform會(huì)創(chuàng)建新的層疊上下文有時(shí)候會(huì)連帶出z-index和定位問(wèn)題。筆試寫方案的時(shí)候可以提一句“flex最穩(wěn)transform有層疊上下文副作用”這種細(xì)節(jié)會(huì)讓你跟其他候選人明顯拉開差距。3. JavaScript核心考點(diǎn)輸出題背后藏著多少語(yǔ)言特性JS部分永遠(yuǎn)是前端筆試的主導(dǎo)奇安信也不例外。這套題里代碼輸出題占比不低而且專門挑那些“看似簡(jiǎn)單一寫就錯(cuò)”的語(yǔ)法糖來(lái)考。3.1 作用域、閉包和立即執(zhí)行函數(shù)經(jīng)典題目var在for循環(huán)中配合setTimeout打印的問(wèn)題。連續(xù)輸出10個(gè)10原因在于var聲明是函數(shù)作用域循環(huán)體里的i是同一個(gè)變量等到定時(shí)器回調(diào)執(zhí)行時(shí)循環(huán)早已結(jié)束i已經(jīng)變成了10。解決辦法有兩個(gè)用let聲明讓每一次循環(huán)都綁定一個(gè)新的i或者用立即執(zhí)行函數(shù)IIFE傳遞當(dāng)前值。閉包在奇安信這類基礎(chǔ)題里幾乎是必考的。比如問(wèn)你以下代碼輸出什么function foo() { let count 0; return function () { count; console.log(count); }; } const f foo(); f(); f();答案是1, 2。核心考點(diǎn)是內(nèi)部函數(shù)引用了外部函數(shù)的變量外部函數(shù)執(zhí)行結(jié)束后變量沒(méi)有被垃圾回收而是被內(nèi)部函數(shù)繼續(xù)持有。這也解釋了為什么閉包可能造成內(nèi)存泄漏——如果你在DOM事件里用了閉包又忘了移除事件綁定整個(gè)作用域鏈都會(huì)殘留。3.2 this的指向問(wèn)題別背口訣畫調(diào)用棧this指向是JS筆試的重災(zāi)區(qū)也是很多工作兩三年的前端說(shuō)不清的地方。出題人很愛(ài)把this和對(duì)象調(diào)用、普通函數(shù)調(diào)用、箭頭函數(shù)混在一起出題比如const obj { name: qihoo, getName: function () { return this.name; }, getNameArrow: () { return this.name; }, }; console.log(obj.getName()); console.log(obj.getNameArrow());第一行輸出qihoo沒(méi)問(wèn)題this指向調(diào)用者obj。第二行就有意思了箭頭函數(shù)沒(méi)有自己的this它的this是在定義時(shí)從外部作用域捕獲的所以這里的this指向全局對(duì)象this.name是undefined。我不建議死記“誰(shuí)調(diào)用指向誰(shuí)”這種口訣因?yàn)橛龅郊^函數(shù)這口訣立刻就失效了。更好的分析方式是先把函數(shù)調(diào)用方式畫出來(lái)看它是普通調(diào)用、對(duì)象方法調(diào)用、call/apply調(diào)用還是new調(diào)用然后逐層確定this如果函數(shù)是箭頭函數(shù)直接往上找最近的外層普通函數(shù)的this。這套邏輯可以應(yīng)對(duì)所有this考題。3.3 事件循環(huán)與異步順序輸出題里最能拉開差距的就是事件循環(huán)。比如考這樣的代碼console.log(a); setTimeout(() { console.log(b); }, 0); Promise.resolve().then(() { console.log(c); }); console.log(d);輸出順序是a d c b。這里考察的微任務(wù)和宏任務(wù)執(zhí)行順序同步代碼先執(zhí)行然后本輪事件循環(huán)中的微任務(wù)Promise的then回調(diào)優(yōu)先于宏任務(wù)setTimeout回調(diào)執(zhí)行。即使setTimeout的延遲是0它也會(huì)被放進(jìn)宏任務(wù)隊(duì)列要等下一輪事件循環(huán)才輪到。2019年的考題還很少問(wèn)到async/await和微任務(wù)嵌套的復(fù)雜情況但現(xiàn)在這里已經(jīng)成了必考深水區(qū)。我的建議是把事件循環(huán)的機(jī)制徹底搞清楚執(zhí)行棧清空后微任務(wù)隊(duì)列會(huì)一次性清空然后再取一個(gè)宏任務(wù)執(zhí)行宏任務(wù)執(zhí)行過(guò)程中又會(huì)產(chǎn)生新的微任務(wù)這些微任務(wù)會(huì)排在下一次宏任務(wù)之前執(zhí)行。把這條鏈路理解透不管題目怎么嵌套你都不會(huì)懵。3.4 對(duì)象與數(shù)組方法map、filter、reduce的熟用程度數(shù)組方法題在筆試題里也經(jīng)常出現(xiàn)。比如[1, 2, 3].map(parseInt)的輸出是什么答案是[1, NaN, NaN]。原因是parseInt接收兩個(gè)參數(shù)map回調(diào)會(huì)把當(dāng)前值和索引傳進(jìn)去所以第二次調(diào)用是parseInt(2, 1)第三次是parseInt(3, 2)都是無(wú)效進(jìn)制返回NaN。這類題考察的不是你會(huì)不會(huì)用map而是你清不清楚map回調(diào)的完整參數(shù)列表當(dāng)前值、索引、原數(shù)組以及parseInt的完整參數(shù)簽名字符串、進(jìn)制基數(shù)。所以平時(shí)寫代碼多留意標(biāo)準(zhǔn)API的完整簽名少依賴記憶中的“常用方式”這種坑踩一次就能記住。4. 框架與工程化考點(diǎn)安全公司也躲不開的范疇雖然前面說(shuō)這套題基礎(chǔ)占比大但框架和工程化的內(nèi)容也沒(méi)有缺席。尤其是在開放題和應(yīng)用題里Vue/React的使用經(jīng)驗(yàn)是加分項(xiàng)。4.1 Vue與React從“會(huì)寫頁(yè)面”到“說(shuō)清原理”筆試中常見的框架問(wèn)題有Vue的雙向綁定原理、虛擬DOM的作用、key的作用、組件通訊方式、生命周期鉤子對(duì)比。React的常見考點(diǎn)則是state與props的區(qū)別、setState的異步性、受控組件與非受控組件、 useState/useEffect的依賴數(shù)組、Fiber架構(gòu)解決了什么問(wèn)題。奇安信前臺(tái)產(chǎn)品偏管理類和展示類Vue的普及率在我印象里比React高一些所以Vue出題概率更大。重點(diǎn)準(zhǔn)備這幾個(gè)問(wèn)題computed和watch的區(qū)別computed是聲明式地根據(jù)依賴數(shù)據(jù)計(jì)算出新值有緩存watch是監(jiān)聽數(shù)據(jù)變化執(zhí)行副作用操作用于異步或開銷較大的操作。v-if和v-show的區(qū)別v-if是條件渲染不滿足條件直接不渲染DOM切換有渲染銷毀成本v-show是css的display切換只是視覺(jué)隱藏適合頻繁切換的場(chǎng)景。key的作用key幫助diff算法識(shí)別節(jié)點(diǎn)復(fù)用當(dāng)列表順序變化時(shí)正確的key可以最小化DOM操作。不建議用數(shù)組索引當(dāng)key因?yàn)樵鰟h元素后索引會(huì)變可能導(dǎo)致狀態(tài)錯(cuò)亂。4.2 前端性能優(yōu)化筆試愛(ài)問(wèn)實(shí)際項(xiàng)目更要會(huì)安全產(chǎn)品的前端頁(yè)面往往數(shù)據(jù)量大、圖表多、刷新頻繁性能優(yōu)化是實(shí)際問(wèn)題。筆試?yán)锏男阅軆?yōu)化題通常不要求你寫代碼但要求你按場(chǎng)景列出方案。我通常會(huì)按瀏覽器工作流程來(lái)組織答案網(wǎng)絡(luò)層面做資源壓縮合并、HTTP緩存、CDN分發(fā)渲染層面減少DOM操作次數(shù)避免強(qiáng)制同步回流用文檔碎片或虛擬列表處理長(zhǎng)列表代碼層面做按需加載和懶加載拆分首屏代碼優(yōu)化圖片格式和尺寸。這里有一個(gè)很多人的誤區(qū)一說(shuō)性能優(yōu)化就在扣JS執(zhí)行時(shí)間但實(shí)際上首屏性能的大頭通常在資源加載和網(wǎng)絡(luò)請(qǐng)求。2019年我優(yōu)化一個(gè)數(shù)據(jù)中心管理后臺(tái)時(shí)把十幾個(gè)第三方依賴全部改為按需引入首屏體積從2.4M降到800K加載時(shí)間從5秒變成2秒左右這個(gè)收益遠(yuǎn)大于優(yōu)化幾個(gè)函數(shù)執(zhí)行耗時(shí)。所以面試時(shí)一定要先說(shuō)網(wǎng)絡(luò)和加載層面的優(yōu)化再說(shuō)渲染和運(yùn)行時(shí)的優(yōu)化這更符合真實(shí)場(chǎng)景的優(yōu)先級(jí)。4.3 工程化工具鏈webpack問(wèn)題怎么答不露怯工程化題偶爾會(huì)考webpack的構(gòu)建流程、loader和plugin的區(qū)別、如何做代碼分割。這類題其實(shí)有個(gè)答題模板先說(shuō)整個(gè)構(gòu)建過(guò)程是入口解析、依賴收集、模塊編譯、代碼生成這幾個(gè)階段loader負(fù)責(zé)對(duì)模塊源代碼進(jìn)行轉(zhuǎn)換是在編譯單個(gè)模塊時(shí)執(zhí)行plugin則在整個(gè)構(gòu)建流程里都可以介入通過(guò)鉤子機(jī)制完成更廣泛的任務(wù)。只要把base流程說(shuō)清楚然后舉一兩個(gè)常用的loader和plugin例子比如babel-loader處理ES6、eslint-loader做代碼檢查、HtmlWebpackPlugin生成HTML、TerserPlugin做代碼壓縮基本就能過(guò)關(guān)。5. 安全公司面試的隱藏彩蛋前端安全題才是重頭戲奇安信做的是安全業(yè)務(wù)前端筆試題里幾乎一定會(huì)出現(xiàn)安全相關(guān)的考點(diǎn)。這既是奇安信面試的特色也是非安全公司面試容易忽略的盲區(qū)。把這塊準(zhǔn)備好你能給面試官留下極其深刻的印象。5.1 XSS攻擊與防御別只知道alert彈窗XSS跨站腳本攻擊是前端安全里最經(jīng)典的考點(diǎn)?;驹硎枪粽甙褠阂饽_本注入到頁(yè)面中當(dāng)其他用戶訪問(wèn)這個(gè)頁(yè)面時(shí)腳本在用戶瀏覽器中執(zhí)行從而盜取Cookie、篡改頁(yè)面內(nèi)容或者發(fā)起請(qǐng)求。防御手段有三個(gè)層次。第一層是輸入過(guò)濾用戶輸入的內(nèi)容一律當(dāng)作數(shù)據(jù)而非代碼處理前端要對(duì)特殊字符做轉(zhuǎn)義比如替換為lt;替換為gt;第二層是輸出編碼在不同上下文HTML標(biāo)簽內(nèi)、屬性內(nèi)、JavaScript內(nèi)要用不同的編碼方式第三層是設(shè)置安全響應(yīng)頭比如Content-Security-PolicyCSP可以限制頁(yè)面能加載的資源和腳本來(lái)源就算腳本被注入也執(zhí)行不了。5.2 CSRF與CORS跨域問(wèn)題的安全視角CSRF跨站請(qǐng)求偽造的經(jīng)典場(chǎng)景是用戶登錄了銀行網(wǎng)站又去訪問(wèn)了惡意網(wǎng)站惡意網(wǎng)站利用瀏覽器自動(dòng)攜帶Cookie的特性構(gòu)造一個(gè)跨站請(qǐng)求讓用戶在不知情的情況下完成轉(zhuǎn)賬或修改操作。防御CSRF的思路主要有校驗(yàn)請(qǐng)求頭中的Origin或Referer用token機(jī)制——后端生成一個(gè)隨機(jī)token存到表單或請(qǐng)求頭里服務(wù)器驗(yàn)證token是否有效設(shè)置Cookie的SameSite屬性限制跨站請(qǐng)求是否攜帶Cookie用自定義請(qǐng)求頭因?yàn)榭缯菊?qǐng)求無(wú)法自動(dòng)構(gòu)造自定義頭。CORS跨域資源共享和CSRF容易混淆但考點(diǎn)完全不同。CORS是瀏覽器對(duì)外部請(qǐng)求的一種協(xié)議規(guī)范當(dāng)頁(yè)面請(qǐng)求了其他域的接口瀏覽器會(huì)先發(fā)起預(yù)檢請(qǐng)求OPTIONS服務(wù)器通過(guò)響應(yīng)頭里的Access-Control-Allow-Origin來(lái)聲明是否允許跨域。前端開發(fā)中常見的跨域方案除了CORS外還有JSONP、代理服務(wù)器等。有一道題讓我印象很深題目問(wèn)的是“前端做了CORS配置能徹底解決跨域安全問(wèn)題嗎”。正確答案當(dāng)然是不能。CORS只是瀏覽器層面的訪問(wèn)控制接口本身是否安全還需要后端做鑒權(quán)驗(yàn)證。你能在理解CORS的同時(shí)指出這個(gè)邊界面試官會(huì)明顯高看你一眼。5.3 路徑遍歷與文件上傳場(chǎng)景熱詞里提到了“奇安信 輸入驗(yàn)證路徑遍歷”這個(gè)知識(shí)點(diǎn)同樣會(huì)出現(xiàn)在筆試題甚至實(shí)際產(chǎn)品中。路徑遍歷是指攻擊者通過(guò)構(gòu)造../這樣的特殊路徑訪問(wèn)服務(wù)器上本不該被訪問(wèn)的文件。前端在接收文件上傳、下載參數(shù)時(shí)一定要對(duì)路徑參數(shù)做白名單校驗(yàn)不能直接拼接用戶輸入到文件路徑里。比如下載文件的接口如果后端拿到的文件名參數(shù)是../../etc/passwd拼接路徑后就可能讀取到系統(tǒng)敏感文件。前端的責(zé)任是上傳文件時(shí)校驗(yàn)文件類型限制文件大小對(duì)文件名做合法性校驗(yàn)下載文件時(shí)不要把用戶輸入直接作為路徑參數(shù)傳遞而是建議后端用映射ID來(lái)定位文件。6. 手寫實(shí)現(xiàn)題這些代碼現(xiàn)在就可以背下來(lái)手寫題是筆試?yán)镒钅苤苯臃从尘幋a能力的一環(huán)。說(shuō)實(shí)話這部分題是有套路的核心題型不多練熟了就是送分題。但如果沒(méi)練過(guò)現(xiàn)場(chǎng)寫很容易翻車。6.1 數(shù)組去重至少給出三種思路數(shù)組去重是最經(jīng)典的手寫題也是我當(dāng)年筆試的第一道手寫題。現(xiàn)在回想起來(lái)這道題的關(guān)鍵不是寫出來(lái)而是寫得全面、有層次。最樸素的方案是雙重循環(huán)或使用indexOf做去重時(shí)間復(fù)雜度較高。用Set是最簡(jiǎn)潔的方案一行代碼搞定[...new Set(arr)]。但面試官往往不希望你就此打住還會(huì)追問(wèn)如果數(shù)組里有對(duì)象Set去重還靠譜嗎這時(shí)候要答到Array.from(new Set(arr.map(item JSON.stringify(item))))來(lái)對(duì)對(duì)象序列化后去重或者用reduce配合Map來(lái)做按屬性去重。我寫題時(shí)的習(xí)慣是先說(shuō)清楚各種方案的優(yōu)缺點(diǎn)再選一種代碼量適中的方案寫上。除非題目明確要求性能最優(yōu)否則不要一上來(lái)就寫奇技淫巧。6.2 深拷貝考你會(huì)不會(huì)考慮邊界情況深拷貝題的高頻程度不用多說(shuō)。手寫一個(gè)遞歸深拷貝看起來(lái)簡(jiǎn)單但能拿滿分的人不多?;A(chǔ)版本是判斷值類型和引用類型值類型直接返回引用類型創(chuàng)建新對(duì)象后遞歸復(fù)制屬性。但要注意幾個(gè)邊界數(shù)組和對(duì)象要分別處理要防止循環(huán)引用否則會(huì)棧溢出函數(shù)的特殊處理不算重點(diǎn)但要說(shuō)清楚。比較穩(wěn)妥的寫法是function deepClone(obj, map new WeakMap()) { if (obj null || typeof obj ! object) return obj; if (map.has(obj)) return map.get(obj); const result Array.isArray(obj) ? [] : {}; map.set(obj, result); for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { result[key] deepClone(obj[key], map); } } return result; }這里用WeakMap來(lái)處理循環(huán)引用是關(guān)鍵點(diǎn)。很多人寫深拷貝不考慮循環(huán)引用一遇到a.self a這種結(jié)構(gòu)就爆棧。能主動(dòng)想到用WeakMap處理這個(gè)問(wèn)題這道題基本穩(wěn)了。6.3 防抖和節(jié)流應(yīng)用場(chǎng)景比代碼本身更重要防抖debounce和節(jié)流throttle也是手寫題里的??汀7蓝兜暮诵氖恰把舆t執(zhí)行”在事件觸發(fā)后的一段時(shí)間內(nèi)如果再次觸發(fā)就重新計(jì)時(shí)適用于輸入框搜索聯(lián)想、窗口resize后的計(jì)算節(jié)流的核心是“限制執(zhí)行頻率”一段時(shí)間內(nèi)只執(zhí)行一次適用于滾動(dòng)加載、按鈕連點(diǎn)、拖拽等場(chǎng)景。一個(gè)比較完整的防抖實(shí)現(xiàn)是function debounce(fn, delay, immediate false) { let timer null; return function (...args) { if (immediate !timer) fn.apply(this, args); if (timer) clearTimeout(timer); timer setTimeout(() { fn.apply(this, args); timer null; }, delay); }; }注意這里用apply綁定this并且...args接收參數(shù)這兩個(gè)細(xì)節(jié)很多培訓(xùn)班出來(lái)的候選人都會(huì)漏。如果面試官讓你說(shuō)應(yīng)用場(chǎng)景別只說(shuō)搜索輸入可以補(bǔ)充一個(gè)“請(qǐng)求取消”的場(chǎng)景用戶快速點(diǎn)擊多個(gè)篩選條件時(shí)只保留最后一個(gè)請(qǐng)求結(jié)果既能防止重復(fù)請(qǐng)求也能避免舊請(qǐng)求覆蓋新請(qǐng)求。6.4 其他高頻手寫題清單除了上面幾道下面是2019年前后前端筆試中出現(xiàn)頻率很高的手寫題建議每道題都能做到10分鐘內(nèi)獨(dú)立寫出可運(yùn)行版本實(shí)現(xiàn)一個(gè)new操作符實(shí)現(xiàn)Function.prototype.call/apply/bind實(shí)現(xiàn)一個(gè)Promise.all實(shí)現(xiàn)一個(gè)簡(jiǎn)單的發(fā)布訂閱EventEmitter實(shí)現(xiàn)instanceof的底層邏輯實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Object.create洗牌算法Fisher-Yates手寫一個(gè)簡(jiǎn)單的MVVM響應(yīng)式系統(tǒng)這些題目雖然看著多但其實(shí)是相互關(guān)聯(lián)的。比如Promise.all的核心是理解Promise的執(zhí)行和狀態(tài)流轉(zhuǎn)EventEmitter的核心是維護(hù)一個(gè)事件回調(diào)數(shù)組。把底層邏輯想明白代碼寫出來(lái)就是水到渠成的事。7. 時(shí)間分配與答題策略筆試比的不只是會(huì)多少一套筆試題做下來(lái)很多人掛掉的原因不是不會(huì)而是來(lái)不及做。筆試的時(shí)間分配直接決定你的得分上限。7.1 拿到卷子先做這三件事第一快速瀏覽全卷把所有題目分成“秒殺題”“思考題”“硬骨頭”三類。第二先把秒殺題做完并檢查一遍保證拿滿基礎(chǔ)分。第三再去做思考題最后進(jìn)攻硬骨頭。不要把時(shí)間耗在某一題上比如深拷貝寫了一半卡住就先跳到下一題最后再回來(lái)補(bǔ)。我見過(guò)不少人在一道手寫題上死磕了30分鐘結(jié)果后面10道基礎(chǔ)選擇題全沒(méi)做。這種策略性失誤完全可以通過(guò)考前模擬來(lái)避免。建議考前自己卡著時(shí)間做兩套完整的真題或高質(zhì)量模擬題別只看題不解題。7.2 代碼題的“過(guò)程分”怎么拿手寫題即使寫不出來(lái)完整答案也要把自己的思路分步驟寫下來(lái)。比如深拷貝寫不出來(lái)你寫了“先判斷類型然后遞歸復(fù)制注意循環(huán)引用”三行字也能拿一部分過(guò)程分。更聰明的做法是在注釋里寫清楚思路然后寫出一個(gè)不完美但思路正確的版本——筆試批卷人更看重你解決問(wèn)題的思維路徑而不是最后的代碼是否一字不差。另外手寫題的代碼格式一定要干凈。變量命名合理、縮進(jìn)統(tǒng)一、函數(shù)抽取清晰這些細(xì)節(jié)都能加分。別小看這些印象分在水平接近的候選人里代碼風(fēng)格好的往往優(yōu)先進(jìn)面試。7.3 安全公司的筆試復(fù)習(xí)重點(diǎn)既然目標(biāo)公司是安全行業(yè)除了常規(guī)前端知識(shí)一定要多花時(shí)間看安全和業(yè)務(wù)結(jié)合的部分。我的建議是準(zhǔn)備一個(gè)“安全場(chǎng)景下的前端項(xiàng)目經(jīng)驗(yàn)”故事比如你曾經(jīng)處理過(guò)登錄表單的輸入校驗(yàn)、給文件上傳接口做過(guò)類型和尺寸校驗(yàn)、用CSP增強(qiáng)過(guò)頁(yè)面的安全性或者改過(guò)接口的鑒權(quán)邏輯防止越權(quán)訪問(wèn)。把這些真實(shí)經(jīng)歷整理成一小段有背景、有舉措、有結(jié)果的描述比背十個(gè)面試題模板都管用。8. 那些年我踩過(guò)的筆試坑和回頭看的心得現(xiàn)在回頭看看2019年的前端筆試再對(duì)照這些年帶團(tuán)隊(duì)的面試經(jīng)歷有些心得值得和還在準(zhǔn)備筆試的你說(shuō)一說(shuō)。8.1 筆試掛掉最常見的三個(gè)原因第一基礎(chǔ)題不仔細(xì)讀題。很多題目不是不會(huì)是題眼沒(méi)看到。比如題目要求用ES5實(shí)現(xiàn)防抖你上來(lái)就用let、箭頭函數(shù)直接扣分。第二手寫題只看邏輯不看邊界。代碼在正常輸入下沒(méi)問(wèn)題但一遇到空數(shù)組、空對(duì)象、循環(huán)引用就崩了這種代碼在工程化標(biāo)準(zhǔn)里是過(guò)不了評(píng)審的。第三框架經(jīng)驗(yàn)寫得太滿基礎(chǔ)一塌糊涂。筆試就是一面照妖鏡你平時(shí)靠框架和工具掩蓋的知識(shí)盲區(qū)在基礎(chǔ)題面前全都會(huì)現(xiàn)原形。8.2 從面試官視角看什么樣的候選人能過(guò)我后來(lái)也參與過(guò)校招筆試的出題和閱卷站在面試官的角度我會(huì)優(yōu)先看三樣?xùn)|西一是代碼的完整性邊界條件和異常處理有沒(méi)有考慮二是思路的清晰度即使題目沒(méi)完全做出來(lái)注釋和流程描述也能看出思考過(guò)程三是對(duì)安全相關(guān)的意識(shí)哪怕筆試沒(méi)有專門的安全題候選人在回答性能優(yōu)化、跨域問(wèn)題時(shí)能否主動(dòng)提到安全風(fēng)險(xiǎn)這會(huì)讓我覺(jué)得這個(gè)候選人很契合公司業(yè)務(wù)。8.3 給正在準(zhǔn)備前端筆試的你的實(shí)操建議如果你現(xiàn)在是準(zhǔn)備階段我建議你做兩件事。第一件把本文提到的所有手寫題全部寫一遍寫到不需要思考就能默寫的程度。筆試現(xiàn)場(chǎng)是有時(shí)間壓力的如果這些基礎(chǔ)題你在腦子清醒時(shí)都不能秒寫現(xiàn)場(chǎng)環(huán)境會(huì)更糟。第二件找一個(gè)有經(jīng)驗(yàn)的前端幫你做一次模擬面試讓他用這套題的類型抽考你重點(diǎn)看你能不能邊說(shuō)思路邊寫代碼而不是默默寫完。很多筆試掛掉的人不是不會(huì)寫是不敢在時(shí)間壓力下寫這個(gè)問(wèn)題只能靠模擬場(chǎng)景來(lái)克服。這組題放在今天依然不過(guò)時(shí)因?yàn)樗嫉膹膩?lái)不是某個(gè)特定版本的框架API而是你對(duì)前端底層的理解深度。這份理解力一旦建立不管面哪家公司筆試環(huán)節(jié)都不會(huì)對(duì)你構(gòu)成真正的阻礙。