本文挑選了20道大廠面試題,建議在閱讀時,先思考一番,不要直接看解析。儘管,本文全部的答案,都是我在翻閱各類資料,思考並驗證以後,纔給出的。但因水平有限,本人的答案未必是最優的,若是您有更好的答案,歡迎給我留言。若是有錯誤,也請在評論區指出,謝謝。javascript
本文篇幅較長,但願小夥伴們可以堅持讀完,若是想加入交流羣,能夠經過文末的公衆號添加我爲好友。css
更多文章可戳:github.com/YvetteLau/B…html
new
的實現原理:前端
若是用一句話說明 this 的指向,那麼便是: 誰調用它,this 就指向誰。html5
可是僅經過這句話,咱們不少時候並不能準確判斷 this 的指向。所以咱們須要藉助一些規則去幫助本身:java
this 的指向能夠按照如下順序判斷:node
瀏覽器環境:不管是否在嚴格模式下,在全局執行環境中(在任何函數體外部)this 都指向全局對象 window
;git
node 環境:不管是否在嚴格模式下,在全局執行環境中(在任何函數體外部),this 都是空對象 {}
;github
new
綁定若是是 new
綁定,而且構造函數中沒有返回 function 或者是 object,那麼 this 指向這個新對象。以下:面試
構造函數返回值不是 function 或 object。
new Super()
返回的是 this 對象。
構造函數返回值是 function 或 object,
new Super()
是返回的是Super種返回的對象。
這裏一樣須要注意一種特殊狀況,若是 call,apply 或者 bind 傳入的第一個參數值是 undefined
或者 null
,嚴格模式下 this 的值爲傳入的值 null /undefined。非嚴格模式下,實際應用的默認綁定規則,this 指向全局對象(node環境爲global,瀏覽器環境爲window)
xxx.fn()
非嚴格模式: node環境,指向全局對象 global,瀏覽器環境,指向全局對象 window。
嚴格模式:執行 undefined
箭頭函數沒有本身的this,繼承外層上下文綁定的this。
深拷貝和淺拷貝是針對複雜數據類型來講的,淺拷貝只拷貝一層,而深拷貝是層層拷貝。
深拷貝複製變量值,對於非基本類型的變量,則遞歸至基本類型變量後,再複製。 深拷貝後的對象與原來的對象是徹底隔離的,互不影響,對一個對象的修改並不會影響另外一個對象。
淺拷貝是會將對象的每一個屬性進行依次複製,可是當對象的屬性值是引用類型時,實質複製的是其引用,當引用指向的值改變時也會跟着變化。
可使用 for in
、 Object.assign
、 擴展運算符 ...
、Array.prototype.slice()
、Array.prototype.concat()
等,例如:
能夠看出淺拷貝只最第一層屬性進行了拷貝,當第一層的屬性值是基本數據類型時,新的對象和原對象互不影響,可是若是第一層的屬性值是複雜數據類型,那麼新對象和原對象的屬性值其指向的是同一塊內存地址。
1.深拷貝最簡單的實現是:
JSON.parse(JSON.stringify(obj))
JSON.parse(JSON.stringify(obj))
是最簡單的實現方式,可是有一些缺陷:
2.實現一個 deepClone 函數
RegExp
或者 Date
類型,返回對應類型call
和 apply
的功能相同,都是改變 this
的執行,並當即執行函數。區別在於傳參方式不一樣。
func.call(thisArg, arg1, arg2, ...)
:第一個參數是 this
指向的對象,其它參數依次傳入。
func.apply(thisArg, [argsArray])
:第一個參數是 this
指向的對象,第二個參數是數組或類數組。
一塊兒思考一下,如何模擬實現 call
?
首先,咱們知道,函數均可以調用 call
,說明 call
是函數原型上的方法,全部的實例均可以調用。即: Function.prototype.call
。
call
方法中獲取調用call()
函數window / global
(非嚴格模式)call
的第一個參數是 this 指向的對象,根據隱式綁定的規則,咱們知道 obj.foo()
, foo()
中的 this
指向 obj
;所以咱們能夠這樣調用函數 thisArgs.func(...args)
apply
的實現思路和 call
一致,僅參數處理略有差異。以下:
在開始以前,咱們首先須要搞清楚函數柯里化的概念。
函數柯里化是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。
函數柯里化的主要做用:
- 利用隱式類型轉換
==
操做符在左右數據類型不一致時,會先進行隱式轉換。
a == 1 && a == 2 && a == 3
的值意味着其不多是基本數據類型。由於若是 a 是 null 或者是 undefined bool類型,都不可能返回true。
所以能夠推測 a 是複雜數據類型,JS 中複雜數據類型只有 object
,回憶一下,Object 轉換爲原始類型會調用什麼方法?
若是部署了 [Symbol.toPrimitive]
接口,那麼調用此接口,若返回的不是基本數據類型,拋出錯誤。
若是沒有部署 [Symbol.toPrimitive]
接口,那麼根據要轉換的類型,先調用 valueOf
/ toString
hint
是 default
時,調用順序爲:valueOf
>>> toString
,即valueOf
返回的不是基本數據類型,纔會繼續調用 valueOf
,若是toString
返回的還不是基本數據類型,那麼拋出錯誤。hint
是 string
(Date對象的hint默認是string) ,調用順序爲:toString
>>> valueOf
,即toString
返回的不是基本數據類型,纔會繼續調用 valueOf
,若是valueOf
返回的還不是基本數據類型,那麼拋出錯誤。hint
是 number
,調用順序爲: valueOf
>>> toString
- 利用數據劫持(Proxy/Object.defineProperty)
- 數組的
toString
接口默認調用數組的join
方法,重寫join
方法
Box 是 CSS 佈局的對象和基本單位,頁面是由若干個Box組成的。
元素的類型 和 display
屬性,決定了這個 Box 的類型。不一樣類型的 Box 會參與不一樣的 Formatting Context。
Formatting Context
Formatting Context 是頁面的一塊渲染區域,而且有一套渲染規則,決定了其子元素將如何定位,以及和其它元素的關係和相互做用。
Formatting Context 有 BFC (Block formatting context),IFC (Inline formatting context),FFC (Flex formatting context) 和 GFC (Grid formatting context)。FFC 和 GFC 爲 CC3 中新增。
margin
屬性決定。屬於同一個BFC的兩個相鄰Box的margin會發生重疊【符合合併原則的margin合併後是使用大的margin】如何建立BFC
BFC 的應用
margin
會發生重疊,觸發生成兩個BFC,即不會重疊)
<script>
標籤中增長async
(html5) 或者defer
(html4) 屬性,腳本就會異步加載。
<script src="../XXX.js" defer></script>
defer
和 async
的區別在於:
defer
要等到整個頁面在內存中正常渲染結束(DOM 結構徹底生成,以及其餘腳本執行完成),在window.onload 以前執行;async
一旦下載完,渲染引擎就會中斷渲染,執行這個腳本之後,再繼續渲染。defer
腳本,會按照它們在頁面出現的順序加載async
腳本不能保證加載順序動態建立
script
標籤
動態建立的 script
,設置 src
並不會開始下載,而是要添加到文檔中,JS文件纔會開始下載。
XHR 異步加載JS
ES5 有 6 種方式能夠實現繼承,分別爲:
原型鏈繼承的基本思想是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。
缺點:
借用構造函數的技術,其基本思想爲:
在子類型的構造函數中調用超類型構造函數。
優勢:
缺點:
組合繼承指的是將原型鏈和借用構造函數技術組合到一塊,從而發揮兩者之長的一種繼承模式。基本思路:
使用原型鏈實現對原型屬性和方法的繼承,經過借用構造函數來實現對實例屬性的繼承,既經過在原型上定義方法來實現了函數複用,又保證了每一個實例都有本身的屬性。
缺點:
優勢:
原型繼承的基本思想:
藉助原型能夠基於已有的對象建立新對象,同時還沒必要所以建立自定義類型。
在 object()
函數內部,先穿甲一個臨時性的構造函數,而後將傳入的對象做爲這個構造函數的原型,最後返回了這個臨時類型的一個新實例,從本質上講,object()
對傳入的對象執行了一次淺拷貝。
ECMAScript5經過新增 Object.create()
方法規範了原型式繼承。這個方法接收兩個參數:一個用做新對象原型的對象和(可選的)一個爲新對象定義額外屬性的對象(能夠覆蓋原型對象上的同名屬性),在傳入一個參數的狀況下,Object.create()
和 object()
方法的行爲相同。
在沒有必要建立構造函數,僅讓一個對象與另外一個對象保持類似的狀況下,原型式繼承是能夠勝任的。
缺點:
同原型鏈實現繼承同樣,包含引用類型值的屬性會被全部實例共享。
寄生式繼承是與原型式繼承緊密相關的一種思路。寄生式繼承的思路與寄生構造函數和工廠模式相似,即建立一個僅用於封裝繼承過程的函數,該函數在內部已某種方式來加強對象,最後再像真地是它作了全部工做同樣返回對象。
基於 person
返回了一個新對象 -—— person2
,新對象不只具備 person
的全部屬性和方法,並且還有本身的 sayHi()
方法。在考慮對象而不是自定義類型和構造函數的狀況下,寄生式繼承也是一種有用的模式。
缺點:
所謂寄生組合式繼承,即經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法,基本思路:
沒必要爲了指定子類型的原型而調用超類型的構造函數,咱們須要的僅是超類型原型的一個副本,本質上就是使用寄生式繼承來繼承超類型的原型,而後再將結果指定給子類型的原型。寄生組合式繼承的基本模式以下所示:
constructor
屬性至此,咱們就能夠經過調用 inheritPrototype
來替換爲子類型原型賦值的語句:
優勢:
只調用了一次超類構造函數,效率更高。避免在SuberType.prototype
上面建立沒必要要的、多餘的屬性,與其同時,原型鏈還能保持不變。
所以寄生組合繼承是引用類型最理性的繼承範式。
隱藏類型
屏幕並非惟一的輸出機制,好比說屏幕上看不見的元素(隱藏的元素),其中一些依然可以被讀屏軟件閱讀出來(由於讀屏軟件依賴於可訪問性樹來闡述)。爲了消除它們之間的歧義,咱們將其歸爲三大類:
徹底隱藏
display
屬性display: none;
複製代碼
HTML5 新增屬性,至關於 display: none
<div hidden>
</div>
複製代碼
視覺上的隱藏
position
和 盒模型 將元素移出可視區範圍posoition
爲 absolute
或 fixed
,經過設置 top
、left
等值,將其移出可視區域。position:absolute;
left: -99999px;
複製代碼
position
爲 relative
,經過設置 top
、left
等值,將其移出可視區域。position: relative;
left: -99999px;
height: 0
複製代碼
margin-left: -99999px;
height: 0;
複製代碼
transform: scale(0);
height: 0;
複製代碼
translateX
, translateY
transform: translateX(-99999px);
height: 0
複製代碼
rotate
transform: rotateY(90deg);
複製代碼
height: 0;
width: 0;
font-size: 0;
複製代碼
height: 0;
width: 0;
overflow: hidden;
複製代碼
opacity: 0;
複製代碼
visibility
屬性visibility: hidden;
複製代碼
z-index
屬性position: relative;
z-index: -999;
複製代碼
再設置一個層級較高的元素覆蓋在此元素上。
clip-path: polygon(0 0, 0 0, 0 0, 0 0);
複製代碼
語義上的隱藏
讀屏軟件不可讀,佔據空間,可見。
<div aria-hidden="true">
</div>
複製代碼
聲明方式 | 變量提高 | 暫時性死區 | 重複聲明 | 塊做用域有效 | 初始值 | 從新賦值 |
---|---|---|---|---|---|---|
var | 會 | 不存在 | 容許 | 不是 | 非必須 | 容許 |
let | 不會 | 存在 | 不容許 | 是 | 非必須 | 容許 |
const | 不會 | 存在 | 不容許 | 是 | 必須 | 不容許 |
1.let/const 定義的變量不會出現變量提高,而 var 定義的變量會提高。
2.相同做用域中,let 和 const 不容許重複聲明,var 容許重複聲明。
3.const 聲明變量時必須設置初始值
4.const 聲明一個只讀的常量,這個常量不可改變。
這裏有一個很是重要的點便是:在JS中,複雜數據類型,存儲在棧中的是堆內存的地址,存在棧中的這個地址是不變的,可是存在堆中的值是能夠變得。有沒有至關常量指針/指針常量~
一圖勝萬言,以下圖所示,不變的是棧內存中 a 存儲的 20,和 b 中存儲的 0x0012ff21(瞎編的一個數字)。而 {age: 18, star: 200} 是可變的。
執行上下文就是當前 JavaScript 代碼被解析和執行時所在環境的抽象概念, JavaScript 中運行任何的代碼都是在執行上下文中運行。
執行上下文類型分爲:
執行上下文建立過程當中,須要作如下幾件事:
做用域負責收集和維護由全部聲明的標識符(變量)組成的一系列查詢,並實施一套很是嚴格的規則,肯定當前執行的代碼對這些標識符的訪問權限。—— 摘錄自《你不知道的JavaScript》(上卷)
做用域有兩種工做模型:詞法做用域和動態做用域,JS採用的是詞法做用域工做模型,詞法做用域意味着做用域是由書寫代碼時變量和函數聲明的位置決定的。(with
和 eval
可以修改詞法做用域,可是不推薦使用,對此不作特別說明)
做用域分爲:
執行棧,也叫作調用棧,具備 LIFO (後進先出) 結構,用於存儲在代碼執行期間建立的全部執行上下文。
規則以下:
以一段代碼具體說明:
Global Execution Context
(即全局執行上下文)首先入棧,過程以下:
僞代碼:
//全局執行上下文首先入棧
ECStack.push(globalContext);
//執行fun1();
ECStack.push(<fun1> functionContext);
//fun1中又調用了fun2;
ECStack.push(<fun2> functionContext);
//fun2中又調用了fun3;
ECStack.push(<fun3> functionContext);
//fun3執行完畢
ECStack.pop();
//fun2執行完畢
ECStack.pop();
//fun1執行完畢
ECStack.pop();
//javascript繼續順序執行下面的代碼,但ECStack底部始終有一個 全局上下文(globalContext);
複製代碼
做用域鏈就是從當前做用域開始一層一層向上尋找某個變量,直到找到全局做用域仍是沒找到,就宣佈放棄。這種一層一層的關係,就是做用域鏈。
如:
fn2做用域鏈 = [fn2做用域, fn1做用域,全局做用域]
防抖函數的做用就是控制函數在必定時間內的執行次數。防抖意味着N秒內函數只會被執行一次,若是N秒內再次被觸發,則從新計算延遲時間。
舉例說明: 小思最近在減肥,可是她很是吃吃零食。爲此,與其男友約定好,若是10天不吃零食,就能夠購買一個包(不要問爲何是包,由於包治百病)。可是若是中間吃了一次零食,那麼就要從新計算時間,直到小思堅持10天沒有吃零食,才能購買一個包。因此,管不住嘴的小思,沒有機會買包(悲傷的故事)... 這就是 防抖。
防抖函數實現
timeout
是 null
,調用 later()
,若 immediate
爲true
,那麼當即調用 func.apply(this, params)
;若是 immediate
爲 false
,那麼過 wait
以後,調用 func.apply(this, params)
timeout
已經重置爲 null
(即 setTimeout
的倒計時結束),那麼流程與第一次觸發時同樣,若 timeout
不爲 null
(即 setTimeout 的倒計時未結束),那麼清空定時器,從新開始計時。immediate
爲 true 時,表示函數在每一個等待時延的開始被調用。immediate
爲 false 時,表示函數在每一個等待時延的結束被調用。
防抖的應用場景
節流函數的做用是規定一個單位時間,在這個單位時間內最多隻能觸發一次函數執行,若是這個單位時間內屢次觸發函數,只能有一次生效。
節流函數實現
禁用第一次首先執行,傳遞 {leading: false}
;想禁用最後一次執行,傳遞 {trailing: false}
節流的應用場景
《JavaScript高級程序設計》:
閉包是指有權訪問另外一個函數做用域中的變量的函數
《JavaScript權威指南》:
從技術的角度講,全部的JavaScript函數都是閉包:它們都是對象,它們都關聯到做用域鏈。
《你不知道的JavaScript》
當函數能夠記住並訪問所在的詞法做用域時,就產生了閉包,即便函數是在當前詞法做用域以外執行。
閉包使得函數能夠繼續訪問定義時的詞法做用域。拜 fn 所賜,在 foo() 執行後,foo 內部做用域不會被銷燬。
可以訪問函數定義時所在的詞法做用域(阻止其被回收)。
私有化變量
模塊模式具備兩個必備的條件(來自《你不知道的JavaScript》)
Promise.all 功能
Promise.all(iterable)
返回一個新的 Promise 實例。此實例在 iterable
參數內全部的 promise
都 fulfilled
或者參數中不包含 promise
時,狀態變成 fulfilled
;若是參數中 promise
有一個失敗rejected
,此實例回調失敗,失敗緣由的是第一個失敗 promise
的返回結果。
let p = Promise.all([p1, p2, p3]);
複製代碼
p的狀態由 p1,p2,p3決定,分紅如下;兩種狀況:
(1)只有p一、p二、p3的狀態都變成 fulfilled
,p的狀態纔會變成 fulfilled
,此時p一、p二、p3的返回值組成一個數組,傳遞給p的回調函數。
(2)只要p一、p二、p3之中有一個被 rejected
,p的狀態就變成 rejected
,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。
Promise.all 的特色
Promise.all 的返回值是一個 promise 實例
Promise.all
會 同步 返回一個已完成狀態的 promise
Promise.all
會 異步 返回一個已完成狀態的 promise
Promise.all
返回一個 處理中(pending) 狀態的 promise
.Promise.all 返回的 promise 的狀態
Promise.all
返回的 promise
異步地變爲完成。promise
失敗,Promise.all
異步地將失敗的那個結果給失敗狀態的回調函數,而無論其它 promise
是否完成Promise.all
返回的 promise
的完成狀態的結果都是一個數組Promise.all 實現
例如:
flattenDeep([1, [2, [3, [4]], 5]]); //[1, 2, 3, 4, 5]
複製代碼
ES6 爲數組實例新增了 flat
方法,用於將嵌套的數組「拉平」,變成一維的數組。該方法返回一個新數組,對原數組沒有影響。
flat
默認只會 「拉平」 一層,若是想要 「拉平」 多層的嵌套數組,須要給 flat
傳遞一個整數,表示想要拉平的層數。
當傳遞的整數大於數組嵌套的層數時,會將數組拉平爲一維數組,JS能表示的最大數字爲 Math.pow(2, 53) - 1
,所以咱們能夠這樣定義 flattenDeep
函數
利用 reduce 和 concat
使用 stack 無限反嵌套多層嵌套數組
例如:
uniq([1, 2, 3, 5, 3, 2]);//[1, 2, 3, 5]
複製代碼
法1: 利用ES6新增數據類型
Set
Set
相似於數組,可是成員的值都是惟一的,沒有重複的值。
法2: 利用
indexOf
法3: 利用
includes
法4:利用
reduce
法5:利用
Map
Symbol.iterator
屬性,Symbol.iterator()
返回的是一個遍歷器對象for ... of
進行循環Array.from
轉換爲數組Iterator
接口的數據結構:儘管瀏覽器有同源策略,可是 <script>
標籤的 src
屬性不會被同源策略所約束,能夠獲取任意服務器上的腳本並執行。jsonp
經過插入 script
標籤的方式來實現跨域,參數只能經過 url
傳入,僅能支持 get
請求。
實現原理:
jsonp源碼實現
使用:
服務端代碼(node):
[1] [JavaScript高級程序設計第六章]
[2] Step-By-Step】高頻面試題深刻解析 / 週刊01
[3] Step-By-Step】高頻面試題深刻解析 / 週刊02
[4] Step-By-Step】高頻面試題深刻解析 / 週刊03
[5] Step-By-Step】高頻面試題深刻解析 / 週刊04
謝謝各位小夥伴願意花費寶貴的時間閱讀本文,若是本文給了您一點幫助或者是啓發,請不要吝嗇你的贊和Star,您的確定是我前進的最大動力。 github.com/YvetteLau/B…