最近一個(gè)月把大廠面了個(gè)遍,還未上岸……
本文由我的讀者小伙伴 sensFeng 投稿,這一個(gè)月他面了很多家大公司,這份面試經(jīng)驗(yàn)對(duì)最近正在準(zhǔn)備找工作的小伙伴可以說(shuō)是非常有參考價(jià)值了,在文章中他也給出了他整理的答案,誠(chéng)意滿滿!
也可以前往掘金查看:https://juejin.cn/post/6995744994166308895
前言
筆者兩年前端經(jīng)驗(yàn),前后大概面了一個(gè)月,期間面了很多公司,比如有贊、涂鴉智能、滴滴、字節(jié)、酷家樂(lè) 大搜車(chē)、??低?/code>、稅友等等,梳理一下基于我個(gè)人面試過(guò)程中被問(wèn)的到的一些問(wèn)題(包括但不限于)
在開(kāi)始面試之前,一份優(yōu)秀的簡(jiǎn)歷也是十分重要,推薦兩篇文章:
如何寫(xiě)「前端簡(jiǎn)歷」,能敲開(kāi)字節(jié)跳動(dòng)的大門(mén)? 一份優(yōu)秀的前端開(kāi)發(fā)工程師簡(jiǎn)歷是怎么樣的[1]
為了讓自己拿到比較滿意的 offer,前一周選了一些中小公司來(lái)練手,后面發(fā)現(xiàn),太小的公司沒(méi)有鍛煉效果,所以一周后,就開(kāi)始給規(guī)模比較大的廠投簡(jiǎn)歷了。
印象比較深刻的是酷家樂(lè),一面是遠(yuǎn)程,然后二面約你現(xiàn)場(chǎng),現(xiàn)場(chǎng)是三輪技術(shù) + hr,差不多三個(gè)小時(shí),效率很高,但是第一次面試這么久(當(dāng)時(shí)也不知道要面這么久),導(dǎo)致后面表現(xiàn)的不是很好,遂,卒。涂鴉智能的效率也很高,跟酷家樂(lè)的面試流程是一樣的,都是遠(yuǎn)程一面,現(xiàn)場(chǎng)三輪技術(shù) + hr。其他的公司基本上是一輪一輪的來(lái),每一輪的結(jié)果一般第二天會(huì)告知你,當(dāng)然,如果后面幾天等不到,也是一種告知。
悄悄加一句,歡迎聯(lián)系 ssh 內(nèi)推或者交個(gè)朋友 ??,微信 sshsunlight
HTTP
http 的問(wèn)題算是面試熱點(diǎn)問(wèn)題,在一些交叉面或者一面里面很喜歡問(wèn),是比較考驗(yàn)?zāi)銓?duì)基礎(chǔ)的掌握。
get 和 post 有什么區(qū)別?
請(qǐng)求參數(shù):get 請(qǐng)求參數(shù)是通過(guò) url 傳遞的,多個(gè)參數(shù)以&連接;POST 請(qǐng)求放在 request body 中。
請(qǐng)求緩存:get 請(qǐng)求會(huì)被緩存,而 post 請(qǐng)求不會(huì),除非手動(dòng)設(shè)置。
相對(duì)的安全性:get 是將參數(shù)通過(guò) url 傳遞的,會(huì)被瀏覽器緩存,容易被他人獲取,post 相對(duì)來(lái)說(shuō),比較安全。
請(qǐng)求參數(shù)長(zhǎng)度限制:get 通過(guò) url 傳參,瀏覽器會(huì)限制 url 的長(zhǎng)度(http不會(huì))。
編碼方式:GET 請(qǐng)求只能進(jìn)行 url 編碼,而 POST 支持多種編碼方式。
http1.1 和 http2.0 有什么區(qū)別?
(包括但不限于 1.1 和 2.0 的版本對(duì)比)
http1.1
引入了持久鏈接,即 TCP 默認(rèn)不關(guān)閉,可以被多個(gè)請(qǐng)求復(fù)用 引入管道機(jī)制,一個(gè) TCP 連接,可以同時(shí)發(fā)送多個(gè)請(qǐng)求 新增了一些緩存的字段 新增了一些方法,PUT、DELETE、OPTIONS、PATCH 支持?jǐn)帱c(diǎn)續(xù)傳,通過(guò)請(qǐng)求頭字段 Rang 來(lái)實(shí)現(xiàn)
http2.0
頭部壓縮 多路復(fù)用 二進(jìn)制傳輸,頭信息和數(shù)據(jù)體都是二進(jìn)制 請(qǐng)求優(yōu)先級(jí), 設(shè)置數(shù)據(jù)幀的優(yōu)先級(jí),讓服務(wù)器優(yōu)先處理 服務(wù)器主動(dòng)推送消息
被問(wèn)到的一些問(wèn)題:
管道機(jī)制會(huì)造成什么樣的問(wèn)題,http2.0 是怎么解決的 頭部壓縮的原理是什么 options 方法的作用 http2.0 允許服務(wù)器主動(dòng)推送消息,那跟 WebSocket 有什么區(qū)別嗎?
推薦一篇神三元大佬的文章:HTTP 靈魂之問(wèn),鞏固你的 HTTP 知識(shí)體系[2]
說(shuō)一下 http 緩存
等你說(shuō)完強(qiáng)緩存和協(xié)商緩存的大致流程,面試官可能基于你的答案,來(lái)深入考察你對(duì) http 緩存的理解,比如(包括但不限于):
200 狀態(tài)碼一定是服務(wù)器返回的嗎?
不是,命中強(qiáng)緩存的話,會(huì)直接從內(nèi)存或者磁盤(pán)中讀取資源,并返回一個(gè) 200 狀態(tài)碼,具體操作可以試試瀏覽器的前進(jìn)后退鍵。
Expires 和 Cache-Control 的 max-age 指令分別是如何確定過(guò)期時(shí)間的??jī)?yōu)劣勢(shì)是什么?
為什么有了 Last-Modified 還要有 ETag?解決了什么問(wèn)題?
no-store 和 no-cache 的意思分別是什么?
推薦一篇文章,解答了這些問(wèn)題:一張圖理解 http 緩存[3]
https 為什么比 http 安全
面試官會(huì)讓你說(shuō)下 https 做了什么,然后不排除會(huì)問(wèn)你加密原理。(ps: 字節(jié)面試官問(wèn)了)
主要用到了對(duì)稱加密和非對(duì)稱加密,推薦閱讀:徹底搞懂 HTTPS 的加密原理[4]
說(shuō)一下三次握手四次揮手
這個(gè)問(wèn)題問(wèn)的比較少,只遇到過(guò)一次。推薦一篇筆者之前寫(xiě)的文章: TCP 三次握手、四次揮手[5]
JS
0.1 + 0.2 !== 0.3?為什么?
二進(jìn)制轉(zhuǎn)換:js 在做數(shù)字計(jì)算的時(shí)候,底層都是轉(zhuǎn)二進(jìn)制來(lái)計(jì)算的,0.1 轉(zhuǎn)二進(jìn)制是無(wú)限循環(huán)小數(shù),0.2 也是,但是 js 采用的IEEE754 二進(jìn)制浮點(diǎn)運(yùn)算[6],小數(shù)后面只會(huì)保留 53 位有效數(shù)字,導(dǎo)致精度丟失。對(duì)階運(yùn)算:階小的尾數(shù)要根據(jù)階差來(lái)右移,尾數(shù)位移時(shí)可能會(huì)發(fā)生數(shù)丟失的情況,影響精度。
如何解決上面說(shuō)的精確度丟失問(wèn)題?
剛開(kāi)始第一反應(yīng)就是,先將小數(shù)點(diǎn)擴(kuò)大變成整數(shù),做完加法之后,再除回去。(不妥當(dāng),因?yàn)檫€是可能會(huì)超過(guò) js 最大數(shù)) 利用第三方庫(kù),比如 Math.js、big.js等都轉(zhuǎn)成字符串,然后對(duì)兩個(gè)字符串做加法運(yùn)算(手寫(xiě)題有實(shí)現(xiàn))
閉包
說(shuō)一下閉包的本質(zhì)是什么?會(huì)造成哪些問(wèn)題 除了函數(shù)的內(nèi)部返回一個(gè)函數(shù),還有其他的方法產(chǎn)生閉包嗎?(有)
// 函數(shù)內(nèi)部延遲調(diào)用,產(chǎn)生了閉包
function test() {
let name = "tom";
setTimeout(() => {
console.log(name);
}, 2000);
}
test();
推薦文章:JavaScript 的靜態(tài)作用域鏈與“動(dòng)態(tài)”閉包鏈[7]
什么是作用域?什么是作用域鏈?函數(shù)執(zhí)行上下文包含了哪些內(nèi)容?
this 指向
面試官問(wèn):JS 的 this 指向[8]
es6 的問(wèn)題
箭頭函數(shù)和普通函數(shù)的區(qū)別 什么是暫時(shí)性死區(qū),什么是變量提升 for of 和 for in 的區(qū)別,怎么讓 for of 可以遍歷一個(gè)對(duì)象? es6 的 Map 和 WeakMap 的區(qū)別,WeakMap 解決了什么問(wèn)題? promise 哪些方法是原型上的,哪些方法是實(shí)例上的
推薦阮一峰老師的教程:ES6 入門(mén)教程[9]
原型 + 原型鏈 (這個(gè)屬于必問(wèn)的題)
這個(gè)問(wèn)題只要能描述的清楚對(duì)象如何查找它的值,基本上就理解了一大半。
解釋一下上面的圖:
prototype是函數(shù)特有的屬性,__proto__是每個(gè)對(duì)象都有的屬性,而prototype本身也是一個(gè)對(duì)象當(dāng)我們?nèi)カ@取 a.name的時(shí)候,會(huì)先從對(duì)象的自身屬性開(kāi)始查找,如果沒(méi)有的話,就會(huì)從a.__proto__上找對(duì)象 a.__proto__又指向構(gòu)造器函數(shù)test的prototype(原型),所以從a.__proto上找屬性其實(shí)就是在test.prototype找屬性,但是prototype(原型)本身又是一個(gè)對(duì)象,這樣的話,會(huì)重復(fù)上面兩個(gè)步驟,最終都是找到了Object這個(gè)構(gòu)造器函數(shù),而Object.__proto是一個(gè) null 值,如果中間有值就返回,沒(méi)有就賦值undefined。這樣的鏈?zhǔn)浇Y(jié)構(gòu)就是原型鏈
因?yàn)闃?gòu)造器函數(shù)原型上的constructor是指向構(gòu)造器函數(shù)自身的,所以
a.constructor === test; // true
a.__proto__.constructor === test; // true
a.__proto__.constructor === test.prototype.constructor; // true
有一道面試題可以測(cè)試一下,說(shuō)出打印內(nèi)容,并且說(shuō)明原因。
function test() {}
test.prototype.then = function () {
console.log("test => then");
};
Function.prototype.mythen = function () {
console.log("Function => mythen");
};
test.mythen();
test.then();
eventLoop
面試官會(huì)基于你的回答來(lái)提問(wèn),比如:
你剛剛說(shuō)到 js 是單線程,那線程跟進(jìn)程有什么區(qū)別? 瀏覽器新開(kāi)了一個(gè)頁(yè)面,有幾個(gè)線程? 為什么要設(shè)計(jì)成微任務(wù)先執(zhí)行,宏任務(wù)后執(zhí)行。
推薦一篇酷家樂(lè)大佬的文章:這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制[10]
垃圾回收機(jī)制
你剛剛提到的標(biāo)記清除法有什么缺點(diǎn)?怎么解決? 你剛剛提到的引用計(jì)數(shù)法有什么缺點(diǎn)嗎? v8 里面的垃圾回收機(jī)制是什么? v8 是怎么解決循環(huán)引用的?
推薦文章:你真的了解垃圾回收機(jī)制嗎[11]
說(shuō)一下數(shù)據(jù)類(lèi)型,如何判斷一個(gè)數(shù)組
判斷數(shù)組的方法:
Array.isArray(arr); // true arr instanceof Array; // true arr.constructor === Array; // true Object.prototype.toString.call(arr); // "[object Array]"
ps:通過(guò) instanceof 和 constructor 來(lái)判斷不一定準(zhǔn)確,因?yàn)榭赡軙?huì)被重寫(xiě)。
常用的設(shè)計(jì)模式?
什么是抽象工廠模式 發(fā)布訂閱模式和觀察者模式有什么區(qū)別 你項(xiàng)目里面都用了哪些設(shè)計(jì)模式
推薦文章:前端需要掌握的設(shè)計(jì)模式[12]
瀏覽器渲染過(guò)程
一般我都是根據(jù)這張圖,把流程說(shuō)一遍。
被面試官問(wèn)到的一些問(wèn)題:
link 標(biāo)簽會(huì)不會(huì)阻塞頁(yè)面的渲染?說(shuō)一下原因? 為什么 css 推薦放上面,js 推薦放下面? js 會(huì)阻塞頁(yè)面的渲染嗎?說(shuō)一下原因? css 會(huì)阻塞 html 的解析嗎?為什么? css 會(huì)阻塞 html 的渲染嗎?為什么?
js 執(zhí)行是單獨(dú)的線程,瀏覽器渲染是GUI渲染線程負(fù)責(zé)的,兩個(gè)線程是互斥關(guān)系,所以很好理解,js 會(huì)阻塞頁(yè)面的渲染。
css 不會(huì)阻塞 html 的解析,解析 html 和解析 css 是并行的,但是 css 會(huì)阻塞 html 的渲染,因?yàn)轫?yè)面渲染的時(shí)候,需要style Rules + DOM Tree一起合成Render Tree。
正常來(lái)說(shuō),解析頁(yè)面過(guò)程中如果遇到一個(gè) script 標(biāo)簽,會(huì)停止 html 解析(如下圖),去下載 script 腳本,下載完畢之后立即執(zhí)行腳本,然后接著解析 html,所以如果 script 下載速度很慢,會(huì)造成頁(yè)面白屏。
defer:html 解析和腳本下載并行(異步),等 html 解析之后,DOMContentLoaded觸發(fā)之前執(zhí)行腳本。async:html 解析和腳本下載并行(異步),下載后立即執(zhí)行腳本,且中斷 html 解析。
推薦閱讀:
瀏覽器的工作原理:新式網(wǎng)絡(luò)瀏覽器幕后揭秘[13]、
從 8 道面試題看瀏覽器渲染過(guò)程與性能優(yōu)化[14]
性能優(yōu)化
我一般從 http 請(qǐng)求 + 代碼層面 + 構(gòu)建工具來(lái)回答。
setTimeout 和 requestAnimationFrame 做動(dòng)畫(huà)有區(qū)別嗎?哪一個(gè)更好?為什么?
有區(qū)別:
準(zhǔn)確性
setTimeout做動(dòng)畫(huà)不準(zhǔn)確,因?yàn)槭呛耆蝿?wù),設(shè)置的延遲時(shí)間并不等于在延遲時(shí)間之后立即執(zhí)行,因?yàn)樾枰却饺蝿?wù)和微任務(wù)執(zhí)行完,才執(zhí)行宏任務(wù),容易丟幀;requestAnimationFrame則是在每一幀繪制元素之前執(zhí)行,更精確。
更好的性能
在隱藏元素或者元素不可見(jiàn)時(shí),
setTimeout仍然在后臺(tái)執(zhí)行動(dòng)畫(huà)任務(wù);而requestAnimationFrame會(huì)停止動(dòng)畫(huà),這意味著更少的 CPU 和更少的內(nèi)存消耗。
介紹一下同源策略?你知道那些跨域方法?cors 跨域的原理是什么有了解過(guò)嗎?
CSS
介紹一下盒模型?
flex: 1 代表什么意思
用過(guò) flex 布局嗎?都有哪些屬性?
說(shuō)說(shuō)什么是 BFC,一般你都用來(lái)干什么,解決了什么問(wèn)題?
實(shí)現(xiàn)元素水平垂直居中?盡可能說(shuō)多一些方法?
左側(cè)固定 + 右側(cè)自適應(yīng)布局?說(shuō)說(shuō)幾種方案?
重繪和重排?
推薦文章:重排(reflow)和重繪(repaint)[15]
React
面了一圈下來(lái),發(fā)現(xiàn) react 問(wèn)的都差不多。
都用過(guò)那些版本的 react,分別介紹一下區(qū)別?
說(shuō)一下前端路由,他們的實(shí)現(xiàn)原理分別是什么?
hash 路由:通過(guò)監(jiān)聽(tīng)hashchange事件來(lái)捕捉 url 的變化,來(lái)決定是否更新頁(yè)面。
history 路由:主要監(jiān)聽(tīng)popState、pushState、replaceState來(lái)決定是否更新頁(yè)面。但是要注意,僅僅調(diào)用pushState方法或replaceState方法 ,并不會(huì)觸發(fā)popState事件,只有用戶點(diǎn)擊瀏覽器倒退按鈕和前進(jìn)按鈕,或者使用 JavaScript 調(diào)用back、forward、go方法時(shí)才會(huì)觸發(fā),想要pushState、replaceState的時(shí)候觸發(fā)popState事件,需要自定義事件。
你能手寫(xiě)個(gè)簡(jiǎn)單的 redux 嗎?
面試官說(shuō)完之后,給我遞過(guò)來(lái)筆和紙......(內(nèi)心崩潰) 寫(xiě)完了之后,面試官讓我給他講一遍。
function createStore(reducer) {
let listeners = [];
let currentState;
function getState() {
return currentState;
}
function dispatch(action) {
currentState = reducer(currentState, action);
listeners.forEach((l) => l());
}
function subscribe(fn) {
listeners.push(fn);
return function unsubscribe() {
listeners = listeners.filter((l) => l !== fn);
};
}
return {
getState,
dispatch,
subscribe,
};
}
redux 里面 dispatch 是如何正確找到 reducer 的?
combineReducers 源碼[16]
是的,redux 它不找,一把梭,每次 dispatch 都要遍歷一遍所有的 reducer...
redux 怎么掛載中間件的?它的執(zhí)行順序是什么樣的?
核心函數(shù)compose
function compose(middlewears) {
return middlewears.reduce(
(a, b) =>
(...argu) =>
a(b(...argu))
);
}
執(zhí)行順序:從后往前執(zhí)行 redux 中間件。
除了 redux,還用過(guò)其他的狀態(tài)管理庫(kù)嗎?
沒(méi)有。
redux 的缺點(diǎn)?
react 生命周期?
setState 什么情況下同步,什么情況下異步?
講一下 react 的事件機(jī)制,為什么這么設(shè)計(jì)?react17 里面有什么變化嗎?
推薦文章:一文吃透 react 事件系統(tǒng)原理[17]
設(shè)計(jì)的好處:
抹平瀏覽器差異,實(shí)現(xiàn)更好的跨平臺(tái) 避免垃圾回收,React 引入事件池,在事件池中獲取或釋放事件對(duì)象,避免頻繁地去創(chuàng)建和銷(xiāo)毀 方便事件統(tǒng)一管理和事務(wù)機(jī)制
class 組件跟函數(shù)組件有什么區(qū)別?
能在 if 判斷里面寫(xiě) hooks 嗎?為什么不能?
Hooks 是用鏈表保存狀態(tài)的,每次渲染的時(shí)候,必須要保證 hooks 的長(zhǎng)度和順序是一樣的,如果不一致,react 無(wú)法獲取正確狀態(tài),會(huì)報(bào)錯(cuò)。
HOC 和 hooks 的區(qū)別?
hooks 實(shí)現(xiàn)原理?不用鏈表可以用其他方法實(shí)現(xiàn)嗎?
基于鏈表來(lái)實(shí)現(xiàn)的,也可以用數(shù)組來(lái)模擬。
useEffect 依賴傳空數(shù)組和 componentDidMount 有什么區(qū)別嗎?
useeffect 和 useLayouteffect 區(qū)別
useEffect不會(huì)阻塞瀏覽器渲染,而useLayoutEffect會(huì)阻塞瀏覽器渲染。useEffect會(huì)在瀏覽器渲染結(jié)束后執(zhí)行,useLayoutEffect則是在DOM更新完成后,瀏覽器繪制之前執(zhí)行。
介紹一下 react dom diff
介紹一下 Vdom?
在 React 中,有做過(guò)什么性能優(yōu)化嗎?
按照自己腦袋瓜里面的想法,說(shuō)了幾種。
推薦文章:React 性能優(yōu)化的 8 種方式了解一下[18]
React.memo()和 React.useMemo()有什么區(qū)別嗎?
接上題,然后面試官這兩個(gè)有什么區(qū)別嗎?
直接上官網(wǎng): React.memo[19]、 React.useMemo[20]
useCallback 和 useMemo 的區(qū)別?
同接上題 官網(wǎng)地址 useCallback[21]
React.fiber 了解嗎?造成卡頓的原因是什么?react.fiber 里面是怎么解決的?中斷更新之后,下次是如何找到要更新的位置的?
推薦荒山大佬的文章:這可能是最通俗的 React Fiber(時(shí)間分片) 打開(kāi)方式[22]
函數(shù)式編程
講 hooks 很大概率問(wèn)到函數(shù)式編程?(被問(wèn)到過(guò)好幾次)
說(shuō)一下你理解的函數(shù)式編程? 有哪些庫(kù)是利用了函數(shù)式編程的思想? lodash 是真正的函數(shù)式編程庫(kù)嗎?
簡(jiǎn)明 JavaScript 函數(shù)式編程——入門(mén)篇[23]
高階組件里面如何防止 ref 丟失?
React.forwardRef,上官網(wǎng)地址:React.forwardRef[24]
webpack && node
都用過(guò) node 干了什么?用過(guò) node 框架嗎? node 一些基本 api,如何刪除文件,創(chuàng)建文件,寫(xiě)入文件。 用過(guò) node 的哪些模塊?
進(jìn)程和線程的區(qū)別
介紹一下模塊發(fā)展史
node_modules 問(wèn)題
假如 npm 安裝了一個(gè)模塊 A、依賴 c 的 0.0.1 版本,又安裝了一個(gè)模塊 B,依賴 c 的 0.0.2 版本。請(qǐng)問(wèn) node_module 是怎么保證 A、B 正確的找到對(duì)應(yīng)的 c 版本的包的?
webpack 打包原理
初始化參數(shù):從配置文件和 Shell 語(yǔ)句中讀取與合并參數(shù),得出最終的參數(shù); 開(kāi)始編譯:用上一步得到的參數(shù)初始化 Compiler 對(duì)象,加載所有配置的插件,執(zhí)行對(duì)象的 run 方法開(kāi)始執(zhí)行編譯;確定入口:根據(jù)配置中的 entry 找出所有的入口文件 編譯模塊:從入口文件出發(fā),調(diào)用所有配置的 Loader 對(duì)模塊進(jìn)行編譯,再找出該模塊依賴的模塊,再遞歸本步驟直到所有入口依賴的文件都經(jīng)過(guò)了本步驟的處理; 完成模塊編譯:在經(jīng)過(guò)第 4 步使用 Loader 翻譯完所有模塊后,得到了每個(gè)模塊被翻譯后的最終內(nèi)容以及它們之間的依賴關(guān)系 輸出資源:根據(jù)入口和模塊之間的依賴關(guān)系,組裝成一個(gè)個(gè)包含多個(gè)模塊的 Chunk,再把每個(gè) Chunk 轉(zhuǎn)換成一個(gè)單獨(dú)的文件加入到輸出列表,這步是可以修改輸出內(nèi)容的最后機(jī)會(huì) 輸出完成:在確定好輸出內(nèi)容后,根據(jù)配置確定輸出的路徑和文件名,把文件內(nèi)容寫(xiě)入到文件系統(tǒng)。

