如何一周時間準(zhǔn)備前端面試

簡歷在找工作過程中是非常非常重要的,無論你是什么途徑去面試的,面試你的人一定會看你的簡歷。 1、重點(diǎn)
簡歷就像高考作文——閱卷時間非常短。
內(nèi)容要簡潔。
直擊重點(diǎn),表現(xiàn)出自己的優(yōu)勢(只要是符合招人單位要求的都是優(yōu)勢,不是別人不會的你會才叫優(yōu)勢)。
2、簡歷包含的內(nèi)容
個人信息。
專業(yè)技能。
工作經(jīng)歷。
項(xiàng)目經(jīng)歷。
社區(qū)貢獻(xiàn)。
2.1 基本信息
必備:姓名 電話 郵箱。
年齡(最好寫上,在這個行業(yè)年齡還是比較重要的),學(xué)歷(寫好是哪一屆)。
頭像無所謂(好看就放上唄)。
可以放 github 鏈接,前提是有內(nèi)容。
2.2 專業(yè)技能
表現(xiàn)出自己的核心競爭力(只要是符合招人單位要求的都是優(yōu)勢)。
內(nèi)容不要太多,3、5 條即可。
太基礎(chǔ)的不要寫,例如會用 vscode、。
2.3 工作經(jīng)歷
如實(shí)寫。
寫明公司,職位,入職離職時間即可,多寫無益。
如果有空窗期,如實(shí)寫明即可。
2.4 項(xiàng)目經(jīng)歷
寫 2-4 個具有說服力的項(xiàng)目(不要什么項(xiàng)目都寫,沒用)。
項(xiàng)目名稱,項(xiàng)目描述,技術(shù)棧,個人角色。
2.5 社區(qū)貢獻(xiàn)
有博客或者開源作品,會讓你更有競爭力。
切記:需要真的有內(nèi)容,不可臨時抱佛腳。
3、注意事項(xiàng)
界面不能太花哨,簡潔明了即可。
注意用詞,“精通”“熟練”等慎用,可用“熟悉”。
不可造假,會被拉入黑名單。
4、面試前準(zhǔn)備
看 JD,是否需要臨時準(zhǔn)備一下。
打印紙質(zhì)簡歷,帶著紙和筆(增加好印象)。
最好帶著自己電腦,現(xiàn)場可能手寫代碼(帶一個帆布包最適合,又優(yōu)雅又方便)。
要有時間觀念,如果遲到或者推遲,要提前說。
衣著適當(dāng),不用正裝,也不要太隨意。
為何離職?—— 不要吐槽前東家,說自己的原因(想找一個更好的發(fā)展平臺等)。
能加班嗎?—— 能!除非你特別自信,能找到其他機(jī)會。
不要挑戰(zhàn)面試官,即便他錯了(面試一定要保證愉快)。
HTML 和 CSS 面試題答不出來基本可以回去了。 1、HTML 面試題 以下是針對 HTML 相關(guān)的面試題,一般來說這地方不會出太多題,面試官也不愿意花太多時間在這上面。 1.1 如何理解 HTML 語義化?
讓人更容易讀懂(增加代碼可讀性)。
讓搜索引擎更容易讀懂,有助于爬蟲抓取更多的有效信息,爬蟲依賴于標(biāo)簽來確定上下文和各個關(guān)鍵字的權(quán)重(SEO)。
在沒有 CSS 樣式下,頁面也能呈現(xiàn)出很好地內(nèi)容結(jié)構(gòu)、代碼結(jié)構(gòu)。
1.2 script 標(biāo)簽中 defer 和 async 的區(qū)別?
script
:會阻礙 HTML 解析,只有下載好并執(zhí)行完腳本才會繼續(xù)解析 HTML。async script
:解析 HTML 過程中進(jìn)行腳本的異步下載,下載成功立馬執(zhí)行,有可能會阻斷 HTML 的解析。defer script
script

1.3 從瀏覽器地址欄輸入 url 到請求返回發(fā)生了什么
先閱讀這篇科普性質(zhì)的:[從 URL 輸入到頁面展現(xiàn)到底發(fā)生什么?先閱讀篇文章:。
輸入 URL 后解析出協(xié)議、主機(jī)、端口、路徑等信息,并構(gòu)造一個 HTTP 請求。
強(qiáng)緩存。
協(xié)商緩存。
DNS 域名解析。()
TCP 連接。 總是要問:為什么需要三次握手,兩次不行嗎?其實(shí)這是由 TCP 的自身特點(diǎn)可靠傳輸決定的??蛻舳撕头?wù)端要進(jìn)行可靠傳輸,那么就需要確認(rèn)雙方的接收和發(fā)送能力。第一次握手可以確認(rèn)客服端的發(fā)送能力,第二次握手,確認(rèn)了服務(wù)端的發(fā)送能力和接收能力,所以第三次握手才可以確認(rèn)客戶端的接收能力。不然容易出現(xiàn)丟包的現(xiàn)象。
http 請求。
服務(wù)器處理請求并返回 HTTP 報(bào)文。

