本文挑選了20道大廠面試題,建議在閱讀時,先思考一番,不要直接看解析。儘管,本文所有的答案,都是我在翻閱各種資料,思考並驗證之後,才給出的。但因水平有限,本人的答案未必是最優的,如果您有更好的答案,歡迎給我留言。如果有錯誤,也請在評論區指出,謝謝。
本文篇幅較長,希望小夥伴們能夠堅持讀完,如果想加入交流群,可以通過文末的公眾號新增我為好友。
更多文章可戳:github.com/YvetteLau/B…
1. new的實現原理是什麼?
檢視解析
new
的實現原理:
- 建立一個空物件,建構函式中的this指向這個空物件
- 這個新物件被執行 [[原型]] 連線
- 執行建構函式方法,屬性和方法被新增到this引用的物件中
- 如果建構函式中沒有返回其它物件,那麼返回this,即建立的這個的新物件,否則,返回建構函式中返回的物件。
2. 如何正確判斷this的指向?
檢視解析
如果用一句話說明 this 的指向,那麼即是: 誰呼叫它,this 就指向誰。
但是僅通過這句話,我們很多時候並不能準確判斷 this 的指向。因此我們需要藉助一些規則去幫助自己:
this 的指向可以按照以下順序判斷:
全域性環境中的 this
瀏覽器環境:無論是否在嚴格模式下,在全域性執行環境中(在任何函式體外部)this 都指向全域性物件 window
;
node 環境:無論是否在嚴格模式下,在全域性執行環境中(在任何函式體外部),this 都是空物件 {}
;
是否是 new
繫結
如果是 new
繫結,並且建構函式中沒有返回 function 或者是 object,那麼 this 指向這個新物件。如下:
建構函式返回值不是 function 或 object。
new Super()
返回的是 this 物件。
建構函式返回值是 function 或 object,
new Super()
是返回的是Super種返回的物件。
函式是否通過 call,apply 呼叫,或者使用了 bind 繫結,如果是,那麼this繫結的就是指定的物件【歸結為顯式繫結】。
這裡同樣需要注意一種特殊情況,如果 call,apply 或者 bind 傳入的第一個引數值是 undefined
或者 null
,嚴格模式下 this 的值為傳入的值 null /undefined。非嚴格模式下,實際應用的預設繫結規則,this 指向全域性物件(node環境為global,瀏覽器環境為window)
隱式繫結,函式的呼叫是在某個物件上觸發的,即呼叫位置上存在上下文物件。典型的隱式呼叫為: xxx.fn()
預設繫結,在不能應用其它繫結規則時使用的預設規則,通常是獨立函式呼叫。
非嚴格模式: node環境,指向全域性物件 global,瀏覽器環境,指向全域性物件 window。
嚴格模式:執行 undefined
箭頭函式的情況:
箭頭函式沒有自己的this,繼承外層上下文繫結的this。
3. 深拷貝和淺拷貝的區別是什麼?實現一個深拷貝
檢視解析
深拷貝和淺拷貝是針對複雜資料型別來說的,淺拷貝只拷貝一層,而深拷貝是層層拷貝。
深拷貝
深拷貝複製變數值,對於非基本型別的變數,則遞迴至基本型別變數後,再複製。 深拷貝後的物件與原來的物件是完全隔離的,互不影響,對一個物件的修改並不會影響另一個物件。
淺拷貝
淺拷貝是會將物件的每個屬性進行依次複製,但是當物件的屬性值是引用型別時,實質複製的是其引用,當引用指向的值改變時也會跟著變化。
可以使用 for in
、 Object.assign
、 擴充套件運算子 ...
、Array.prototype.slice()
、Array.prototype.concat()
等,例如:
可以看出淺拷貝只最第一層屬性進行了拷貝,當第一層的屬性值是基本資料型別時,新的物件和原物件互不影響,但是如果第一層的屬性值是複雜資料型別,那麼新物件和原物件的屬性值其指向的是同一塊記憶體地址。
深拷貝實現
1.深拷貝最簡單的實現是:
JSON.parse(JSON.stringify(obj))
JSON.parse(JSON.stringify(obj))
是最簡單的實現方式,但是有一些缺陷:
- 物件的屬性值是函式時,無法拷貝。
- 原型鏈上的屬性無法拷貝
- 不能正確的處理 Date 型別的資料
- 不能處理 RegExp
- 會忽略 symbol
- 會忽略 undefined
2.實現一個 deepClone 函式
- 如果是基本資料型別,直接返回
- 如果是
RegExp
或者Date
型別,返回對應型別 - 如果是複雜資料型別,遞迴。
- 考慮迴圈引用的問題
4. call/apply 的實現原理是什麼?
檢視解析
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
一致,僅引數處理略有差別。如下:
5. 柯里化函式實現
檢視解析
在開始之前,我們首先需要搞清楚函式柯里化的概念。
函式柯里化是把接受多個引數的函式變換成接受一個單一引數(最初函式的第一個引數)的函式,並且返回接受餘下的引數而且返回結果的新函式的技術。
函式柯里化的主要作用:
- 引數複用
- 提前返回 – 返回接受餘下的引數且返回結果的新函式
- 延遲執行 – 返回新函式,等待執行
6. 如何讓 (a == 1 && a == 2 && a == 3) 的值為true?
檢視解析
- 利用隱式型別轉換
==
操作符在左右資料型別不一致時,會先進行隱式轉換。
a == 1 && a == 2 && a == 3
的值意味著其不可能是基本資料型別。因為如果 a 是 null 或者是 undefined bool型別,都不可能返回true。
因此可以推測 a 是複雜資料型別,JS 中複雜資料型別只有 object
,回憶一下,Object 轉換為原始型別會呼叫什麼方法?
-
如果部署了
[Symbol.toPrimitive]
介面,那麼呼叫此介面,若返回的不是基本資料型別,丟擲錯誤。 -
如果沒有部署
[Symbol.toPrimitive]
介面,那麼根據要轉換的型別,先呼叫valueOf
/toString
- 非Date型別物件,
hint
是default
時,呼叫順序為:valueOf
>>>toString
,即valueOf
返回的不是基本資料型別,才會繼續呼叫valueOf
,如果toString
返回的還不是基本資料型別,那麼丟擲錯誤。 - 如果
hint
是string
(Date物件的hint預設是string) ,呼叫順序為:toString
>>>valueOf
,即toString
返回的不是基本資料型別,才會繼續呼叫valueOf
,如果valueOf
返回的還不是基本資料型別,那麼丟擲錯誤。 - 如果
hint
是number
,呼叫順序為:valueOf
>>>toString
- 非Date型別物件,
- 利用資料劫持(Proxy/Object.defineProperty)
- 陣列的
toString
介面預設呼叫陣列的join
方法,重寫join
方法
7. 什麼是BFC?BFC的佈局規則是什麼?如何建立BFC?
檢視解析
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 中新增。
- BFC內,盒子依次垂直排列。
- BFC內,兩個盒子的垂直距離由
margin
屬性決定。屬於同一個BFC的兩個相鄰Box的margin會發生重疊【符合合併原則的margin合併後是使用大的margin】 - BFC內,每個盒子的左外邊緣接觸內部盒子的左邊緣(對於從右到左的格式,右邊緣接觸)。即使在存在浮動的情況下也是如此。除非建立新的BFC。
- BFC的區域不會與float box重疊。
- BFC就是頁面上的一個隔離的獨立容器,容器裡面的子元素不會影響到外面的元素。反之也如此。
- 計算BFC的高度時,浮動元素也參與計算。
如何建立BFC
- 根元素
- 浮動元素(float 屬性不為 none)
- position 為 absolute 或 fixed
- overflow 不為 visible 的塊元素
- display 為 inline-block, table-cell, table-caption
BFC 的應用
- 防止 margin 重疊 (同一個BFC內的兩個兩個相鄰Box的
margin
會發生重疊,觸發生成兩個BFC,即不會重疊) - 清除內部浮動 (建立一個新的 BFC,因為根據 BFC 的規則,計算 BFC 的高度時,浮動元素也參與計算)
- 自適應多欄佈局 (BFC的區域不會與float box重疊。因此,可以觸發生成一個新的BFC)
8. 非同步載入JS指令碼的方式有哪些?
檢視解析
<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
9. ES5有幾種方式可以實現繼承?分別有哪些優缺點?
檢視解析
ES5 有 6 種方式可以實現繼承,分別為:
1. 原型鏈繼承
原型鏈繼承的基本思想是利用原型讓一個引用型別繼承另一個引用型別的屬性和方法。
缺點:
- 通過原型來實現繼承時,原型會變成另一個型別的例項,原先的例項屬性變成了現在的原型屬性,該原型的引用型別屬性會被所有的例項共享。
- 在建立子型別的例項時,沒有辦法在不影響所有物件例項的情況下給超型別的建構函式中傳遞引數。
2. 借用建構函式
借用建構函式的技術,其基本思想為:
在子型別的建構函式中呼叫超型別建構函式。
優點:
- 可以向超類傳遞引數
- 解決了原型中包含引用型別值被所有例項共享的問題
缺點:
- 方法都在建構函式中定義,函式複用無從談起,另外超型別原型中定義的方法對於子型別而言都是不可見的。
3. 組合繼承(原型鏈 + 借用建構函式)
組合繼承指的是將原型鏈和借用建構函式技術組合到一塊,從而發揮二者之長的一種繼承模式。基本思路:
使用原型鏈實現對原型屬性和方法的繼承,通過借用建構函式來實現對例項屬性的繼承,既通過在原型上定義方法來實現了函式複用,又保證了每個例項都有自己的屬性。
缺點:
- 無論什麼情況下,都會呼叫兩次超型別建構函式:一次是在建立子型別原型的時候,另一次是在子型別建構函式內部。
優點:
- 可以向超類傳遞引數
- 每個例項都有自己的屬性
- 實現了函式複用
4. 原型式繼承
原型繼承的基本思想:
藉助原型可以基於已有的物件建立新物件,同時還不必因此建立自定義型別。
在 object()
函式內部,先穿甲一個臨時性的建構函式,然後將傳入的物件作為這個建構函式的原型,最後返回了這個臨時型別的一個新例項,從本質上講,object()
對傳入的物件執行了一次淺拷貝。
ECMAScript5通過新增 Object.create()
方法規範了原型式繼承。這個方法接收兩個引數:一個用作新物件原型的物件和(可選的)一個為新物件定義額外屬性的物件(可以覆蓋原型物件上的同名屬性),在傳入一個引數的情況下,Object.create()
和 object()
方法的行為相同。
在沒有必要建立建構函式,僅讓一個物件與另一個物件保持相似的情況下,原型式繼承是可以勝任的。
缺點:
同原型鏈實現繼承一樣,包含引用型別值的屬性會被所有例項共享。
5. 寄生式繼承
寄生式繼承是與原型式繼承緊密相關的一種思路。寄生式繼承的思路與寄生建構函式和工廠模式類似,即建立一個僅用於封裝繼承過程的函式,該函式在內部已某種方式來增強物件,最後再像真地是它做了所有工作一樣返回物件。
基於 person
返回了一個新物件 -—— person2
,新物件不僅具有 person
的所有屬性和方法,而且還有自己的 sayHi()
方法。在考慮物件而不是自定義型別和建構函式的情況下,寄生式繼承也是一種有用的模式。
缺點:
- 使用寄生式繼承來為物件新增函式,會由於不能做到函式複用而效率低下。
- 同原型鏈實現繼承一樣,包含引用型別值的屬性會被所有例項共享。
6. 寄生組合式繼承
所謂寄生組合式繼承,即通過借用建構函式來繼承屬性,通過原型鏈的混成形式來繼承方法,基本思路:
不必為了指定子型別的原型而呼叫超型別的建構函式,我們需要的僅是超型別原型的一個副本,本質上就是使用寄生式繼承來繼承超型別的原型,然後再將結果指定給子型別的原型。寄生組合式繼承的基本模式如下所示:
- 第一步:建立超型別原型的一個副本
- 第二步:為建立的副本新增
constructor
屬性 - 第三步:將新建立的物件賦值給子型別的原型
至此,我們就可以通過呼叫 inheritPrototype
來替換為子型別原型賦值的語句:
優點:
只呼叫了一次超類建構函式,效率更高。避免在SuberType.prototype
上面建立不必要的、多餘的屬性,與其同時,原型鏈還能保持不變。
因此寄生組合繼承是引用型別最理性的繼承正規化。
10. 隱藏頁面中的某個元素的方法有哪些?
檢視解析
隱藏型別
螢幕並不是唯一的輸出機制,比如說螢幕上看不見的元素(隱藏的元素),其中一些依然能夠被讀屏軟體閱讀出來(因為讀屏軟體依賴於可訪問性樹來闡述)。為了消除它們之間的歧義,我們將其歸為三大類:
- 完全隱藏:元素從渲染樹中消失,不佔據空間。
- 視覺上的隱藏:螢幕中不可見,佔據空間。
- 語義上的隱藏:讀屏軟體不可讀,但正常佔據空。
完全隱藏
1.display
屬性
display: none;
複製程式碼
2.hidden 屬性
HTML5 新增屬性,相當於 display: none
<div hidden>
</div>
複製程式碼
視覺上的隱藏
1.利用 position
和 盒模型 將元素移出可視區範圍
- 設定
posoition
為absolute
或fixed
,通過設定top
、left
等值,將其移出可視區域。
position:absolute;
left: -99999px;
複製程式碼
- 設定
position
為relative
,通過設定top
、left
等值,將其移出可視區域。
position: relative;
left: -99999px;
height: 0
複製程式碼
- 設定 margin 值,將其移出可視區域範圍(可視區域佔位)。
margin-left: -99999px;
height: 0;
複製程式碼
2.利用 transfrom
- 縮放
transform: scale(0);
height: 0;
複製程式碼
- 移動
translateX
,translateY
transform: translateX(-99999px);
height: 0
複製程式碼
- 旋轉
rotate
transform: rotateY(90deg);
複製程式碼
3.設定其大小為0
- 寬高為0,字型大小為0:
height: 0;
width: 0;
font-size: 0;
複製程式碼
- 寬高為0,超出隱藏:
height: 0;
width: 0;
overflow: hidden;
複製程式碼
4.設定透明度為0
opacity: 0;
複製程式碼
5.visibility
屬性
visibility: hidden;
複製程式碼
6.層級覆蓋,z-index
屬性
position: relative;
z-index: -999;
複製程式碼
再設定一個層級較高的元素覆蓋在此元素上。
7.clip-path 裁剪
clip-path: polygon(0 0, 0 0, 0 0, 0 0);
複製程式碼
語義上的隱藏
aria-hidden 屬性
讀屏軟體不可讀,佔據空間,可見。
<div aria-hidden="true">
</div>
複製程式碼
11. let、const、var 的區別有哪些?
檢視解析
宣告方式 | 變數提升 | 暫時性死區 | 重複宣告 | 塊作用域有效 | 初始值 | 重新賦值 |
---|---|---|---|---|---|---|
var | 會 | 不存在 | 允許 | 不是 | 非必須 | 允許 |
let | 不會 | 存在 | 不允許 | 是 | 非必須 | 允許 |
const | 不會 | 存在 | 不允許 | 是 | 必須 | 不允許 |
1.let/const 定義的變數不會出現變數提升,而 var 定義的變數會提升。
2.相同作用域中,let 和 const 不允許重複宣告,var 允許重複宣告。
3.const 宣告變數時必須設定初始值
4.const 宣告一個只讀的常量,這個常量不可改變。
這裡有一個非常重要的點即是:在JS中,複雜資料型別,儲存在棧中的是堆記憶體的地址,存在棧中的這個地址是不變的,但是存在堆中的值是可以變得。有沒有相當常量指標/指標常量~
一圖勝萬言,如下圖所示,不變的是棧記憶體中 a 儲存的 20,和 b 中儲存的 0x0012ff21(瞎編的一個數字)。而 {age: 18, star: 200} 是可變的。
12. 說一說你對JS執行上下文棧和作用域鏈的理解?
檢視解析
在開始說明JS上下文棧和作用域之前,我們先說明下JS上下文以及作用域的概念。
JS執行上下文
執行上下文就是當前 JavaScript 程式碼被解析和執行時所在環境的抽象概念, JavaScript 中執行任何的程式碼都是在執行上下文中執行。
執行上下文型別分為:
- 全域性執行上下文
- 函式執行上下文
執行上下文建立過程中,需要做以下幾件事:
- 建立變數物件:首先初始化函式的引數arguments,提升函式宣告和變數宣告。
- 建立作用域鏈(Scope Chain):在執行期上下文的建立階段,作用域鏈是在變數物件之後建立的。
- 確定this的值,即 ResolveThisBinding
作用域
作用域負責收集和維護由所有宣告的識別符號(變數)組成的一系列查詢,並實施一套非常嚴格的規則,確定當前執行的程式碼對這些識別符號的訪問許可權。—— 摘錄自《你不知道的JavaScript》(上卷)
作用域有兩種工作模型:詞法作用域和動態作用域,JS採用的是詞法作用域工作模型,詞法作用域意味著作用域是由書寫程式碼時變數和函式宣告的位置決定的。(with
和 eval
能夠修改詞法作用域,但是不推薦使用,對此不做特別說明)
作用域分為:
- 全域性作用域
- 函式作用域
- 塊級作用域
JS執行上下文棧(後面簡稱執行棧)
執行棧,也叫做呼叫棧,具有 LIFO (後進先出) 結構,用於儲存在程式碼執行期間建立的所有執行上下文。
規則如下:
- 首次執行JavaScript程式碼的時候,會建立一個全域性執行的上下文並Push到當前的執行棧中,每當發生函式呼叫,引擎都會為該函式建立一個新的函式執行上下文並Push當前執行棧的棧頂。
- 當棧頂的函式執行完成後,其對應的函式執行上下文將會從執行棧中Pop出,上下文的控制權將移動到當前執行棧的下一個執行上下文。
以一段程式碼具體說明:
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作用域,全域性作用域]
13. 防抖函式的作用是什麼?請實現一個防抖函式
檢視解析
> 防抖函式的作用
防抖函式的作用就是控制函式在一定時間內的執行次數。防抖意味著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 時,表示函式在每個等待時延的結束被呼叫。
防抖的應用場景
- 搜尋框輸入查詢,如果使用者一直在輸入中,沒有必要不停地呼叫去請求服務端介面,等使用者停止輸入的時候,再呼叫,設定一個合適的時間間隔,有效減輕服務端壓力。
- 表單驗證
- 按鈕提交事件。
- 瀏覽器視窗縮放,resize事件(如視窗停止改變大小之後重新計算佈局)等。
14. 節流函式的作用是什麼?有哪些應用場景,請實現一個節流函式
檢視解析
> 節流函式的作用
節流函式的作用是規定一個單位時間,在這個單位時間內最多隻能觸發一次函式執行,如果這個單位時間內多次觸發函式,只能有一次生效。
節流函式實現
禁用第一次首先執行,傳遞 {leading: false}
;想禁用最後一次執行,傳遞 {trailing: false}
節流的應用場景
- 按鈕點選事件
- 拖拽事件
- onScoll
- 計算滑鼠移動的距離(mousemove)
15. 什麼是閉包?閉包的作用是什麼?
檢視解析
閉包的定義
《JavaScript高階程式設計》:
閉包是指有權訪問另一個函式作用域中的變數的函式
《JavaScript權威指南》:
從技術的角度講,所有的JavaScript函式都是閉包:它們都是物件,它們都關聯到作用域鏈。
《你不知道的JavaScript》
當函式可以記住並訪問所在的詞法作用域時,就產生了閉包,即使函式是在當前詞法作用域之外執行。
建立一個閉包
閉包使得函式可以繼續訪問定義時的詞法作用域。拜 fn 所賜,在 foo() 執行後,foo 內部作用域不會被銷燬。
閉包的作用
-
能夠訪問函式定義時所在的詞法作用域(阻止其被回收)。
-
私有化變數
- 模擬塊級作用域
- 建立模組
模組模式具有兩個必備的條件(來自《你不知道的JavaScript》)
- 必須有外部的封閉函式,該函式必須至少被呼叫一次(每次呼叫都會建立一個新的模組例項)
- 封閉函式必須返回至少一個內部函式,這樣內部函式才能在私有作用域中形成閉包,並且可以訪問或者修改私有的狀態。
16. 實現 Promise.all 方法
檢視解析
在實現 Promise.all 方法之前,我們首先要知道 Promise.all 的功能和特點,因為在清楚了 Promise.all 功能和特點的情況下,我們才能進一步去寫實現。
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)只有p1、p2、p3的狀態都變成 fulfilled
,p的狀態才會變成 fulfilled
,此時p1、p2、p3的返回值組成一個陣列,傳遞給p的回撥函式。
(2)只要p1、p2、p3之中有一個被 rejected
,p的狀態就變成 rejected
,此時第一個被reject的例項的返回值,會傳遞給p的回撥函式。
Promise.all 的特點
Promise.all 的返回值是一個 promise 例項
- 如果傳入的引數為空的可迭代物件,
Promise.all
會 同步 返回一個已完成狀態的promise
- 如果傳入的引數中不包含任何 promise,
Promise.all
會 非同步 返回一個已完成狀態的promise
- 其它情況下,
Promise.all
返回一個 處理中(pending) 狀態的promise
.
Promise.all 返回的 promise 的狀態
- 如果傳入的引數中的 promise 都變成完成狀態,
Promise.all
返回的promise
非同步地變為完成。 - 如果傳入的引數中,有一個
promise
失敗,Promise.all
非同步地將失敗的那個結果給失敗狀態的回撥函式,而不管其它promise
是否完成 - 在任何情況下,
Promise.all
返回的promise
的完成狀態的結果都是一個陣列
Promise.all 實現
17. 請實現一個 flattenDeep 函式,把巢狀的陣列扁平化
例如:
flattenDeep([1, [2, [3, [4]], 5]]); //[1, 2, 3, 4, 5]
複製程式碼
檢視解析
> 利用 Array.prototype.flat
ES6 為陣列例項新增了 flat
方法,用於將巢狀的陣列“拉平”,變成一維的陣列。該方法返回一個新陣列,對原陣列沒有影響。
flat
預設只會 “拉平” 一層,如果想要 “拉平” 多層的巢狀陣列,需要給 flat
傳遞一個整數,表示想要拉平的層數。
當傳遞的整數大於陣列巢狀的層數時,會將陣列拉平為一維陣列,JS能表示的最大數字為 Math.pow(2, 53) - 1
,因此我們可以這樣定義 flattenDeep
函式
利用 reduce 和 concat
使用 stack 無限反巢狀多層巢狀陣列
18. 請實現一個 uniq 函式,實現陣列去重
例如:
uniq([1, 2, 3, 5, 3, 2]);//[1, 2, 3, 5]
複製程式碼
檢視解析
法1: 利用ES6新增資料型別
Set
Set
類似於陣列,但是成員的值都是唯一的,沒有重複的值。
法2: 利用
indexOf
法3: 利用
includes
法4:利用
reduce
法5:利用
Map
19. 可迭代物件有哪些特點
檢視解析
ES6 規定,預設的 `Iterator` 介面部署在資料結構的 `Symbol.iterator` 屬性,換個角度,也可以認為,一個資料結構只要具有 `Symbol.iterator` 屬性(`Symbol.iterator` 方法對應的是遍歷器生成函式,返回的是一個遍歷器物件),那麼就可以其認為是可迭代的。
可迭代物件的特點
- 具有
Symbol.iterator
屬性,Symbol.iterator()
返回的是一個遍歷器物件 - 可以使用
for ... of
進行迴圈 - 通過被
Array.from
轉換為陣列
原生具有 Iterator
介面的資料結構:
- Array
- Map
- Set
- String
- TypedArray
- 函式的 arguments 物件
- NodeList 物件
20. JSONP 的原理是什麼?
檢視解析
儘管瀏覽器有同源策略,但是 <script>
標籤的 src
屬性不會被同源策略所約束,可以獲取任意伺服器上的指令碼並執行。jsonp
通過插入 script
標籤的方式來實現跨域,引數只能通過 url
傳入,僅能支援 get
請求。
實現原理:
- Step1: 建立 callback 方法
- Step2: 插入 script 標籤
- Step3: 後臺接受到請求,解析前端傳過去的 callback 方法,返回該方法的呼叫,並且資料作為引數傳入該方法
- Step4: 前端執行服務端返回的方法呼叫
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…