webpack 性能優(yōu)化你是怎么做的?
推薦文章:帶你深度解鎖 Webpack 系列(優(yōu)化篇)[25]
loader 和 plugin 的區(qū)別?
loader(轉(zhuǎn)換):主要是做轉(zhuǎn)換功能,比如將 css、less 文件轉(zhuǎn)成 js,識(shí)別不同的文件后綴名,可以拓展一下自己比較熟悉的 loader。 plugin(插件):原理是監(jiān)聽(tīng) webpack 構(gòu)建過(guò)程中的一些鉤子,然后做一些自己的操作,更多的是豐富 webpack 的功能。
loader 的執(zhí)行順序是什么?如何寫(xiě)一個(gè) loader?如何寫(xiě)一個(gè) plugin?loader 有異步的嗎?
執(zhí)行順序從右往左、從下到上。
loader 本質(zhì)是一個(gè)函數(shù),plugin 本質(zhì)是一個(gè)類(lèi),具體如何編寫(xiě),推薦文章:手把手教你寫(xiě)一個(gè) loader / plugin[26]
loader 有同步的也有異步。
file-loader 返回的是什么?
返回的是一個(gè)字符串,詳情見(jiàn):file-loader 源碼地址[27]
webpack 有幾種 hash,它們有什么區(qū)別?一般你在項(xiàng)目里面是用哪種 hash?
hash:是整個(gè)項(xiàng)目的 hash 值,其根據(jù)每次編譯內(nèi)容計(jì)算得到,每次編譯之后都會(huì)生成新的 hash,即修改任何文件都會(huì)導(dǎo)致所有文件的 hash 發(fā)生改變。chunkHash:根據(jù)不同的入口文件(Entry)進(jìn)行依賴文件解析、構(gòu)建對(duì)應(yīng)的 chunk,生成對(duì)應(yīng)的哈希值(來(lái)源于同一個(gè) chunk,則 hash 值就一樣)。contentHash:根據(jù)文件內(nèi)容生成 hash 值,文件內(nèi)容相同 hash 值就相同
webpack5 有哪些新特性?
推薦文章:飛書(shū)團(tuán)隊(duì) Webpack5 上手測(cè)評(píng)[28]
webpack 熱更新原理?
推薦文章:輕松理解 webpack 熱更新原理[29]
tree-shaking 原理?
利用ES Module做靜態(tài)分析,通過(guò)分析 ast 語(yǔ)法樹(shù),對(duì)每個(gè)模塊維護(hù)了一個(gè)作用域,收集模塊內(nèi)部使用的變量,然后分析作用域,將import進(jìn)來(lái)未被使用的模塊刪除,最后遞歸處理文件。
babel 轉(zhuǎn)換代碼的過(guò)程,箭頭函數(shù)轉(zhuǎn)普通函數(shù)的時(shí)候,是如何處理 this 的?
過(guò)程:parser => transfrom => generator,可以根據(jù)自己的理解,展開(kāi)說(shuō)說(shuō)。
箭頭函數(shù)轉(zhuǎn)普通函數(shù)如何處理 this:就近找上一層作用域里面的 this,用一個(gè)唯一變量名 that 緩存一下 this,然后將之前箭頭函數(shù)里面的 this 替換成 that 即可。
手寫(xiě)題
節(jié)流和防抖
(某獨(dú)角獸公司)面試官:你能手寫(xiě)個(gè)節(jié)流函數(shù)嗎?然后遞過(guò)來(lái)了筆跟紙......
函數(shù)防抖
定義:在 n 秒時(shí)間內(nèi),函數(shù)只會(huì)觸發(fā)一次,如果期間被觸發(fā),則重新計(jì)時(shí)。
場(chǎng)景:input框?qū)崟r(shí)搜索、瀏覽器的resize、和scroll
function debounce(fn, delay) {
let timer;
return function (...argu) {
let that = this;
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(that, argu);
}, delay);
};
}
函數(shù)節(jié)流
定義:在 n 秒內(nèi),事件只執(zhí)行一次,如果期間被觸發(fā),也不會(huì)響應(yīng)事件。
場(chǎng)景:表單重復(fù)提交、滾動(dòng)加載
// 利用時(shí)間戳實(shí)現(xiàn)
function throttle(fn, delay) {
let previous = new Date();
return function (...argu) {
let nowDate = new Date();
if (nowDate - previous > delay) {
fn.apply(this, argu);
previous = nowDate;
}
};
}
// 利用定時(shí)器實(shí)現(xiàn)
function throttle(fn, delay) {
let timer;
return function (...argu) {
let that = this;
if (timer) return false;
timer = setTimeout(() => {
fn.apply(that, argu);
timer = null; // 釋放timer變量,讓下一次的函數(shù)接著執(zhí)行
}, delay);
};
}
寫(xiě)一種你熟悉的排序?
沒(méi)錯(cuò),也是用筆寫(xiě),寫(xiě)完了然后給他講思路。我選了一個(gè)快速排序
// 先選一個(gè)數(shù)當(dāng)作基點(diǎn),一般選擇最后一個(gè)數(shù)
// 然后遍歷arr, 找出這個(gè)基點(diǎn)數(shù)的比它大的數(shù)組集合和比它小的數(shù)組集合
// 遞歸此步驟
function quickSort(arr) {
if (arr.length < 2) {
return arr;
}
const cur = arr[arr.length - 1];
let left = [];
let right = [];
for (let i = 0; i < arr.length - 1; i++) {
if (arr[i] >= cur) {
right.push(arr[i]);
} else {
left.push(arr[i]);
}
}
return [...quickSort(left), cur, ...quickSort(right)];
}
console.log(quickSort([1, 3, 3, 6, 2, 4, 1]));
(字節(jié))說(shuō)出打印順序
默認(rèn)非嚴(yán)格模式
function Foo() {
getName = function () {
alert(1);
};
return this;
}
Foo.getName = function () {
alert(2);
};
Foo.prototype.getName = function () {
alert(3);
};
var getName = function () {
alert(4);
};
function getName() {
alert(5);
}
// 請(qǐng)寫(xiě)出以下輸出結(jié)果:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
正確輸出順序:2 4 1 1 2 3
Foo.getName();這個(gè)沒(méi)什么好說(shuō)的,輸出 2getName();考察 var 和函數(shù)提升,函數(shù)優(yōu)先級(jí)大于 var,所以輸出 4Foo().getName();Foo()返回 this,此時(shí) this 指向 window,F(xiàn)oo().getName 相當(dāng)于 window.getName。但是 Foo()內(nèi)部又對(duì) window 上的 getName 重新賦值了,所以輸出 1getName();同上,輸出 1new Foo.getName();考察運(yùn)算符優(yōu)先級(jí)[30],new 無(wú)參數(shù)列表,對(duì)應(yīng)的優(yōu)先級(jí)是 18;成員訪問(wèn)操作符., 對(duì)應(yīng)的優(yōu)先級(jí)是 19。因此相當(dāng)于是new (Foo.getName)();new 操作符會(huì)執(zhí)行構(gòu)造函數(shù)中的方法,因此此處輸出為 2.new Foo().getName();new 帶參數(shù)列表,對(duì)應(yīng)的優(yōu)先級(jí)是 19,和成員訪問(wèn)操作符.優(yōu)先級(jí)相同。同級(jí)運(yùn)算符,按照從左到右的順序依次計(jì)算。new Foo()先初始化 Foo 的實(shí)例化對(duì)象,實(shí)例上沒(méi)有 getName 方法,因此需要原型上去找,即找到了Foo.prototype.getName,輸出 3
instanceof 原理,能?chē)L試手寫(xiě)一個(gè) instanceof 函數(shù)嗎
function myInstanceOf(left, right) {
if (left === null || typeof left !== "object") return false;
if (typeof right !== "function") throw new Error("right must be function");
let L = left.__proto__;
let R = right.prototype;
while (L !== null) {
if (L === R) return true;
L = L.__proto__;
}
return false;
}
實(shí)現(xiàn) new 關(guān)鍵字
創(chuàng)建一個(gè)空的簡(jiǎn)單 javaScript 對(duì)象,即{} 將創(chuàng)建對(duì)象的 __proto__指向構(gòu)造函數(shù)的prototype修改 this指向如果構(gòu)造函數(shù)沒(méi)有返回值,就返回創(chuàng)建的對(duì)象。
function myNew(context, ...argu) {
let obj = Object.create(null);
obj.__proto = context.prototype;
let res = context.apply(obj, argu);
return typeof res === "object" ? res : obj;
}
大數(shù)相加
let a = "123456789012345678";
let b = "1";
function add(a, b) {
//...
}
add(a, b); // '123456789012345679'
思路:模擬加法運(yùn)算,但是需要用'0'補(bǔ)齊長(zhǎng)度,對(duì)于整數(shù),向前補(bǔ) 0。
let a = "123456789012345678";
let b = "1";
// 1. 先找出最大的長(zhǎng)度的數(shù)
// 2. 給較小的數(shù)填充向前填充0
function add(a, b) {
let maxLength = Math.max(a.length, b.length);
a = a.padStart(maxLength, "0");
b = b.padStart(maxLength, "0");
// 123456789012345678
// 000000000000000001
let res = ""; // 返回的值
let sum = 0; // 同位相加的和
let t = 0; // 同位相加和的十位數(shù)
let r = 0; // 同位相加和的個(gè)位數(shù)
for (let i = maxLength - 1; i >= 0; i--) {
sum = parseInt(a[i]) + parseInt(b[i]) + t;
t = Math.floor(sum / 10); // 拿到十位數(shù)的值
r = sum % 10; // 拿到個(gè)位數(shù)的值
res = r + res;
}
return res;
}
console.log(add(a, b)); // 123456789012345679
(滴滴)扁平化數(shù)組
實(shí)現(xiàn)一個(gè) flat 函數(shù),接收 arr 和 depth 參數(shù)
function flat(arr, depth = 1) {
return depth > 0
? arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flat(cur, depth - 1) : cur);
}, [])
: arr.slice();
}
實(shí)現(xiàn)一個(gè) event 類(lèi)
class EventEmitter {
constructor() {}
// 監(jiān)聽(tīng)事件
on() {}
// 觸發(fā)事件
emit() {}
// 只監(jiān)聽(tīng)一次,下次emit不會(huì)觸發(fā)
once() {}
// 移除事件
off() {}
}
const events = new EventEmitter();
events.on("hobby", (...argu) => {
console.log("打游戲", ...argu);
});
let eat = () => {
console.log("吃");
};
events.once("hobby", eat);
events.on("hobby", (...argu) => {
console.log("逛街", ...argu);
});
events.off("hobby", eat);
events.emit("hobby", 1, 2, 3);
events.emit("hello", "susan");
//打游戲 1 2 3
// 逛街 1 2 3
答案:
class EventEmitter {
constructor() {
this.events = {}; // 存放著所有的事件{eventName: [callback, ...]}
}
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [callback];
} else {
this.events[eventName].push(callback);
}
}
emit(eventName, ...argu) {
if (this.events[eventName]) {
this.events[eventName].forEach((fn) => fn(...argu));
}
}
off(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(
(fn) => callback !== fn && fn.l !== callback
);
}
}
once(eventName, callback) {
const _once = () => {
callback();
this.off(eventName, _once);
};
_once.l = callback;
this.on(eventName, _once);
}
}
實(shí)現(xiàn)千分位 format 函數(shù)
// 接收一個(gè)number,返回一個(gè)string
function format(number) {}
console.log(format(12345.789)); // 12,345.789,0
console.log(format(0.12345678)); // 0.123,456,78
console.log(format(123456)); // 123,456
思路
基于小數(shù)點(diǎn)切分,對(duì)于整數(shù)部分,從后往前遍歷,隔 3 加 ,小數(shù)點(diǎn)部分,從前往后便利,隔 3 加 ,
function format(number) {
let str = number.toString();
let [int, dec = ""] = str.split(".");
let intStr = "";
for (let i = int.length - 1; i >= 0; i--) {
if ((int.length - i) % 3 === 0 && i !== 0) {
intStr = "," + int[i] + intStr;
} else {
intStr = int[i] + intStr;
}
}
let decStr = "";
if (dec.length > 0) {
for (let i = 0; i < dec.length; i++) {
let sum = decStr + dec[i];
if ((i + 1) % 3 === 0) {
decStr = sum + ",";
} else {
decStr = sum;
}
}
}
return decStr.length > 0 ? `${intStr}.${decStr}` : `${intStr}`;
}
(字節(jié)、滴滴)根據(jù)傳入的姓名權(quán)重信息,返回隨機(jī)的姓名(隨機(jī)概率依據(jù)權(quán)重)
第一次沒(méi)看懂題目,面試官解釋了一下。
/**
* @description: 根據(jù)傳入的姓名權(quán)重信息,返回隨機(jī)的姓名(隨機(jī)概率依據(jù)權(quán)重)
* @param {Array} personValue
* @returns {String} personName 姓名
*/
var getPersonName = function (personValue) {};
const person = [
{
name: "張三",
weight: 1,
},
{
name: "李四",
weight: 10,
},
{
name: "王五",
weight: 100,
},
];
function getResult(count) {
const res = {};
for (let i = 0; i < count; i++) {
const name = getPersonName(person);
res[name] = res[name] ? res[name] + 1 : 1;
}
console.log(res);
}
getResult(10000);
答案:
var getPersonName = function (personValue) {
// 標(biāo)記區(qū)間,并且獲得weight的總數(shù)
let sum = personValue.reduce((pre, cur) => {
cur.startW = pre;
return (cur.endW = cur.weight + pre);
}, 0);
let s = Math.random() * sum; // 獲得一個(gè) 0 - 111 的隨機(jī)數(shù)
// 判斷隨機(jī)數(shù)的所屬區(qū)間
let person = personValue.find((item) => item.startW < s && s <= item.endW);
return person.name;
};
(字節(jié))實(shí)現(xiàn)一個(gè) promise.all
Promise.all = function (promises) {
let result = [];
let count = 0;
return new Promise((resolve, reject) => {
promises.forEach((p, index) => {
// 兼容不是promise的情況
Promise.resolve(p)
.then((res) => {
result[index] = res;
count++;
if (count === promises.length) {
resolve(result);
}
})
.catch((err) => {
reject(err);
});
});
});
};
(滴滴)兩數(shù)之和
力扣原題:兩數(shù)之和[31]
剛開(kāi)始用雙重循環(huán)寫(xiě)了一個(gè),時(shí)間復(fù)雜度是 O(n^2),面試官問(wèn)你能優(yōu)化到 O(n)嗎?然后有了下面這個(gè)。
var twoSum = function (nums, target) {
let len = nums.length;
let map = new Map();
for (let i = 0; i < len; i++) {
if (map.has(target - nums[i])) {
return [map.get(target - nums[i]), i];
} else {
map.set(nums[i], i);
}
}
};
console.log(twoSum([2, 7, 11, 15], 9));
(字節(jié))無(wú)重復(fù)最長(zhǎng)子串
力扣原題:無(wú)重復(fù)最長(zhǎng)子串[32]
var lengthOfLongestSubstring = function (s) {
let arr = [];
let max = 0;
for (let i = 0; i < s.length; i++) {
let index = arr.indexOf(s[i]);
if (index !== -1) {
arr.splice(0, index + 1);
}
arr.push(s.charAt(i));
max = Math.max(arr.length, max);
}
return max;
};
實(shí)現(xiàn) new Queue 類(lèi)
new Queue()
.task(1000,()=>console.log(1))
.task(2000,()=>console.log(2))
.task(3000,()=>console.log(3)).start();
實(shí)現(xiàn)一個(gè)Queue函數(shù),調(diào)用start之后,1s后打印1,接著2s后打印2,然后3s后打印3
答案:
function sleep(delay, callback) {
return new Promise((resolve, reject) => {
setTimeout(() => {
callback();
resolve();
}, delay);
});
}
class Queue {
constructor() {
this.listenser = [];
}
task(delay, callback) {
// 收集函數(shù)
this.listenser.push(() => sleep(delay, callback));
return this;
}
async start() {
// 遍歷函數(shù)
for (let l of this.listenser) {
await l();
}
}
}
new Queue()
.task(1000, () => console.log(1))
.task(2000, () => console.log(2))
.task(3000, () => console.log(3))
.start();
總結(jié)
還出了很多場(chǎng)景題,讓你給出解決方案,中間經(jīng)歷的太久了,面試記錄做的不夠好,很多問(wèn)題都忘了。一面基本上都是問(wèn)基礎(chǔ),后面幾輪面試會(huì)深入項(xiàng)目和要你給出解決方案。這次面試也是發(fā)現(xiàn)了自己的不足,平時(shí)寫(xiě)代碼的過(guò)程中,思考的不夠多,希望今后能多一些思考。
參考資料
一份優(yōu)秀的前端開(kāi)發(fā)工程師簡(jiǎn)歷是怎么樣的: https://www.zhihu.com/question/23150301/answer/1229870117
[2]HTTP 靈魂之問(wèn),鞏固你的 HTTP 知識(shí)體系: https://juejin.cn/post/6844904100035821575#heading-100
[3]一張圖理解 http 緩存: https://segmentfault.com/a/1190000015816331
[4]徹底搞懂 HTTPS 的加密原理: https://zhuanlan.zhihu.com/p/43789231
[5]TCP 三次握手、四次揮手: https://juejin.cn/post/6844904194764177416
[6]IEEE754 二進(jìn)制浮點(diǎn)運(yùn)算: https://www.h-schmidt.net/FloatConverter/IEEE754.html
[7]JavaScript 的靜態(tài)作用域鏈與“動(dòng)態(tài)”閉包鏈: https://juejin.cn/post/6957913856488243237
[8]面試官問(wèn):JS 的 this 指向: https://juejin.cn/post/6844903746984476686
[9]ES6 入門(mén)教程: https://es6.ruanyifeng.com/
[10]這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制: https://juejin.cn/post/6844903512845860872
[11]你真的了解垃圾回收機(jī)制嗎: https://juejin.cn/post/6981588276356317214
[12]前端需要掌握的設(shè)計(jì)模式: https://juejin.cn/post/6874906145463468046
[13]瀏覽器的工作原理:新式網(wǎng)絡(luò)瀏覽器幕后揭秘: https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/
[14]從 8 道面試題看瀏覽器渲染過(guò)程與性能優(yōu)化: https://juejin.cn/post/6844904040346681358
[15]重排(reflow)和重繪(repaint): https://juejin.cn/post/6844904083212468238
[16]combineReducers 源碼: https://github.com/reduxjs/redux/blob/master/src/combineReducers.ts
[17]一文吃透 react 事件系統(tǒng)原理: https://juejin.cn/post/6955636911214067720
[18]React 性能優(yōu)化的 8 種方式了解一下: https://juejin.cn/post/6844903924302888973
[19]React.memo: https://zh-hans.reactjs.org/docs/react-api.html#reactmemo
[20]React.useMemo: https://zh-hans.reactjs.org/docs/hooks-reference.html#usememo
[21]官網(wǎng)地址 useCallback: https://zh-hans.reactjs.org/docs/hooks-reference.html#usecallback
[22]這可能是最通俗的 React Fiber(時(shí)間分片) 打開(kāi)方式: https://juejin.cn/post/6844903975112671239
[23]簡(jiǎn)明 JavaScript 函數(shù)式編程——入門(mén)篇: https://juejin.cn/post/6844903936378273799
[24]React.forwardRef: https://zh-hans.reactjs.org/docs/react-api.html#reactforwardref
[25]帶你深度解鎖 Webpack 系列(優(yōu)化篇): https://juejin.cn/post/6844904093463347208
[26]手把手教你寫(xiě)一個(gè) loader / plugin: https://juejin.cn/post/6976052326947618853
[27]file-loader 源碼地址: https://github.com/webpack-contrib/file-loader/blob/master/src/index.js
[28]飛書(shū)團(tuán)隊(duì) Webpack5 上手測(cè)評(píng): https://juejin.cn/post/6844904169405415432
[29]輕松理解 webpack 熱更新原理: https://juejin.cn/post/6844904008432222215
[30]運(yùn)算符優(yōu)先級(jí): https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FJavaScript%2FReference%2FOperators%2FOperator_Precedence
[31]兩數(shù)之和: https://leetcode-cn.com/problems/two-sum/
[32]無(wú)重復(fù)最長(zhǎng)子串: https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/