2、CSS 面試題 ?以下是針對 CSS 相關(guān)的面試題,這些題答不出來會給人非常不好的技術(shù)印象。 ?2.1 盒模型介紹 ?CSS3 中的盒模型有以下兩種:標(biāo)準(zhǔn)盒模型、IE(替代)盒模型。 ?兩種盒子模型都是由 content + padding + border + margin
構(gòu)成,其大小都是由 content + padding + border
決定的,但是盒子內(nèi)容寬/高度(即 width/height
)的計(jì)算范圍根據(jù)盒模型的不同會有所不同:
標(biāo)準(zhǔn)盒模型:只包含
content
。IE(替代)盒模型:
content + padding + border
。
可以通過 box-sizing
來改變元素的盒模型:
box-sizing: content-box
:標(biāo)準(zhǔn)盒模型(默認(rèn)值)。box-sizing: border-box
:IE(替代)盒模型。
2.2 css 選擇器和優(yōu)先級 ?首先我們要知道有哪些選擇器:!important > style > id > class
,但是涉及多類選擇器作用于同一個元素時候怎么判斷優(yōu)先級呢?相信我,你在改一些第三方庫(比如 ?)樣式時,理解這個會幫助很大! ?這篇文章寫的非常清晰易懂,強(qiáng)烈推薦,看完之后就沒啥問題了: 。 ??上述文章中核心內(nèi)容: 優(yōu)先級是由 A 、B、C、D 的值來決定的,其中它們的值計(jì)算規(guī)則如下:
如果存在內(nèi)聯(lián)樣式,那么
A = 1
,否則A = 0
;B 的值等于
ID選擇器(#id)
出現(xiàn)的次數(shù);C 的值等于
類選擇器(.class)
和屬性選擇器(a[href="https://example.org"])
和偽類(:first-child)
出現(xiàn)的總次數(shù);D 的值等于
標(biāo)簽選擇器(h1,a,div)
和偽元素(::before,::after)
出現(xiàn)的總次數(shù)。 ?從左至右比較,如果是樣式優(yōu)先級相等,取后面出現(xiàn)的樣式。2.3 重排(reflow)和重繪(repaint)的理解 簡單地總結(jié)下兩者的概念:
重排:無論通過什么方式影響了元素的幾何信息(元素在視口內(nèi)的位置和尺寸大小),瀏覽器需要重新計(jì)算元素在視口內(nèi)的幾何屬性,這個過程叫做重排。
重繪:通過構(gòu)造渲染樹和重排(回流)階段,我們知道了哪些節(jié)點(diǎn)是可見的,以及可見節(jié)點(diǎn)的樣式和具體的幾何信息(元素在視口內(nèi)的位置和尺寸大小),接下來就可以將渲染樹的每個節(jié)點(diǎn)都轉(zhuǎn)換為屏幕上的實(shí)際像素,這個階段就叫做重繪。
如何減少重排和重繪?
最小化重繪和重排,比如樣式集中改變,使用添加新樣式類名
.class
或cssText
。批量操作 DOM,比如讀取某元素
offsetWidth
屬性存到一個臨時變量,再去使用,而不是頻繁使用這個計(jì)算屬性;又比如利用document.createDocumentFragment()
來添加要被添加的節(jié)點(diǎn),處理完之后再插入到實(shí)際 DOM 中。使用
\**absolute\**
或\**fixed\**
使元素脫離文檔流,這在制作復(fù)雜的動畫時對性能的影響比較明顯。開啟 GPU 加速,利用 css 屬性
transform
、will-change
等,比如改變元素位置,我們使用translate
會比使用絕對定位改變其left
、top
等來的高效,因?yàn)樗粫|發(fā)重排或重繪,transform
使瀏覽器為元素創(chuàng)建?個 GPU 圖層,這使得動畫元素在一個獨(dú)立的層中進(jìn)行渲染。當(dāng)元素的內(nèi)容沒有發(fā)生改變,就沒有必要進(jìn)行重繪。
這里推薦騰訊 IVWEB 團(tuán)隊(duì)的這篇文章: ,好好認(rèn)真看完,面試應(yīng)該沒問題的。
2.4 對 BFC 的理解 ?BFC 即塊級格式上下文,根據(jù)盒模型可知,每個元素都被定義為一個矩形盒子,然而盒子的布局會受到尺寸,定位,盒子的子元素或兄弟元素,視口的尺寸等因素決定,所以這里有一個瀏覽器計(jì)算的過程,計(jì)算的規(guī)則就是由一個叫做視覺格式化模型的東西所定義的,BFC 就是來自這個概念,它是 CSS 視覺渲染的一部分,用于決定塊級盒的布局及浮動相互影響范圍的一個區(qū)域。 ?BFC 具有一些特性:
塊級元素會在垂直方向一個接一個的排列,和文檔流的排列方式一致。
在 BFC 中上下相鄰的兩個容器的
margin
?會重疊,創(chuàng)建新的 BFC 可以避免外邊距重疊。計(jì)算 BFC 的高度時,需要計(jì)算浮動元素的高度。
BFC 區(qū)域不會與浮動的容器發(fā)生重疊。
BFC 是獨(dú)立的容器,容器內(nèi)部元素不會影響外部元素。
每個元素的左
margin
?值和容器的左border
?相接觸。
利用這些特性,我們可以解決以下問題:
利用
4
?和6
,我們可以實(shí)現(xiàn)三欄(或兩欄)自適應(yīng)布局。利用
2
,我們可以避免margin
?重疊問題。利用
3
,我們可以避免高度塌陷。
創(chuàng)建 BFC 的方式:
絕對定位元素(
position
為absolute
或fixed
)。行內(nèi)塊元素,即
display
為inline-block
。overflow
的值不為visible
。
推薦文章:
2.5 實(shí)現(xiàn)兩欄布局(左側(cè)固定 + 右側(cè)自適應(yīng)布局) ?現(xiàn)在有以下 DOM 結(jié)構(gòu):
<div> <div>左側(cè)</div> <div>右側(cè)</div> </div> 復(fù)制代碼
利用浮動,左邊元素寬度固定 ,設(shè)置向左浮動。將右邊元素的
margin-left
設(shè)為固定寬度 。注意,因?yàn)橛疫呍氐?width
默認(rèn)為auto
,所以會自動撐滿父元素。
.outer { ? height: 100px; } .left { ? float: left; ? width: 200px; ? height: 100%; ? background: lightcoral; } .right { ? margin-left: 200px; ? height: 100%; ? background: lightseagreen; } 復(fù)制代碼
同樣利用浮動,左邊元素寬度固定 ,設(shè)置向左浮動。右側(cè)元素設(shè)置
overflow: hidden;
這樣右邊就觸發(fā)了BFC
,BFC
的區(qū)域不會與浮動元素發(fā)生重疊,所以兩側(cè)就不會發(fā)生重疊。
.outer { ? height: 100px; } .left { ? float: left; ? width: 200px; ? height: 100%; ? background: lightcoral; } .right { ? overflow: auto; ? height: 100%; ? background: lightseagreen; } 復(fù)制代碼
利用
flex
布局,左邊元素固定寬度,右邊的元素設(shè)置flex: 1
。
.outer { ? display: flex; ? height: 100px; } .left { ? width: 200px; ? height: 100%; ? background: lightcoral; } .right { ? flex: 1; ? height: 100%; ? background: lightseagreen; } 復(fù)制代碼
利用絕對定位,父級元素設(shè)為相對定位。左邊元素
absolute
?定位,寬度固定。右邊元素的margin-left
?的值設(shè)為左邊元素的寬度值。
.outer { ? position: relative; ? height: 100px; } .left { ? position: absolute; ? width: 200px; ? height: 100%; ? background: lightcoral; } .right { ? margin-left: 200px; ? height: 100%; ? background: lightseagreen; } 復(fù)制代碼
利用絕對定位,父級元素設(shè)為相對定位。左邊元素寬度固定,右邊元素
absolute
?定位,left
?為寬度大小,其余方向定位為0
。
.outer { ? position: relative; ? height: 100px; } .left { ? width: 200px; ? height: 100%; ? background: lightcoral; } .right { ? position: absolute; ? left: 200px; ? top: 0; ? right: 0; ? bottom: 0; ? height: 100%; ? background: lightseagreen; } 復(fù)制代碼
2.6 實(shí)現(xiàn)圣杯布局和雙飛翼布局(經(jīng)典三分欄布局) ?圣杯布局和雙飛翼布局的目的:
三欄布局,中間一欄最先加載和渲染(內(nèi)容最重要,這就是為什么還需要了解這種布局的原因)。
兩側(cè)內(nèi)容固定,中間內(nèi)容隨著寬度自適應(yīng)。
一般用于 PC 網(wǎng)頁。
圣杯布局和雙飛翼布局的技術(shù)總結(jié):
使用
float
?布局。兩側(cè)使用
margin
負(fù)值,以便和中間內(nèi)容橫向重疊。防止中間內(nèi)容被兩側(cè)覆蓋,圣杯布局用
padding
,雙飛翼布局用margin
。
圣杯布局: HTML 結(jié)構(gòu):
<div id="container"> <p>我是中間</p> <p>我是左邊</p> <p>我是右邊</p> </div> 復(fù)制代碼
CSS 樣式:
#container { ? padding-left: 200px; ? padding-right: 150px; ? overflow: auto; } #container p { ? float: left; } .center { ? width: 100%; ? background-color: lightcoral; } .left { ? width: 200px; ? position: relative; ? left: -200px; ? margin-left: -100%; ? background-color: lightcyan; } .right { ? width: 150px; ? margin-right: -150px; ? background-color: lightgreen; } .clearfix:after { ? content: ""; ? display: table; ? clear: both; } 復(fù)制代碼
雙飛翼布局: HTML 結(jié)構(gòu):
<div id="main"> <div id="main-wrap">main</div> </div> <div id="left">left</div> <div id="right">right</div> 復(fù)制代碼
CSS 樣式:
.float { ? float: left; } #main { ? width: 100%; ? height: 200px; ? background-color: lightpink; } #main-wrap { ? margin: 0 190px 0 190px; } #left { ? width: 190px; ? height: 200px; ? background-color: lightsalmon; ? margin-left: -100%; } #right { ? width: 190px; ? height: 200px; ? background-color: lightskyblue; ? margin-left: -190px; } 復(fù)制代碼
tips:上述代碼中 margin-left: -100%
?相對的是父元素的 content
?寬度,即不包含 paddig
、 border
?的寬度。
其實(shí)以上問題需要掌握 margin 負(fù)值問題 即可很好理解。 ?2.7 水平垂直居中多種實(shí)現(xiàn)方式
利用絕對定位,設(shè)置
left: 50%
?和top: 50%
?現(xiàn)將子元素左上角移到父元素中心位置,然后再通過translate
?來調(diào)整子元素的中心點(diǎn)到父元素的中心。該方法可以不定寬高。
.father { ? position: relative; } .son { ? position: absolute; ? left: 50%; ? top: 50%; ? transform: translate(-50%, -50%); } 復(fù)制代碼
利用絕對定位,子元素所有方向都為
0
,將margin
?設(shè)置為auto
,由于寬高固定,對應(yīng)方向?qū)崿F(xiàn)平分,該方法必須盒子有寬高。
.father { ? position: relative; } .son { ? position: absolute; ? top: 0; ? left: 0; ? right: 0; ? bottom: 0px; ? margin: auto; ? height: 100px; ? width: 100px; } 復(fù)制代碼
利用絕對定位,設(shè)置
left: 50%
和top: 50%
現(xiàn)將子元素左上角移到父元素中心位置,然后再通過margin-left
?和margin-top
?以子元素自己的一半寬高進(jìn)行負(fù)值賦值。該方法必須定寬高。
.father { ? position: relative; } .son { ? position: absolute; ? left: 50%; ? top: 50%; ? width: 200px; ? height: 200px; ? margin-left: -100px; ? margin-top: -100px; } 復(fù)制代碼
利用
flex
,最經(jīng)典最方便的一種了,不用解釋,定不定寬高無所謂的。
.father { ? display: flex; ? justify-content: center; ? align-items: center; } 復(fù)制代碼 ?其實(shí)還有很多方法,比如 display: grid
?或 display: table-cell
?來做,有興趣點(diǎn)擊下面這篇文章可以了解下:
。
2.8 flex 布局
這一塊內(nèi)容看 flex: 1
,它具體包含了以下的意思:
flex-grow: 1
:該屬性默認(rèn)為0
,如果存在剩余空間,元素也不放大。設(shè)置為1
?代表會放大。flex-shrink: 1
:該屬性默認(rèn)為1
,如果空間不足,元素縮小。flex-basis: 0%
:該屬性定義在分配多余空間之前,元素占據(jù)的主軸空間。瀏覽器就是根據(jù)這個屬性來計(jì)算是否有多余空間的。默認(rèn)值為auto
,即項(xiàng)目本身大小。設(shè)置為0%
?之后,因?yàn)橛?flex-grow
?和flex-shrink
?的設(shè)置會自動放大或縮小。在做兩欄布局時,如果右邊的自適應(yīng)元素flex-basis
?設(shè)為auto
?的話,其本身大小將會是0
。
2.9 line-height 如何繼承?
父元素的
line-height
寫了具體數(shù)值,比如30px
,則子元素line-height
繼承該值。父元素的
line-height
寫了比例,比如1.5 或 2
,則子元素line-height
也是繼承該比例。父元素的
line-height
寫了百分比,比如200%
,則子元素line-height
繼承的是父元素font-size * 200%
計(jì)算出來的值。
js 基礎(chǔ)
js 的考察其實(shí)來回就那些東西,不過就我自己而已學(xué)習(xí)的時候理解是真的理解了,但是忘也確實(shí)會忘(大家都說理解了一定不會忘,但是要答全的話還是需要理解+背)。 ?1、數(shù)據(jù)類型 ?以下是比較重要的幾個 js 變量要掌握的點(diǎn)。 ?1.1 基本的數(shù)據(jù)類型介紹,及值類型和引用類型的理解 ?在 JS 中共有 8
?種基礎(chǔ)的數(shù)據(jù)類型,分別為: Undefined
、 Null
、 Boolean
、 Number
、 String
、 Object
、 Symbol
、 BigInt
。 ?其中 Symbol
?和 BigInt
?是 ES6 新增的數(shù)據(jù)類型,可能會被單獨(dú)問:
Symbol 代表獨(dú)一無二的值,最大的用法是用來定義對象的唯一屬性名。
BigInt 可以表示任意大小的整數(shù)。
值類型的賦值變動過程如下:

棧(stack)中的簡單數(shù)據(jù)段,占據(jù)空間小、大小固定,屬于被頻繁使用數(shù)據(jù),所以放入棧中存儲; 引用類型的賦值變動過程如下:

堆(heap)中的對象,占據(jù)空間大、大小不固定。如果存儲在棧中,將會影響程序運(yùn)行的性能; ?1.2 數(shù)據(jù)類型的判斷
typeof:能判斷所有值類型,函數(shù)。不可對 null、對象、數(shù)組進(jìn)行精確判斷,因?yàn)槎挤祷?
object
。
console.log(typeof undefined); // undefined console.log(typeof 2); // number console.log(typeof true); // boolean console.log(typeof "str"); // string console.log(typeof Symbol("foo")); // symbol console.log(typeof 2172141653n); // bigint console.log(typeof function () {}); // function // 不能判別 console.log(typeof []); // object console.log(typeof {}); // object console.log(typeof null); // object 復(fù)制代碼
instanceof:能判斷對象類型,不能判斷基本數(shù)據(jù)類型,其內(nèi)部運(yùn)行機(jī)制是判斷在其原型鏈中能否找到該類型的原型。比如考慮以下代碼:
class People {} class Student extends People {} ?const vortesnail = new Student(); ?console.log(vortesnail instanceof People); // true console.log(vortesnail instanceof Student); // true 復(fù)制代碼 ?其實(shí)現(xiàn)就是順著原型鏈去找,如果能找到對應(yīng)的 Xxxxx.prototype
?即為 true
。比如這里的 vortesnail
?作為實(shí)例,順著原型鏈能找到 Student.prototype
?及 People.prototype
,所以都為 true
。
Object.prototype.toString.call():所有原始數(shù)據(jù)類型都是能判斷的,還有 Error 對象,Date 對象等。
Object.prototype.toString.call(2); // "[object Number]" Object.prototype.toString.call(""); // "[object String]" Object.prototype.toString.call(true); // "[object Boolean]" Object.prototype.toString.call(undefined); // "[object Undefined]" Object.prototype.toString.call(null); // "[object Null]" Object.prototype.toString.call(Math); // "[object Math]" Object.prototype.toString.call({}); // "[object Object]" Object.prototype.toString.call([]); // "[object Array]" Object.prototype.toString.call(function () {}); // "[object Function]" 復(fù)制代碼
在面試中有一個經(jīng)常被問的問題就是:如何判斷變量是否為數(shù)組?
Array.isArray(arr); // true arr.__proto__ === Array.prototype; // true arr instanceof Array; // true Object.prototype.toString.call(arr); // "[object Array]" 復(fù)制代碼
1.3 手寫深拷貝
這個題一定要會啊!筆者面試過程中瘋狂被問到!
文章推薦:
/** ?* 深拷貝 ?* @param {Object} obj 要拷貝的對象 ?* @param {Map} map 用于存儲循環(huán)引用對象的地址 ?*/ function deepClone(obj = {}, map = new Map()) { ? if (typeof obj !== "object") { ? ? return obj; ? } ? if (map.get(obj)) { ? ? return map.get(obj); ? } ? ?let result = {}; ? // 初始化返回結(jié)果 if ( ? ? obj instanceof Array || ? ? // 加 || 的原因是為了防止 Array 的 prototype 被重寫,Array.isArray 也是如此 Object.prototype.toString(obj) === "[object Array]" ? ) { ? ? result = []; ? } ? // 防止循環(huán)引用 ? map.set(obj, result); ? for (const key in obj) { ? ? // 保證 key 不是原型屬性 if (obj.hasOwnProperty(key)) { ? ? ? // 遞歸調(diào)用 ? ? ? result[key] = deepClone(obj[key], map); ? ? } ? } ? ?// 返回結(jié)果 return result; } 復(fù)制代碼
1.4 根據(jù) 0.1+0.2 ! == 0.3,講講 IEEE 754 ,如何讓其相等?
建議先閱讀這篇文章了解 IEEE 754 :
。 再閱讀這篇文章了解如何運(yùn)算: 。 ? 原因總結(jié):進(jìn)制轉(zhuǎn)換
:js 在做數(shù)字計(jì)算的時候,0.1 和 0.2 都會被轉(zhuǎn)成二進(jìn)制后無限循環(huán) ,但是 js 采用的 IEEE 754 二進(jìn)制浮點(diǎn)運(yùn)算,最大可以存儲 53 位有效數(shù)字,于是大于 53 位后面的會全部截掉,將導(dǎo)致精度丟失。對階運(yùn)算
:由于指數(shù)位數(shù)不相同,運(yùn)算時需要對階運(yùn)算,階小的尾數(shù)要根據(jù)階差來右移(0舍1入
),尾數(shù)位移時可能會發(fā)生數(shù)丟失的情況,影響精度。
解決辦法:
轉(zhuǎn)為整數(shù)(大數(shù))運(yùn)算。
function add(a, b) { ? const maxLen = Math.max( ? ? a.toString().split(".")[1].length, ? ? b.toString().split(".")[1].length ? ); ? const base = 10 ** maxLen; ? const bigA = BigInt(base * a); ? const bigB = BigInt(base * b); ? const bigRes = (bigA + bigB) / BigInt(base); // 如果是 (1n + 2n) / 10n 是等于 0n的。。。 return Number(bigRes); } 復(fù)制代碼 ?這里代碼是有問題的,因?yàn)樽詈笥?jì)算 bigRes
的大數(shù)相除(即 /
)是會把小數(shù)部分截掉的,所以我很疑惑為什么網(wǎng)絡(luò)上很多文章都說可以通過先轉(zhuǎn)為整數(shù)運(yùn)算再除回去,為了防止轉(zhuǎn)為的整數(shù)超出 js 表示范圍,還可以運(yùn)用到 ES6 新增的大數(shù)類型,我真的很疑惑,希望有好心人能解答下。
使用
Number.EPSILON
誤差范圍。
function isEqual(a, b) { ? return Math.abs(a - b) < Number.EPSILON; } ?console.log(isEqual(0.1 + 0.2, 0.3)); // true 復(fù)制代碼 ?Number.EPSILON
的實(shí)質(zhì)是一個可以接受的最小誤差范圍,一般來說為 Math.pow(2, -52)
。
轉(zhuǎn)成字符串,對字符串做加法運(yùn)算。
// 字符串?dāng)?shù)字相加 var addStrings = function (num1, num2) { ? let i = num1.length - 1; ? let j = num2.length - 1; ? const res = []; ? let carry = 0; ? while (i >= 0 || j >= 0) { ? ? const n1 = i >= 0 ? Number(num1[i]) : 0; ? ? const n2 = j >= 0 ? Number(num2[j]) : 0; ? ? const sum = n1 + n2 + carry; ? ? res.unshift(sum % 10); ? ? carry = Math.floor(sum / 10); ? ? i--; ? ? j--; ? } ? if (carry) { ? ? res.unshift(carry); ? } ? return res.join(""); }; ?function isEqual(a, b, sum) { ? const [intStr1, deciStr1] = a.toString().split("."); ? const [intStr2, deciStr2] = b.toString().split("."); ? const inteSum = addStrings(intStr1, intStr2); // 獲取整數(shù)相加部分 const deciSum = addStrings(deciStr1, deciStr2); // 獲取小數(shù)相加部分 return inteSum + "." + deciSum === String(sum); } ?console.log(isEqual(0.1, 0.2, 0.3)); // true 復(fù)制代碼 ?這是 leetcode 上一道原題:
。區(qū)別在于原題沒有考慮小數(shù),但是也是很簡單的,我們分為兩個部分計(jì)算就行。 ?2、 原型和原型鏈 ?可以說這部分每家面試官都會問了。。首先理解的話,其實(shí)一張圖即可,一段代碼即可。 ?function Foo() {} ?let f1 = new Foo(); let f2 = new Foo(); 復(fù)制代碼

原型:每一個 JavaScript 對象(null 除外)在創(chuàng)建的時候就會與之關(guān)聯(lián)另一個對象,這個對象就是我們所說的原型,每一個對象都會從原型"繼承"屬性,其實(shí)就是
prototype
對象。原型鏈:由相互關(guān)聯(lián)的原型組成的鏈狀結(jié)構(gòu)就是原型鏈。
先說出總結(jié)的話,再舉例子說明如何順著原型鏈找到某個屬性。 推薦的閱讀: 掌握基本概念,再閱讀這篇文章加深上圖的印象。 3、 作用域與作用域鏈
作用域:規(guī)定了如何查找變量,也就是確定當(dāng)前執(zhí)行代碼對變量的訪問權(quán)限。換句話說,作用域決定了代碼區(qū)塊中變量和其他資源的可見性。(全局作用域、函數(shù)作用域、塊級作用域)
作用域鏈:從當(dāng)前作用域開始一層層往上找某個變量,如果找到全局作用域還沒找到,就放棄尋找 。這種層級關(guān)系就是作用域鏈。(由多個執(zhí)行上下文的變量對象構(gòu)成的鏈表就叫做作用域鏈,學(xué)習(xí)下面的內(nèi)容之后再考慮這句話)
需要注意的是,js 采用的是靜態(tài)作用域,所以函數(shù)的作用域在函數(shù)定義時就確定了。 推薦閱讀:先閱讀,再閱讀。 4、 執(zhí)行上下文 這部分一定要按順序連續(xù)讀這幾篇文章,必須多讀幾遍:
;
;
;
。
總結(jié):當(dāng) JavaScript 代碼執(zhí)行一段可執(zhí)行代碼時,會創(chuàng)建對應(yīng)的執(zhí)行上下文。對于每個執(zhí)行上下文,都有三個重要屬性:
變量對象(Variable object,VO);
作用域鏈(Scope chain);
this。(關(guān)于 this 指向問題,在上面推薦的深入系列也有講從 ES 規(guī)范講的,但是實(shí)在是難懂,對于應(yīng)付面試來說以下這篇阮一峰的文章應(yīng)該就可以了:)
5、 閉包 根據(jù) MDN 中文的定義,閉包的定義如下: ?在 JavaScript 中,每當(dāng)創(chuàng)建一個函數(shù),閉包就會在函數(shù)創(chuàng)建的同時被創(chuàng)建出來??梢栽谝粋€內(nèi)層函數(shù)中訪問到其外層函數(shù)的作用域。
也可以這樣說: ?閉包是指那些能夠訪問自由變量的函數(shù)。 自由變量是指在函數(shù)中使用的,但既不是函數(shù)參數(shù)也不是函數(shù)的局部變量的變量。 閉包 = 函數(shù) + 函數(shù)能夠訪問的自由變量。
在經(jīng)過上一小節(jié)“執(zhí)行上下文”的學(xué)習(xí),再來閱讀這篇文章:,你會對閉包的實(shí)質(zhì)有一定的了解。在回答時,我們這樣答: 在某個內(nèi)部函數(shù)的執(zhí)行上下文創(chuàng)建時,會將父級函數(shù)的活動對象加到內(nèi)部函數(shù)的 [[scope]]
中,形成作用域鏈,所以即使父級函數(shù)的執(zhí)行上下文銷毀(即執(zhí)行上下文棧彈出父級函數(shù)的執(zhí)行上下文),但是因?yàn)槠?/span>活動對象還是實(shí)際存儲在內(nèi)存中可被內(nèi)部函數(shù)訪問到的,從而實(shí)現(xiàn)了閉包。 閉包應(yīng)用: 函數(shù)作為參數(shù)被傳遞: function print(fn) { ? const a = 200; ? fn(); } ?const a = 100; function fn() { ? console.log(a); } ?print(fn); // 100 復(fù)制代碼 函數(shù)作為返回值被返回: function create() { ? const a = 100; ? ?return function () { ? ? console.log(a); ? }; } ?const fn = create(); const a = 200; fn(); // 100 復(fù)制代碼 閉包:自由變量的查找,是在函數(shù)定義的地方,向上級作用域查找。不是在執(zhí)行的地方。 應(yīng)用實(shí)例:比如緩存工具,隱藏?cái)?shù)據(jù),只提供 API 。 function createCache() { ? const data = {}; // 閉包中被隱藏的數(shù)據(jù),不被外界訪問 return { ? ? set: function (key, val) { ? ? ? data[key] = val; ? ? }, ? ? get: function (key) { ? ? ? return data[key]; ? ? }, ? }; } ?const c = createCache(); c.set("a", 100); console.log(c.get("a")); // 100 復(fù)制代碼 6、 call、apply、bind 實(shí)現(xiàn) 這部分實(shí)現(xiàn)還是要知道的,就算工作中不會自己手寫,但是說不準(zhǔn)面試官就是要問,知道點(diǎn)原理也好,可以擴(kuò)寬我們寫代碼的思路。 call ?call() 方法在使用一個指定的 this 值和若干個指定的參數(shù)值的前提下調(diào)用某個函數(shù)或方法。
舉個例子: var obj = { ? value: "vortesnail", }; ?function fn() { ? console.log(this.value); } ?fn.call(obj); // vortesnail 復(fù)制代碼 通過 call
方法我們做到了以下兩點(diǎn):
call
改變了 this 的指向,指向到obj
。fn
函數(shù)執(zhí)行了。
那么如果我們自己寫 call
方法的話,可以怎么做呢?我們先考慮改造 obj
。 var obj = { ? value: "vortesnail", ? fn: function () { ? ? console.log(this.value); ? }, }; ?obj.fn(); // vortesnail 復(fù)制代碼 這時候 this 就指向了 obj
,但是這樣做我們手動給 obj
增加了一個 fn
屬性,這顯然是不行的,不用擔(dān)心,我們執(zhí)行完再使用對象屬性的刪除方法(delete)不就行了? obj.fn = fn; obj.fn(); delete obj.fn; 復(fù)制代碼 根據(jù)這個思路,我們就可以寫出來了: Function.prototype.myCall = function (context) { ? // 判斷調(diào)用對象 if (typeof this !== "function") { ? ? throw new Error("Type error"); ? } ? // 首先獲取參數(shù) let args = [...arguments].slice(1); ? let result = null; ? // 判斷 context 是否傳入,如果沒有傳就設(shè)置為 window ? context = context || window; ? // 將被調(diào)用的方法設(shè)置為 context 的屬性 // this 即為我們要調(diào)用的方法 ? context.fn = this; ? // 執(zhí)行要被調(diào)用的方法 ? result = context.fn(...args); ? // 刪除手動增加的屬性方法 delete context.fn; ? // 將執(zhí)行結(jié)果返回 return result; }; 復(fù)制代碼 apply 我們會了 call
的實(shí)現(xiàn)之后,apply
就變得很簡單了,他們沒有任何區(qū)別,除了傳參方式。 Function.prototype.myApply = function (context) { ? if (typeof this !== "function") { ? ? throw new Error("Type error"); ? } ? let result = null; ? context = context || window; ? // 與上面代碼相比,我們使用 Symbol 來保證屬性唯一 // 也就是保證不會重寫用戶自己原來定義在 context 中的同名屬性 const fnSymbol = Symbol(); ? context[fnSymbol] = this; ? // 執(zhí)行要被調(diào)用的方法 if (arguments[1]) { ? ? result = context; ? } else { ? ? result = context; ? } ? delete context[fnSymbol]; ? return result; }; 復(fù)制代碼 bind bind
返回的是一個函數(shù),這個地方可以詳細(xì)閱讀這篇文章,講的非常清楚:。 Function.prototype.myBind = function (context) { ? // 判斷調(diào)用對象是否為函數(shù) if (typeof this !== "function") { ? ? throw new Error("Type error"); ? } ? // 獲取參數(shù) const args = [...arguments].slice(1), ? const fn = this; ? return function Fn() { ? ? return fn.apply( ? ? ? this instanceof Fn ? this : context, ? ? ? // 當(dāng)前的這個 arguments 是指 Fn 的參數(shù) ? ? ? args.concat(...arguments) ? ? ); ? }; }; 復(fù)制代碼 7、 new 實(shí)現(xiàn)
首先創(chuàng)一個新的空對象。
根據(jù)原型鏈,設(shè)置空對象的
__proto__
為構(gòu)造函數(shù)的prototype
。構(gòu)造函數(shù)的 this 指向這個對象,執(zhí)行構(gòu)造函數(shù)的代碼(為這個新對象添加屬性)。
判斷函數(shù)的返回值類型,如果是引用類型,就返回這個引用類型的對象。
function myNew(context) { ? const obj = new Object(); ? obj.proto = context.prototype; ? const res = context.apply(obj, [...arguments].slice(1)); ? return typeof res === "object" ? res : obj; } 復(fù)制代碼 8、 異步 這部分著重要理解 Promise、async awiat、event loop 等。 8.1 event loop、宏任務(wù)和微任務(wù) 首先推薦一個可以在線看代碼流程的網(wǎng)站:。 然后看下這個視頻學(xué)習(xí)下: 簡單的例子: console.log("Hi"); ?setTimeout(function cb() { ? console.log("cb"); // cb 即 callback }, 5000); ?console.log("Bye"); 復(fù)制代碼

setTimeout
會創(chuàng)建定時器線程,ajax
請求會創(chuàng)建 http 線程。。。這是由 js 的運(yùn)行環(huán)境決定的,比如瀏覽器。 ?看完上面的視頻之后,至少大家畫 Event Loop 的圖講解不是啥問題了,但是涉及到宏任務(wù)和微任務(wù),我們還得拜讀一下這篇文章: 。如果意猶未盡,不如再讀下這篇非常詳細(xì)帶有大量動圖的文章: 。想了解事件循環(huán)和頁面渲染之間關(guān)系的又可以再閱讀這篇文章: 。
注意:1.Call Stack 調(diào)用??臻e -> 2.嘗試 DOM 渲染 -> 觸發(fā) Event loop。
每次 Call Stack 清空(即每次輪詢結(jié)束),即同步任務(wù)執(zhí)行完。
都是 DOM 重新渲染的機(jī)會,DOM 結(jié)構(gòu)有改變則重新渲染。
然后再去觸發(fā)下一次 Event loop。
宏任務(wù):setTimeout,setInterval,Ajax,DOM 事件。 微任務(wù):Promise async/await。 ?兩者區(qū)別:
宏任務(wù):DOM 渲染后觸發(fā),如
setTimeout
、setInterval
、DOM 事件
、script
。微任務(wù):DOM 渲染前觸發(fā),如
Promise.then
、MutationObserver
、Node 環(huán)境下的process.nextTick
。
從 event loop 解釋,為何微任務(wù)執(zhí)行更早?
微任務(wù)是 ES6 語法規(guī)定的(被壓入 micro task queue)。
宏任務(wù)是由瀏覽器規(guī)定的(通過 Web APIs 壓入 Callback queue)。
宏任務(wù)執(zhí)行時間一般比較長。
每一次宏任務(wù)開始之前一定是伴隨著一次 event loop 結(jié)束的,而微任務(wù)是在一次 event loop 結(jié)束前執(zhí)行的。
8.2 Promise
關(guān)于這一塊兒沒什么好說的,最好是實(shí)現(xiàn)一遍 Promise A+ 規(guī)范,多少有點(diǎn)印象,當(dāng)然面試官也不會叫你默寫一個完整的出來,但是你起碼要知道實(shí)現(xiàn)原理。 ??關(guān)于 Promise 的所有使用方式,可參照這篇文章:
。 手寫 Promise 源碼的解析文章,可閱讀此篇文章: 。 關(guān)于 Promise 的面試題,可參考這篇文章: 。實(shí)現(xiàn)一個 Promise.all:
Promise.all = function (promises) { ? return new Promise((resolve, reject) => { ? ? // 參數(shù)可以不是數(shù)組,但必須具有 Iterator 接口 if (typeof promises[Symbol.iterator] !== "function") { ? ? ? reject("Type error"); ? ? } ? ? if (promises.length === 0) { ? ? ? resolve([]); ? ? } else { ? ? ? const res = []; ? ? ? let count = 0; ? ? ? const len = promises.length; ? ? ? for (let i = 0; i < len; i++) { ? ? ? ? //考慮到 promises[i] 可能是 thenable 對象也可能是普通值 Promise.resolve(promises[i]) ? ? ? ? ? .then((data) => { ? ? ? ? ? ? res[i] = data; ? ? ? ? ? ? if (++count === len) { ? ? ? ? ? ? ? resolve(res); ? ? ? ? ? ? } ? ? ? ? ? }) ? ? ? ? ? .catch((err) => { ? ? ? ? ? ? reject(err); ? ? ? ? ? }); ? ? ? } ? ? } ? }); }; 復(fù)制代碼
8.3 async/await 和 Promise 的關(guān)系
async/await 是消滅異步回調(diào)的終極武器。
但和 Promise 并不互斥,反而,兩者相輔相成。
執(zhí)行 async 函數(shù),返回的一定是 Promise 對象。
await 相當(dāng)于 Promise 的 then。
tru...catch 可捕獲異常,代替了 Promise 的 catch。
9、 瀏覽器的垃圾回收機(jī)制 ?這里看這篇文章即可:
。 ?總結(jié)一下: ?有兩種垃圾回收策略:標(biāo)記清除:標(biāo)記階段即為所有活動對象做上標(biāo)記,清除階段則把沒有標(biāo)記(也就是非活動對象)銷毀。
引用計(jì)數(shù):它把對象是否不再需要簡化定義為對象有沒有其他對象引用到它。如果沒有引用指向該對象(引用計(jì)數(shù)為 0),對象將被垃圾回收機(jī)制回收。
標(biāo)記清除的缺點(diǎn):
內(nèi)存碎片化,空閑內(nèi)存塊是不連續(xù)的,容易出現(xiàn)很多空閑內(nèi)存塊,還可能會出現(xiàn)分配所需內(nèi)存過大的對象時找不到合適的塊。
分配速度慢,因?yàn)榧幢闶鞘褂?First-fit 策略,其操作仍是一個 O(n) 的操作,最壞情況是每次都要遍歷到最后,同時因?yàn)樗槠髮ο蟮姆峙湫蕰?/p>
解決以上的缺點(diǎn)可以使用 標(biāo)記整理(Mark-Compact)算法 ,標(biāo)記結(jié)束后,標(biāo)記整理算法會將活著的對象(即不需要清理的對象)向內(nèi)存的一端移動,最后清理掉邊界的內(nèi)存(如下圖)

需要一個計(jì)數(shù)器,所占內(nèi)存空間大,因?yàn)槲覀円膊恢辣灰脭?shù)量的上限。
解決不了循環(huán)引用導(dǎo)致的無法回收問題。
V8 的垃圾回收機(jī)制也是基于標(biāo)記清除算法,不過對其做了一些優(yōu)化。
針對新生區(qū)采用并行回收。
針對老生區(qū)采用增量標(biāo)記與惰性回收。
10、 實(shí)現(xiàn)一個 EventMitter 類 ?EventMitter 就是發(fā)布訂閱模式的典型應(yīng)用:
export class EventEmitter { ? private _events: Record<string, Array<Function>>; ? ?constructor() { ? ? this._events = Object.create(null); ? } ? ?emit(evt: string, ...args: any[]) { ? ? if (!this._events[evt]) return false; ? ? ?const fns = [...this._events[evt]]; ? ? fns.forEach((fn) => { ? ? ? fn.apply(this, args); ? ? }); ? ? ?return true; ? } ? ?on(evt: string, fn: Function) { ? ? if (typeof fn !== "function") { ? ? ? throw new TypeError("The evet-triggered callback must be a function"); ? ? } ? ? if (!this._events[evt]) { ? ? ? this._events[evt] = [fn]; ? ? } else { ? ? ? this._events[evt].push(fn); ? ? } ? } ? ?once(evt: string, fn: Function) { ? ? const execFn = () => { ? ? ? fn.apply(this); ? ? ? this.off(evt, execFn); ? ? }; ? ? this.on(evt, execFn); ? } ? ?off(evt: string, fn?: Function) { ? ? if (!this._events[evt]) return; ? ? if (!fn) { ? ? ? this._events[evt] && (this._events[evt].length = 0); ? ? } ? ? ?let cb; ? ? const cbLen = this._events[evt].length; ? ? for (let i = 0; i < cbLen; i++) { ? ? ? cb = this._events[evt][i]; ? ? ? if (cb === fn) { ? ? ? ? this._events[evt].splice(i, 1); ? ? ? ? break; ? ? ? } ? ? } ? } ? ?removeAllListeners(evt?: string) { ? ? if (evt) { ? ? ? this._events[evt] && (this._events[evt].length = 0); ? ? } else { ? ? ? this._events = Object.create(null); ? ? } ? } } 復(fù)制代碼
1、cookie
本身用于瀏覽器和 server 通訊。
被“借用”到本地存儲來的。
可用 document.cookie = '...' 來修改。
其缺點(diǎn):
存儲大小限制為 4KB。
http 請求時需要發(fā)送到服務(wù)端,增加請求數(shù)量。
只能用 document.cookie = '...' 來修改,太過簡陋。
2、localStorage 和 sessionStorage
HTML5 專門為存儲來設(shè)計(jì)的,最大可存 5M。
API 簡單易用, setItem getItem。
不會隨著 http 請求被發(fā)送到服務(wù)端。
它們的區(qū)別:
localStorage 數(shù)據(jù)會永久存儲,除非代碼刪除或手動刪除。
sessionStorage 數(shù)據(jù)只存在于當(dāng)前會話,瀏覽器關(guān)閉則清空。
一般用 localStorage 會多一些。
Http
前端工程師做出網(wǎng)頁,需要通過網(wǎng)絡(luò)請求向后端獲取數(shù)據(jù),因此 http 協(xié)議是前端面試的必考內(nèi)容。 1、http 狀態(tài)碼 1.1 狀態(tài)碼分類
1xx - 服務(wù)器收到請求。
2xx - 請求成功,如 200。
3xx - 重定向,如 302。
4xx - 客戶端錯誤,如 404。
5xx - 服務(wù)端錯誤,如 500。
1.2 常見狀態(tài)碼
200 - 成功。
301 - 永久重定向(配合 location,瀏覽器自動處理)。
302 - 臨時重定向(配合 location,瀏覽器自動處理)。
304 - 資源未被修改。
403 - 沒權(quán)限。
404 - 資源未找到。
500 - 服務(wù)器錯誤。
504 - 網(wǎng)關(guān)超時。
1.3 關(guān)于協(xié)議和規(guī)范
狀態(tài)碼都是約定出來的。
要求大家都跟著執(zhí)行。
不要違反規(guī)范,例如 IE 瀏覽器。
2、http 緩存
關(guān)于緩存的介紹。
http 緩存策略(強(qiáng)制緩存 + 協(xié)商緩存)。
刷新操作方式,對緩存的影響。
4.1 關(guān)于緩存 什么是緩存? 把一些不需要重新獲取的內(nèi)容再重新獲取一次 為什么需要緩存? 網(wǎng)絡(luò)請求相比于 CPU 的計(jì)算和頁面渲染是非常非常慢的。 哪些資源可以被緩存? 靜態(tài)資源,比如 js css img。

在 Response Headers 中。
控制強(qiáng)制緩存的邏輯。
例如 Cache-Control: max-age=3153600(單位是秒)
Cache-Control 有哪些值:
max-age:緩存最大過期時間。
no-cache:可以在客戶端存儲資源,每次都必須去服務(wù)端做新鮮度校驗(yàn),來決定從服務(wù)端獲取新的資源(200)還是使用客戶端緩存(304)。
no-store:永遠(yuǎn)都不要在客戶端存儲資源,永遠(yuǎn)都去原始服務(wù)器去獲取資源。
4.3 協(xié)商緩存(對比緩存)
服務(wù)端緩存策略。
服務(wù)端判斷客戶端資源,是否和服務(wù)端資源一樣。

在 Response Headers 中,有兩種。
Last-Modified:資源的最后修改時間。
Etag:資源的唯一標(biāo)識(一個字符串,類似于人類的指紋)。
Last-Modified:

Etag:

優(yōu)先使用 Etag。
Last-Modified 只能精確到秒級。

正常操作:地址欄輸入 url,跳轉(zhuǎn)鏈接,前進(jìn)后退等。
手動刷新:f5,點(diǎn)擊刷新按鈕,右鍵菜單刷新。
強(qiáng)制刷新:ctrl + f5,shift+command+r。
正常操作:強(qiáng)制緩存有效,協(xié)商緩存有效。 手動刷新:強(qiáng)制緩存失效,協(xié)商緩存有效。 強(qiáng)制刷新:強(qiáng)制緩存失效,協(xié)商緩存失效。
面試
對于更多面試中可能出現(xiàn)的問題,我還是建議精讀這篇三元的文章:。 比如會被經(jīng)常問到的: GET 和 POST 的區(qū)別。
從緩存的角度,GET 請求會被瀏覽器主動緩存下來,留下歷史記錄,而 POST 默認(rèn)不會。
從編碼的角度,GET 只能進(jìn)行 URL 編碼,只能接收 ASCII 字符,而 POST 沒有限制。
從參數(shù)的角度,GET 一般放在 URL 中,因此不安全,POST 放在請求體中,更適合傳輸敏感信息。
從冪等性的角度,GET 是冪等的,而 POST 不是。(冪等表示執(zhí)行相同的操作,結(jié)果也是相同的)
從 TCP 的角度,GET 請求會把請求報(bào)文一次性發(fā)出去,而 POST 會分為兩個 TCP 數(shù)據(jù)包,首先發(fā) header 部分,如果服務(wù)器響應(yīng) 100(continue), 然后發(fā) body 部分。(火狐瀏覽器除外,它的 POST 請求只發(fā)一個 TCP 包)
HTTP/2 有哪些改進(jìn)?(很大可能問原理)
頭部壓縮。
多路復(fù)用。
服務(wù)器推送。
關(guān)于 HTTPS 的一些原理,可以閱讀這篇文章:。接著你可以觀看這個視頻進(jìn)行更進(jìn)一步的學(xué)習(xí): 關(guān)于跨域問題,大部分文章都是理論性比較強(qiáng),還不如讀這篇文章,,講的非常清晰,我個人覺得對付面試就是先知道使用流程,把這個流程能自己說出來,然后再講下原理即可。
框架
1、 React 事件機(jī)制,React 16 和 React 17 事件機(jī)制的不同 閱讀這篇文章即可:。 為什么要自定義事件機(jī)制?
抹平瀏覽器差異,實(shí)現(xiàn)更好的跨平臺。
避免垃圾回收,React 引入事件池,在事件池中獲取或釋放事件對象,避免頻繁地去創(chuàng)建和銷毀。
方便事件統(tǒng)一管理和事務(wù)機(jī)制。
2、class component 不排除現(xiàn)在還會有面試官問關(guān)于 class component 的問題。 2.1 生命周期
初始化階段。
發(fā)生在 constructor
中的內(nèi)容,在 constructor
中進(jìn)行 state
、props
的初始化,在這個階段修改 state
,不會執(zhí)行更新階段的生命周期,可以直接對 state
賦值。
掛載階段。
componentWillMount ? ?發(fā)生在 render 函數(shù)之前,還沒有掛載 Dom 2. render 3. componentDidMount ? ?發(fā)生在 render 函數(shù)之后,已經(jīng)掛載 Dom 復(fù)制代碼
更新階段。
更新階段分為由 state
更新引起和 props
更新引起。 props 更新時: 1. componentWillReceiveProps(nextProps,nextState) ? ?這個生命周期主要為我們提供對 props 發(fā)生改變的監(jiān)聽,如果你需要在 props 發(fā)生改變后,相應(yīng)改變組件的一些 state。在這個方法中改變 state 不會二次渲染,而是直接合并 state。 2. shouldComponentUpdate(nextProps,nextState) ? ?這個生命周期需要返回一個 Boolean 類型的值,判斷是否需要更新渲染組件,優(yōu)化 react 應(yīng)用的主要手段之一,當(dāng)返回 false 就不會再向下執(zhí)行生命周期了,在這個階段不可以 setState(),會導(dǎo)致循環(huán)調(diào)用。 3. componentWillUpdate(nextProps,nextState) ? ?這個生命周期主要是給我們一個時機(jī)能夠處理一些在 Dom 發(fā)生更新之前的事情,如獲得 Dom 更新前某些元素的坐標(biāo)、大小等,在這個階段不可以 setState(),會導(dǎo)致循環(huán)調(diào)用。 ? ? 一直到這里 this.props 和 this.state 都還未發(fā)生更新 4. render 5. componentDidUpdate(prevProps, prevState) ? ?在此時已經(jīng)完成渲染,Dom 已經(jīng)發(fā)生變化,state 已經(jīng)發(fā)生更新,prevProps、prevState 均為上一個狀態(tài)的值。 ?state 更新時(具體同上) 1. shouldComponentUpdate 2. componentWillUpdate 3. render 4. componentDidUpdate 復(fù)制代碼
卸載階段。
componentWillUnmount ? ?在組件卸載及銷毀之前直接調(diào)用。在此方法中執(zhí)行必要的清理操作,例如,清除 timer,取消網(wǎng)絡(luò)請求或清除在 componentDidMount ?中創(chuàng)建的訂閱等。componentWillUnmount 中不應(yīng)調(diào)用 setState,因?yàn)樵摻M件將永遠(yuǎn)不會重新渲染。組件實(shí)例卸載后,將永遠(yuǎn)不會再掛載它。 ?復(fù)制代碼
在 React 16 中官方已經(jīng)建議刪除以下三個方法,非要使用必須加前綴:UNSAVE_
。 componentWillMount; componentWillReceiveProps; componentWillUpdate; 復(fù)制代碼 取代這兩三個生命周期的以下兩個新的。
static getDerivedStateFromProps(nextProps,nextState) ? ?在組件實(shí)例化、接收到新的 props 、組件狀態(tài)更新時會被調(diào)用 2. getSnapshotBeforeUpdate(prevProps,prevState) ? ?在這個階段我們可以拿到上一個狀態(tài) Dom 元素的坐標(biāo)、大小的等相關(guān)信息。用于替代舊的生命周期中的 componentWillUpdate。 ? ?該函數(shù)的返回值將會作為 componentDidUpdate 的第三個參數(shù)出現(xiàn)。 復(fù)制代碼
需要注意的是,一般都會問為什么要廢棄三個生命周期,原因是什么。 2.2 setState 同步還是異步 setState
本身代碼的執(zhí)行肯定是同步的,這里的異步是指是多個 state 會合成到一起進(jìn)行批量更新。 同步還是異步取決于它被調(diào)用的環(huán)境。
如果
setState
在 React 能夠控制的范圍被調(diào)用,它就是異步的。比如合成事件處理函數(shù),生命周期函數(shù), 此時會進(jìn)行批量更新,也就是將狀態(tài)合并后再進(jìn)行 DOM 更新。如果
setState
在原生 JavaScript 控制的范圍被調(diào)用,它就是同步的。比如原生事件處理函數(shù),定時器回調(diào)函數(shù),Ajax 回調(diào)函數(shù)中,此時setState
被調(diào)用后會立即更新 DOM 。
3、對函數(shù)式編程的理解 這篇文章寫的真的太好了,一定要讀:。 總結(jié)一下: 函數(shù)式編程有兩個核心概念。
數(shù)據(jù)不可變(無副作用): 它要求你所有的數(shù)據(jù)都是不可變的,這意味著如果你想修改一個對象,那你應(yīng)該創(chuàng)建一個新的對象用來修改,而不是修改已有的對象。
無狀態(tài): 主要是強(qiáng)調(diào)對于一個函數(shù),不管你何時運(yùn)行,它都應(yīng)該像第一次運(yùn)行一樣,給定相同的輸入,給出相同的輸出,完全不依賴外部狀態(tài)的變化。
純函數(shù)帶來的意義。
便于測試和優(yōu)化:這個意義在實(shí)際項(xiàng)目開發(fā)中意義非常大,由于純函數(shù)對于相同的輸入永遠(yuǎn)會返回相同的結(jié)果,因此我們可以輕松斷言函數(shù)的執(zhí)行結(jié)果,同時也可以保證函數(shù)的優(yōu)化不會影響其他代碼的執(zhí)行。
可緩存性:因?yàn)橄嗤妮斎肟偸强梢苑祷叵嗤妮敵?,因此,我們可以提前緩存函?shù)的執(zhí)行結(jié)果。
更少的 Bug:使用純函數(shù)意味著你的函數(shù)中不存在指向不明的 this,不存在對全局變量的引用,不存在對參數(shù)的修改,這些共享狀態(tài)往往是絕大多數(shù) bug 的源頭。
4、react hooks 現(xiàn)在應(yīng)該大多數(shù)面試官會問 hooks 相關(guān)的啦。這里我強(qiáng)烈推薦三篇文章,即使沒看過源碼,也能比較好地理解一些原理: 4.1 為什么不能在條件語句中寫 hook 推薦這篇文章:。 hook 在每次渲染時的查找是根據(jù)一個“全局”的下標(biāo)對鏈表進(jìn)行查找的,如果放在條件語句中使用,有一定幾率會造成拿到的狀態(tài)出現(xiàn)錯亂。 4.2 HOC 和 hook 的區(qū)別 hoc 能復(fù)用邏輯和視圖,hook 只能復(fù)用邏輯。 4.3 useEffect 和 useLayoutEffect 區(qū)別 對于 React 的函數(shù)組件來說,其更新過程大致分為以下步驟:
因?yàn)槟硞€事件
state
發(fā)生變化。React 內(nèi)部更新
state
變量。React 處理更新組件中 return 出來的 DOM 節(jié)點(diǎn)(進(jìn)行一系列 dom diff 、調(diào)度等流程)。
將更新過后的 DOM 數(shù)據(jù)繪制到瀏覽器中。
用戶看到新的頁面。
useEffect
在第 4 步之后執(zhí)行,且是異步的,保證了不會阻塞瀏覽器進(jìn)程。 useLayoutEffect
在第 3 步至第 4 步之間執(zhí)行,且是同步代碼,所以會阻塞后面代碼的執(zhí)行。 4.4 useEffect 依賴為空數(shù)組與 componentDidMount 區(qū)別 在 render
執(zhí)行之后,componentDidMount
會執(zhí)行,如果在這個生命周期中再一次 setState
,會導(dǎo)致再次 render
,返回了新的值,瀏覽器只會渲染第二次 render
返回的值,這樣可以避免閃屏。 但是 useEffect
是在真實(shí)的 DOM 渲染之后才會去執(zhí)行,這會造成兩次 render
,有可能會閃屏。 實(shí)際上 useLayoutEffect
會更接近 componentDidMount
的表現(xiàn),它們都同步執(zhí)行且會阻礙真實(shí)的 DOM 渲染的。 4.5 React.memo() 和 React.useMemo() 的區(qū)別
memo
是一個高階組件,默認(rèn)情況下會對props
進(jìn)行淺比較,如果相等不會重新渲染。多數(shù)情況下我們比較的都是引用類型,淺比較就會失效,所以我們可以傳入第二個參數(shù)手動控制。useMemo
返回的是一個緩存值,只有依賴發(fā)生變化時才會去重新執(zhí)行作為第一個參數(shù)的函數(shù),需要記住的是,useMemo
是在render
階段執(zhí)行的,所以不要在這個函數(shù)內(nèi)部執(zhí)行與渲染無關(guān)的操作,諸如副作用這類的操作屬于useEffect
的適用范疇。
4.6 React.useCallback() 和 React.useMemo() 的區(qū)別
useCallback
可緩存函數(shù),其實(shí)就是避免每次重新渲染后都去重新執(zhí)行一個新的函數(shù)。useMemo
可緩存值。
有很多時候,我們在 useEffect
中使用某個定義的外部函數(shù),是要添加到 deps
數(shù)組中的,如果不用 useCallback
緩存,這個函數(shù)在每次重新渲染時都是一個完全新的函數(shù),也就是引用地址發(fā)生了變化,這就會導(dǎo)致 useEffect
總會無意義的執(zhí)行。 4.7 React.forwardRef 是什么及其作用 這里還是閱讀官方文檔來的清晰:。 一般在父組件要拿到子組件的某個實(shí)際的 DOM 元素時會用到。 6、react hooks 與 class 組件對比 7、介紹 React dom diff 算法 。 8、對 React Fiber 的理解 關(guān)于這塊兒我覺得可以好好閱讀下這篇無敵的博客了:。 它可以教你一步步實(shí)現(xiàn)一個簡單的基于 React Fiber 的 React,可以學(xué)到很多 React 的設(shè)計(jì)思想,畢竟為了面試我們可能大多數(shù)人是沒有時間或能力去閱讀源碼的了。 然后我們再閱讀下其它作者對于 React Fiber 的理解,再轉(zhuǎn)化為我們自己的思考總結(jié),以下是推薦文章: 9、React 性能優(yōu)化手段 推薦文章:
使用
React.memo
來緩存組件。使用
React.useMemo
緩存大量的計(jì)算。避免使用匿名函數(shù)。
利用
React.lazy
和React.Suspense
延遲加載不是立即需要的組件。盡量使用 CSS 而不是強(qiáng)制加載和卸載組件。
使用
React.Fragment
避免添加額外的 DOM。
10、React Redux
webpack
原理初探:簡易實(shí)現(xiàn):,加料:,熱更新原理:面試題: 這里要注意,應(yīng)該還會考 webpack5 和 4 有哪些區(qū)別。
模塊化
(這里面沒有講 umd)
性能優(yōu)化
代碼層面:
防抖和節(jié)流(resize,scroll,input)。
減少回流(重排)和重繪。
事件委托。
css 放 ,js 腳本放 ?最底部。
減少 DOM 操作。
按需加載,比如 React 中使用
React.lazy
和React.Suspense
,通常需要與 webpack 中的splitChunks
配合。
構(gòu)建方面:
壓縮代碼文件,在 webpack 中使用
terser-webpack-plugin
壓縮 Javascript 代碼;使用css-minimizer-webpack-plugin
壓縮 CSS 代碼;使用html-webpack-plugin
壓縮 html 代碼。開啟 gzip 壓縮,webpack 中使用
compression-webpack-plugin
,node 作為服務(wù)器也要開啟,使用compression
。常用的第三方庫使用 CDN 服務(wù),在 webpack 中我們要配置 externals,將比如 React, Vue 這種包不打倒最終生成的文件中。而是采用 CDN 服務(wù)。
其它:
使用 http2。因?yàn)榻馕鏊俣瓤欤^部壓縮,多路復(fù)用,服務(wù)器推送靜態(tài)資源。
使用服務(wù)端渲染。
圖片壓縮。
使用 http 緩存,比如服務(wù)端的響應(yīng)中添加
Cache-Control / Expires
。
常見手寫
以下的內(nèi)容是上面沒有提到的手寫,比如 new
、Promise.all
這種上面內(nèi)容中已經(jīng)提到了如何寫。 1、防抖 function debounce(func, wait, immediate) { ? let timeout; ? ?return function () { ? ? let context = this; ? ? let args = arguments; ? ? ?if (timeout) clearTimeout(timeout); ? ? if (immediate) { ? ? ? let callNow = !timeout; ? ? ? timeout = setTimeout(function () { ? ? ? ? timeout = null; ? ? ? }, wait); ? ? ? if (callNow) func.apply(context, args); ? ? } else { ? ? ? timeout = setTimeout(function () { ? ? ? ? func.apply(context, args); ? ? ? }, wait); ? ? } ? }; } 復(fù)制代碼 2、節(jié)流 // 使用時間戳 function throttle(func, wait) { ? let preTime = 0; ? ?return function () { ? ? let nowTime = +new Date(); ? ? let context = this; ? ? let args = arguments; ? ? ?if (nowTime - preTime > wait) { ? ? ? func.apply(context, args); ? ? ? preTime = nowTime; ? ? } ? }; } ?// 定時器實(shí)現(xiàn) function throttle(func, wait) { ? let timeout; ? ?return function () { ? ? let context = this; ? ? let args = arguments; ? ? ?if (!timeout) { ? ? ? timeout = setTimeout(function () { ? ? ? ? timeout = null; ? ? ? ? func.apply(context, args); ? ? ? }, wait); ? ? } ? }; } 復(fù)制代碼 3、快速排序 這里對快排思想不太明白的同學(xué)可以看下這個講解的很清晰的視頻:。 function sortArray(nums) { ? quickSort(0, nums.length - 1, nums); ? return nums; } ?function quickSort(start, end, arr) { ? if (start < end) { ? ? const mid = sort(start, end, arr); ? ? quickSort(start, mid - 1, arr); ? ? quickSort(mid + 1, end, arr); ? } } ?function sort(start, end, arr) { ? const base = arr[start]; ? let left = start; ? let right = end; ? while (left !== right) { ? ? while (arr[right] >= base && right > left) { ? ? ? right--; ? ? } ? ? arr[left] = arr[right]; ? ? while (arr[left] <= base && right > left) { ? ? ? left++; ? ? } ? ? arr[right] = arr[left]; ? } ? arr[left] = base; ? return left; } 復(fù)制代碼 4、instanceof 這個手寫一定要懂原型及原型鏈。 function myInstanceof(target, origin) { ? if (typeof target !== "object" || target === null) return false; ? if (typeof origin !== "function") ? ? throw new TypeError("origin must be function"); ? let proto = Object.getPrototypeOf(target); // 相當(dāng)于 proto = target.proto; while (proto) { ? ? if (proto === origin.prototype) return true; ? ? proto = Object.getPrototypeOf(proto); ? } ? return false; } 復(fù)制代碼 5、數(shù)組扁平化 重點(diǎn),不要覺得用不到就不管,這道題就是考察你對 js 語法的熟練程度以及手寫代碼的基本能力。 function flat(arr, depth = 1) { ? if (depth > 0) { ? ? // 以下代碼還可以簡化,不過為了可讀性,還是.... return arr.reduce((pre, cur) => { ? ? ? return pre.concat(Array.isArray(cur) ? flat(cur, depth - 1) : cur); ? ? }, []); ? } ? return arr.slice(); } 復(fù)制代碼 6、手寫 reduce 先不考慮第二個參數(shù)初始值: Array.prototype.reduce = function (cb) { ? const arr = this; //this就是調(diào)用reduce方法的數(shù)組 let total = arr[0]; // 默認(rèn)為數(shù)組的第一項(xiàng) for (let i = 1; i < arr.length; i++) { ? ? total = cb(total, arr[i], i, arr); ? } ? return total; }; 復(fù)制代碼 考慮上初始值: Array.prototype.reduce = function (cb, initialValue) { ? const arr = this; ? let total = initialValue || arr[0]; ? // 有初始值的話從0遍歷,否則從1遍歷 for (let i = initialValue ? 0 : 1; i < arr.length; i++) { ? ? total = cb(total, arr[i], i, arr); ? } ? return total; }; 復(fù)制代碼 7、帶并發(fā)的異步調(diào)度器 Scheduler JS 實(shí)現(xiàn)一個帶并發(fā)限制的異度調(diào)度器 Scheduler,保證同時運(yùn)行的任務(wù)最多有兩個。完善下面代碼中的 Scheduler 類,使得以下程序能正確輸出。 class Scheduler { ? add(promiseMaker) {} } ?const timeout = (time) => new Promise((resolve) => { ? ? setTimeout(resolve, time); ? }); ?const scheduler = new Scheduler(); const addTask = (time, order) => { ? scheduler.add(() => timeout(time).then(() => console.log(order))); }; ?addTask(1000, "1"); addTask(500, "2"); addTask(300, "3"); addTask(400, "4"); // output:2 3 1 4 // 一開始,1,2兩個任務(wù)進(jìn)入隊(duì)列。 // 500ms 時,2完成,輸出2,任務(wù)3入隊(duì)。 // 800ms 時,3完成,輸出3,任務(wù)4入隊(duì)。 // 1000ms 時,1完成,輸出1。 復(fù)制代碼 根據(jù)題目,我們只需要操作 Scheduler
類就行: class Scheduler { ? constructor() { ? ? this.waitTasks = []; // 待執(zhí)行的任務(wù)隊(duì)列 this.excutingTasks = []; // 正在執(zhí)行的任務(wù)隊(duì)列 this.maxExcutingNum = 2; // 允許同時運(yùn)行的任務(wù)數(shù)量 ? } ? ?add(promiseMaker) { ? ? if (this.excutingTasks.length < this.maxExcutingNum) { ? ? ? this.run(promiseMaker); ? ? } else { ? ? ? this.waitTasks.push(promiseMaker); ? ? } ? } ? ?run(promiseMaker) { ? ? const len = this.excutingTasks.push(promiseMaker); ? ? const index = len - 1; ? ? promiseMaker().then(() => { ? ? ? this.excutingTasks.splice(index, 1); ? ? ? if (this.waitTasks.length > 0) { ? ? ? ? this.run(this.waitTasks.shift()); ? ? ? } ? ? }); ? } } 復(fù)制代碼 8、去重
利用 ES6
set
關(guān)鍵字:
function unique(arr) { ? return [...new Set(arr)]; } 復(fù)制代碼
利用 ES5
filter
方法:
function unique(arr) { ? return arr.filter((item, index, array) => { ? ? return array.indexOf(item) === index; ? }); } 復(fù)制代碼
其它
requestAnimationFrame()
如何排查內(nèi)存泄漏問題,面試官可能會問為什么頁面越來越卡頓,直至卡死,怎么定位到產(chǎn)生這種現(xiàn)象的源代碼(開發(fā)環(huán)境)?()
vite 大火,我復(fù)習(xí)的時候是去年 9 月份,還沒那么火,可能現(xiàn)在的你需要學(xué)一學(xué)了~
vue3 也一樣,如果你是 React 技術(shù)棧(就像我之前一樣)當(dāng)我沒說。
算法
這部分大家可以點(diǎn)擊以下這個倉庫,按照倉庫中的題目順序進(jìn)行刷題,都是我親自刷過的,排了最適合的順序:。然后如果大家想看下大廠的算法高頻題可以看這個倉庫: