關於前端174道 JavaScript知識點匯總(一)

echa攻城獅 發佈 2020-03-31T02:01:41+00:00

最近在整理JavaScript 的時候發現遇到了很多面試中常見的面試題,本部分主要是作者在 Github 等各大論壇收錄的 JavaScript 相關知識和一些相關面試題時所做的筆記,分享這份總結給大家,對大家對 JavaScript 的可以來一次全方位的檢漏和排查,感謝原作者



最近在整理 JavaScript 的時候發現遇到了很多面試中常見的面試題,本部分主要是作者在 Github 等各大論壇收錄的 JavaScript 相關知識和一些相關面試題時所做的筆記,分享這份總結給大家,對大家對 JavaScript 的可以來一次全方位的檢漏和排查,感謝原作者 CavsZhouyou 的付出,原文連結放在文章最下方,如果出現錯誤,希望大家共同指出!

1. 介紹 js 的基本數據類型。

js 一共有六種基本數據類型,分別是 Undefined、Null、Boolean、Number、String,還有在 ES6 中新增的 Symbol 類型,
代表創建後獨一無二且不可變的數據類型,它的出現我認為主要是為了解決可能出現的全局變量衝突的問題。

2. JavaScript 有幾種類型的值?你能畫一下他們的內存圖嗎?

涉及知識點:

  • 棧:原始數據類型(Undefined、Null、Boolean、Number、String)
  • 堆:引用數據類型(對象、數組和函數)
兩種類型的區別是:存儲位置不同。
原始數據類型直接存儲在棧(stack)中的簡單數據段,占據空間小、大小固定,屬於被頻繁使用數據,所以放入棧中存儲。

引用數據類型存儲在堆(heap)中的對象,占據空間大、大小不固定。如果存儲在棧中,將會影響程序運行的性能;引用數據類型在
棧中存儲了指針,該指針指向堆中該實體的起始地址。當解釋器尋找引用值時,會首先檢索其在棧中的地址,取得地址後從堆中獲得實
體。

回答:

js 可以分為兩種類型的值,一種是基本數據類型,一種是複雜數據類型。

基本數據類型....(參考1)

複雜數據類型指的是 Object 類型,所有其他的如 Array、Date 等數據類型都可以理解為 Object 類型的子類。

兩種類型間的主要區別是它們的存儲位置不同,基本數據類型的值直接保存在棧中,而複雜數據類型的值保存在堆中,通過使用在棧中
保存對應的指針來獲取堆中的值。

詳細資料可以參考:《JavaScript 有幾種類型的值?》《JavaScript 有幾種類型的值?能否畫一下它們的內存圖;》

3. 什麼是堆?什麼是棧?它們之間有什麼區別和聯繫?

堆和棧的概念存在於數據結構中和作業系統內存中。

在數據結構中,棧中數據的存取方式為先進後出。而堆是一個優先隊列,是按優先級來進行排序的,優先級可以按照大小來規定。完全
二叉樹是堆的一種實現方式。

在作業系統中,內存被分為棧區和堆區。

棧區內存由編譯器自動分配釋放,存放函數的參數值,局部變量的值等。其操作方式類似於數據結構中的棧。

堆區內存一般由程式設計師分配釋放,若程式設計師不釋放,程序結束時可能由垃圾回收機制回收。

詳細資料可以參考:《什麼是堆?什麼是棧?他們之間有什麼區別和聯繫?》

4. 內部屬性 [[Class]] 是什麼?

所有 typeof 返回值為 "object" 的對象(如數組)都包含一個內部屬性 [[Class]](我們可以把它看作一個內部的分類,而非
傳統的面向對象意義上的類)。這個屬性無法直接訪問,一般通過 Object.prototype.toString(..) 來查看。例如:

Object.prototype.toString.call( [1,2,3] );
// "[object Array]"

Object.prototype.toString.call( /regex-literal/i );
// "[object RegExp]"

5. 介紹 js 有哪些內置對象?

涉及知識點:

全局的對象( global objects )或稱標準內置對象,不要和 "全局對象(global object)" 混淆。這裡說的全局的對象是說在
全局作用域裡的對象。全局作用域中的其他對象可以由用戶的腳本創建或由宿主程序提供。

標準內置對象的分類

(1)值屬性,這些全局屬性返回一個簡單值,這些值沒有自己的屬性和方法。

例如 Infinity、NaN、undefined、null 字面量

(2)函數屬性,全局函數可以直接調用,不需要在調用時指定所屬對象,執行結束後會將結果直接返回給調用者。

例如 eval()、parseFloat()、parseInt() 等

(3)基本對象,基本對象是定義或使用其他對象的基礎。基本對象包括一般對象、函數對象和錯誤對象。

例如 Object、Function、Boolean、Symbol、Error 等

(4)數字和日期對象,用來表示數字、日期和執行數學計算的對象。

例如 Number、Math、Date

(5)字符串,用來表示和操作字符串的對象。

例如 String、RegExp

(6)可索引的集合對象,這些對象表示按照索引值來排序的數據集合,包括數組和類型數組,以及類數組結構的對象。例如 Array

(7)使用鍵的集合對象,這些集合對象在存儲數據時會使用到鍵,支持按照插入順序來疊代元素。

例如 Map、Set、WeakMap、WeakSet

(8)矢量集合,SIMD 矢量集合中的數據會被組織為一個數據序列。

例如 SIMD 等

(9)結構化數據,這些對象用來表示和操作結構化的緩衝區數據,或使用 JSON 編碼的數據。

例如 JSON 等

(10)控制抽象對象

例如 Promise、Generator 等

(11)反射

例如 Reflect、Proxy

(12)國際化,為了支持多語言處理而加入 ECMAScript 的對象。

例如 Intl、Intl.Collator 等

(13)WebAssembly

(14)其他

例如 arguments

回答:

js 中的內置對象主要指的是在程序執行前存在全局作用域裡的由 js 定義的一些全局值屬性、函數和用來實例化其他對象的構造函
數對象。一般我們經常用到的如全局變量值 NaN、undefined,全局函數如 parseInt()、parseFloat() 用來實例化對象的構
造函數如 Date、Object 等,還有提供數學計算的單體內置對象如 Math 對象。

詳細資料可以參考:《標準內置對象的分類》《JS 所有內置對象屬性和方法匯總》

6. undefined 與 undeclared 的區別?

已在作用域中聲明但還沒有賦值的變量,是 undefined 的。相反,還沒有在作用域中聲明過的變量,是 undeclared 的。

對於 undeclared 變量的引用,瀏覽器會報引用錯誤,如 ReferenceError: b is not defined 。但是我們可以使用 typ
eof 的安全防範機制來避免報錯,因為對於 undeclared(或者 not defined )變量,typeof 會返回 "undefined"。

7. null 和 undefined 的區別?

首先 Undefined 和 Null 都是基本數據類型,這兩個基本數據類型分別都只有一個值,就是 undefined 和 null。

undefined 代表的含義是未定義,null 代表的含義是空對象。一般變量聲明了但還沒有定義的時候會返回 undefined,null
主要用於賦值給一些可能會返回對象的變量,作為初始化。

undefined 在 js 中不是一個保留字,這意味著我們可以使用 undefined 來作為一個變量名,這樣的做法是非常危險的,它
會影響我們對 undefined 值的判斷。但是我們可以通過一些方法獲得安全的 undefined 值,比如說 void 0。

當我們對兩種類型使用 typeof 進行判斷的時候,Null 類型化會返回 「object」,這是一個歷史遺留的問題。當我們使用雙等
號對兩種類型的值進行比較時會返回 true,使用三個等號時會返回 false。

詳細資料可以參考:《JavaScript 深入理解之 undefined 與 null》

8. 如何獲取安全的 undefined 值?

因為 undefined 是一個標識符,所以可以被當作變量來使用和賦值,但是這樣會影響 undefined 的正常判斷。

表達式 void ___ 沒有返回值,因此返回結果是 undefined。void 並不改變表達式的結果,只是讓表達式不返回值。

按慣例我們用 void 0 來獲得 undefined。

9. 說幾條寫 JavaScript 的基本規範?

在平常項目開發中,我們遵守一些這樣的基本規範,比如說:

(1)一個函數作用域中所有的變量聲明應該儘量提到函數首部,用一個 var 聲明,不允許出現兩個連續的 var 聲明,聲明時
    如果變量沒有值,應該給該變量賦值對應類型的初始值,便於他人閱讀代碼時,能夠一目了然的知道變量對應的類型值。

(2)代碼中出現地址、時間等字符串時需要使用常量代替。

(3)在進行比較的時候吧,儘量使用'===', '!=='代替'==', '!='。

(4)不要在內置對象的原型上添加方法,如 Array, Date。

(5)switch 語句必須帶有 default 分支。

(6)for 循環必須使用大括號。

(7)if 語句必須使用大括號。

10. JavaScript 原型,原型鏈? 有什麼特點?

在 js 中我們是使用構造函數來新建一個對象的,每一個構造函數的內部都有一個 prototype 屬性值,這個屬性值是一個對
象,這個對象包含了可以由該構造函數的所有實例共享的屬性和方法。當我們使用構造函數新建一個對象後,在這個對象的內部
將包含一個指針,這個指針指向構造函數的 prototype 屬性對應的值,在 ES5 中這個指針被稱為對象的原型。一般來說我們
是不應該能夠獲取到這個值的,但是現在瀏覽器中都實現了 __proto__ 屬性來讓我們訪問這個屬性,但是我們最好不要使用這
個屬性,因為它不是規範中規定的。ES5 中新增了一個 Object.getPrototypeOf() 方法,我們可以通過這個方法來獲取對
象的原型。

當我們訪問一個對象的屬性時,如果這個對象內部不存在這個屬性,那麼它就會去它的原型對象里找這個屬性,這個原型對象又
會有自己的原型,於是就這樣一直找下去,也就是原型鏈的概念。原型鏈的盡頭一般來說都是 Object.prototype 所以這就
是我們新建的對象為什麼能夠使用 toString() 等方法的原因。

特點:

JavaScript 對象是通過引用來傳遞的,我們創建的每個新對象實體中並沒有一份屬於自己的原型副本。當我們修改原型時,與
之相關的對象也會繼承這一改變。

詳細資料可以參考:《JavaScript 深入理解之原型與原型鏈》

11. js 獲取原型的方法?

  • p.proto
  • p.constructor.prototype
  • Object.getPrototypeOf(p)

12. 在 js 中不同進位數字的表示方式

  • 以 0X、0x 開頭的表示為十六進位。
  • 以 0、0O、0o 開頭的表示為八進位。
  • 以 0B、0b 開頭的表示為二進位格式。

13. js 中整數的安全範圍是多少?

安全整數指的是,在這個範圍內的整數轉化為二進位存儲的時候不會出現精度丟失,能夠被「安全」呈現的最大整數是 2^53 - 1,
即9007199254740991,在 ES6 中被定義為 Number.MAX_SAFE_INTEGER。最小整數是-9007199254740991,在 ES6 中
被定義為 Number.MIN_SAFE_INTEGER。

如果某次計算的結果得到了一個超過 JavaScript 數值範圍的值,那麼這個值會被自動轉換為特殊的 Infinity 值。如果某次
計算返回了正或負的 Infinity 值,那麼該值將無法參與下一次的計算。判斷一個數是不是有窮的,可以使用 isFinite 函數
來判斷。

14. typeof NaN 的結果是什麼?

NaN 意指「不是一個數字」(not a number),NaN 是一個「警戒值」(sentinel value,有特殊用途的常規值),用於指出
數字類型中的錯誤情況,即「執行數學運算沒有成功,這是失敗後返回的結果」。

typeof NaN; // "number"

NaN 是一個特殊值,它和自身不相等,是唯一一個非自反(自反,reflexive,即 x === x 不成立)的值。而 NaN != NaN
為 true。

15. isNaN 和 Number.isNaN 函數的區別?

函數 isNaN 接收參數後,會嘗試將這個參數轉換為數值,任何不能被轉換為數值的的值都會返回 true,因此非數字值傳入也會
返回 true ,會影響 NaN 的判斷。

函數 Number.isNaN 會首先判斷傳入參數是否為數字,如果是數字再繼續判斷是否為 NaN ,這種方法對於 NaN 的判斷更為
準確。

16. Array 構造函數只有一個參數值時的表現?

Array 構造函數隻帶一個數字參數的時候,該參數會被作為數組的預設長度(length),而非只充當數組中的一個元素。這樣
創建出來的只是一個空數組,只不過它的 length 屬性被設置成了指定的值。

構造函數 Array(..) 不要求必須帶 new 關鍵字。不帶時,它會被自動補上。

17. 其他值到字符串的轉換規則?

規範的 9.8 節中定義了抽象操作 ToString ,它負責處理非字符串到字符串的強制類型轉換。

(1)Null 和 Undefined 類型 ,null 轉換為 "null",undefined 轉換為 "undefined",

(2)Boolean 類型,true 轉換為 "true",false 轉換為 "false"。

(3)Number 類型的值直接轉換,不過那些極小和極大的數字會使用指數形式。

(4)Symbol 類型的值直接轉換,但是只允許顯式強制類型轉換,使用隱式強制類型轉換會產生錯誤。

(3)對普通對象來說,除非自行定義 toString() 方法,否則會調用 toString()(Object.prototype.toString())
    來返回內部屬性 [[Class]] 的值,如"[object Object]"。如果對象有自己的 toString() 方法,字符串化時就會
    調用該方法並使用其返回值。

18. 其他值到數字值的轉換規則?

有時我們需要將非數字值當作數字來使用,比如數學運算。為此 ES5 規範在 9.3 節定義了抽象操作 ToNumber。

(1)Undefined 類型的值轉換為 NaN。

(2)Null 類型的值轉換為 0。

(3)Boolean 類型的值,true 轉換為 1,false 轉換為 0。

(4)String 類型的值轉換如同使用 Number() 函數進行轉換,如果包含非數字值則轉換為 NaN,空字符串為 0。

(5)Symbol 類型的值不能轉換為數字,會報錯。

(6)對象(包括數組)會首先被轉換為相應的基本類型值,如果返回的是非數字的基本類型值,則再遵循以上規則將其強制轉換為數字。

為了將值轉換為相應的基本類型值,抽象操作 ToPrimitive 會首先(通過內部操作 DefaultValue)檢查該值是否有valueOf() 方法。如果有並且返回基本類型值,就使用該值進行強制類型轉換。如果沒有就使用 toString() 的返回值(如果存在)來進行強制類型轉換。

如果 valueOf() 和 toString() 均不返回基本類型值,會產生 TypeError 錯誤。

19. 其他值到布爾類型的值的轉換規則?

ES5 規範 9.2 節中定義了抽象操作 ToBoolean,列舉了布爾強制類型轉換所有可能出現的結果。

以下這些是假值:
• undefined
• null
• false
• +0、-0 和 NaN
• ""

假值的布爾強制類型轉換結果為 false。從邏輯上說,假值列表以外的都應該是真值。

20. {} 和 [] 的 valueOf 和 toString 的結果是什麼?

{} 的 valueOf 結果為 {} ,toString 的結果為 "[object Object]"

[] 的 valueOf 結果為 [] ,toString 的結果為 ""

21. 什麼是假值對象?

瀏覽器在某些特定情況下,在常規 JavaScript 語法基礎上自己創建了一些外來值,這些就是「假值對象」。假值對象看起來和
普通對象並無二致(都有屬性,等等),但將它們強制類型轉換為布爾值時結果為 false 最常見的例子是 document.all,它
是一個類數組對象,包含了頁面上的所有元素,由 DOM(而不是 JavaScript 引擎)提供給 JavaScript 程序使用。

22. ~ 操作符的作用?

~ 返回 2 的補碼,並且 ~ 會將數字轉換為 32 位整數,因此我們可以使用 ~ 來進行取整操作。

~x 大致等同於 -(x+1)。

23. 解析字符串中的數字和將字符串強制類型轉換為數字的返回結果都是數字,它們之間的區別是什麼?

解析允許字符串(如 parseInt() )中含有非數字字符,解析按從左到右的順序,如果遇到非數字字符就停止。而轉換(如 Nu
mber ())不允許出現非數字字符,否則會失敗並返回 NaN。

24. + 操作符什麼時候用於字符串的拼接?

根據 ES5 規範 11.6.1 節,如果某個操作數是字符串或者能夠通過以下步驟轉換為字符串的話,+ 將進行拼接操作。如果其
中一個操作數是對象(包括數組),則首先對其調用 ToPrimitive 抽象操作,該抽象操作再調用 [[DefaultValue]],以
數字作為上下文。如果不能轉換為字符串,則會將其轉換為數字類型來進行計算。

簡單來說就是,如果 + 的其中一個操作數是字符串(或者通過以上步驟最終得到字符串),則執行字符串拼接,否則執行數字
加法。

那麼對於除了加法的運算符來說,只要其中一方是數字,那麼另一方就會被轉為數字。

25. 什麼情況下會發生布爾值的隱式強制類型轉換?

(1) if (..) 語句中的條件判斷表達式。
(2) for ( .. ; .. ; .. ) 語句中的條件判斷表達式(第二個)。
(3) while (..) 和 do..while(..) 循環中的條件判斷表達式。
(4) ? : 中的條件判斷表達式。
(5) 邏輯運算符 ||(邏輯或)和 &&(邏輯與)左邊的操作數(作為條件判斷表達式)。

26. || 和 && 操作符的返回值?

|| 和 && 首先會對第一個操作數執行條件判斷,如果其不是布爾值就先進行 ToBoolean 強制類型轉換,然後再執行條件
判斷。

對於 || 來說,如果條件判斷結果為 true 就返回第一個操作數的值,如果為 false 就返回第二個操作數的值。

&& 則相反,如果條件判斷結果為 true 就返回第二個操作數的值,如果為 false 就返回第一個操作數的值。

|| 和 && 返回它們其中一個操作數的值,而非條件判斷的結果

27. Symbol 值的強制類型轉換?

ES6 允許從符號到字符串的顯式強制類型轉換,然而隱式強制類型轉換會產生錯誤。

Symbol 值不能夠被強制類型轉換為數字(顯式和隱式都會產生錯誤),但可以被強制類型轉換為布爾值(顯式和隱式結果
都是 true )。

28. == 操作符的強制類型轉換規則?

(1)字符串和數字之間的相等比較,將字符串轉換為數字之後再進行比較。

(2)其他類型和布爾類型之間的相等比較,先將布爾值轉換為數字後,再應用其他規則進行比較。

(3)null 和 undefined 之間的相等比較,結果為真。其他值和它們進行比較都返回假值。

(4)對象和非對象之間的相等比較,對象先調用 ToPrimitive 抽象操作後,再進行比較。

(5)如果一個操作值為 NaN ,則相等比較返回 false( NaN 本身也不等於 NaN )。

(6)如果兩個操作值都是對象,則比較它們是不是指向同一個對象。如果兩個操作數都指向同一個對象,則相等操作符返回 true,否則,返回 false。

詳細資料可以參考:《JavaScript 字符串間的比較》

29. 如何將字符串轉化為數字,例如 '12.3b'?

(1)使用 Number() 方法,前提是所包含的字符串不包含不合法字符。

(2)使用 parseInt() 方法,parseInt() 函數可解析一個字符串,並返回一個整數。還可以設置要解析的數字的基數。當基數的值為 0,或沒有設置該參數時,parseInt() 會根據 string 來判斷數字的基數。

(3)使用 parseFloat() 方法,該函數解析一個字符串參數並返回一個浮點數。

(4)使用 + 操作符的隱式轉換。

詳細資料可以參考:《詳解 JS 中 Number()、parseInt() 和 parseFloat() 的區別》

30. 如何將浮點數點左邊的數每三位添加一個逗號,如 12000000.11 轉化為『12,000,000.11』?

function format(number) {
  return number && number.replace(/(?!^)(?=(\d{3})+\.)/g, ",");
}

31. 常用正則表達式

// (1)匹配 16 進位顏色值
var regex = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g;

// (2)匹配日期,如 yyyy-mm-dd 格式
var regex = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;

// (3)匹配 qq 號
var regex = /^[1-9][0-9]{4,10}$/g;

// (4)手機號碼正則
var regex = /^1[34578]\d{9}$/g;

// (5)用戶名正則
var regex = /^[a-zA-Z\$][a-zA-Z0-9_\$]{4,16}$/;

詳細資料可以參考:《前端表單驗證常用的 15 個 JS 正則表達式》《JS 常用正則匯總》

32. 生成隨機數的各種方法?

《JS - 生成隨機數的方法匯總(不同範圍、類型的隨機數)》

33. 如何實現數組的隨機排序?

// (1)使用數組 sort 方法對數組元素隨機排序,讓 Math.random() 出來的數與 0.5 比較,如果大於就返回 1 交換位置,如果小於就返回 -1,不交換位置。

function randomSort(a, b) {
  return Math.random() > 0.5 ? -1 : 1;
}

//  缺點:每個元素被派到新數組的位置不是隨機的,原因是 sort() 方法是依次比較的。

// (2)隨機從原數組抽取一個元素,加入到新數組

function randomSort(arr) {
  var result = [];

  while (arr.length > 0) {
    var randomIndex = Math.floor(Math.random() * arr.length);
    result.push(arr[randomIndex]);
    arr.splice(randomIndex, 1);
  }

  return result;
}

// (3)隨機交換數組內的元素(洗牌算法類似)

function randomSort(arr) {
  var index,
    randomIndex,
    temp,
    len = arr.length;

  for (index = 0; index < len; index++) {
    randomIndex = Math.floor(Math.random() * (len - index)) + index;

    temp = arr[index];
    arr[index] = arr[randomIndex];
    arr[randomIndex] = temp;
  }

  return arr;
}

// es6
function randomSort(array) {
  let length = array.length;

  if (!Array.isArray(array) || length <= 1) return;

  for (let index = 0; index < length - 1; index++) {
    let randomIndex = Math.floor(Math.random() * (length - index)) + index;

    [array[index], array[randomIndex]] = [array[randomIndex], array[index]];
  }

  return array;
}

詳細資料可以參考:《Fisher and Yates 的原始版》《javascript 實現數組隨機排序?》《JavaScript 學習筆記:數組隨機排序》

34. javascript 創建對象的幾種方式?

我們一般使用字面量的形式直接創建對象,但是這種創建方式對於創建大量相似對象的時候,會產生大量的重複代碼。但 js
和一般的面向對象的語言不同,在 ES6 之前它沒有類的概念。但是我們可以使用函數來進行模擬,從而產生出可復用的對象
創建方式,我了解到的方式有這麼幾種:

(1)第一種是工廠模式,工廠模式的主要工作原理是用函數來封裝創建對象的細節,從而通過調用函數來達到復用的目的。但是它有一個很大的問題就是創建出來的對象無法和某個類型聯繫起來,它只是簡單的封裝了復用代碼,而沒有建立起對象和類型間的關係。

(2)第二種是構造函數模式。js 中每一個函數都可以作為構造函數,只要一個函數是通過 new 來調用的,那麼我們就可以把它稱為構造函數。執行構造函數首先會創建一個對象,然後將對象的原型指向構造函數的 prototype 屬性,然後將執行上下文中的 this 指向這個對象,最後再執行整個函數,如果返回值不是對象,則返回新建的對象。因為 this 的值指向了新建的對象,因此我們可以使用 this 給對象賦值。構造函數模式相對於工廠模式的優點是,所創建的對象和構造函數建立起了聯繫,因此我們可以通過原型來識別對象的類型。但是構造函數存在一個缺點就是,造成了不必要的函數對象的創建,因為在 js 中函數也是一個對象,因此如果對象屬性中如果包含函數的話,那麼每次我們都會新建一個函數對象,浪費了不必要的內存空間,因為函數是所有的實例都可以通用的。

(3)第三種模式是原型模式,因為每一個函數都有一個 prototype 屬性,這個屬性是一個對象,它包含了通過構造函數創建的所有實例都能共享的屬性和方法。因此我們可以使用原型對象來添加公用屬性和方法,從而實現代碼的復用。這種方式相對於構造函數模式來說,解決了函數對象的復用問題。但是這種模式也存在一些問題,一個是沒有辦法通過傳入參數來初始化值,另一個是如果存在一個引用類型如 Array 這樣的值,那麼所有的實例將共享一個對象,一個實例對引用類型值的改變會影響所有的實例。

(4)第四種模式是組合使用構造函數模式和原型模式,這是創建自定義類型的最常見方式。因為構造函數模式和原型模式分開使用都存在一些問題,因此我們可以組合使用這兩種模式,通過構造函數來初始化對象的屬性,通過原型對象來實現函數方法的復用。這種方法很好的解決了兩種模式單獨使用時的缺點,但是有一點不足的就是,因為使用了兩種不同的模式,所以對於代碼的封裝性不夠好。

(5)第五種模式是動態原型模式,這一種模式將原型方法賦值的創建過程移動到了構造函數的內部,通過對屬性是否存在的判斷,可以實現僅在第一次調用函數時對原型對象賦值一次的效果。這一種方式很好地對上面的混合模式進行了封裝。

(6)第六種模式是寄生構造函數模式,這一種模式和工廠模式的實現基本相同,我對這個模式的理解是,它主要是基於一個已有的類型,在實例化時對實例化的對象進行擴展。這樣既不用修改原來的構造函數,也達到了擴展對象的目的。它的一個缺點和工廠模式一樣,無法實現對象的識別。

嗯我目前了解到的就是這麼幾種方式。

詳細資料可以參考:《JavaScript 深入理解之對象創建》

35. JavaScript 繼承的幾種實現方式?

我了解的 js 中實現繼承的幾種方式有:

(1)第一種是以原型鏈的方式來實現繼承,但是這種實現方式存在的缺點是,在包含有引用類型的數據時,會被所有的實例對象所共享,容易造成修改的混亂。還有就是在創建子類型的時候不能向超類型傳遞參數。

(2)第二種方式是使用借用構造函數的方式,這種方式是通過在子類型的函數中調用超類型的構造函數來實現的,這一種方法解決了不能向超類型傳遞參數的缺點,但是它存在的一個問題就是無法實現函數方法的復用,並且超類型原型定義的方法子類型也沒有辦法訪問到。

(3)第三種方式是組合繼承,組合繼承是將原型鏈和借用構造函數組合起來使用的一種方式。通過借用構造函數的方式來實現類型的屬性的繼承,通過將子類型的原型設置為超類型的實例來實現方法的繼承。這種方式解決了上面的兩種模式單獨使用時的問題,但是由於我們是以超類型的實例來作為子類型的原型,所以調用了兩次超類的構造函數,造成了子類型的原型中多了很多不必要的屬性。

(4)第四種方式是原型式繼承,原型式繼承的主要思路就是基於已有的對象來創建新的對象,實現的原理是,向函數中傳入一個對象,然後返回一個以這個對象為原型的對象。這種繼承的思路主要不是為了實現創造一種新的類型,只是對某個對象實現一種簡單繼承,ES5 中定義的 Object.create() 方法就是原型式繼承的實現。缺點與原型鏈方式相同。

(5)第五種方式是寄生式繼承,寄生式繼承的思路是創建一個用於封裝繼承過程的函數,通過傳入一個對象,然後複製一個對象的副本,然後對象進行擴展,最後返回這個對象。這個擴展的過程就可以理解是一種繼承。這種繼承的優點就是對一個簡單對象實現繼承,如果這個對象不是我們的自定義類型時。缺點是沒有辦法實現函數的復用。

(6)第六種方式是寄生式組合繼承,組合繼承的缺點就是使用超類型的實例做為子類型的原型,導致添加了不必要的原型屬性。寄生式組合繼承的方式是使用超類型的原型的副本來作為子類型的原型,這樣就避免了創建不必要的屬性。

詳細資料可以參考:《JavaScript 深入理解之繼承》

36. 寄生式組合繼承的實現?

function Person(name) {
  this.name = name;
}

Person.prototype.sayName = function() {
  console.log("My name is " + this.name + ".");
};

function Student(name, grade) {
  Person.call(this, name);
  this.grade = grade;
}

Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

Student.prototype.sayMyGrade = function() {
  console.log("My grade is " + this.grade + ".");
};

37. Javascript 的作用域鏈?

作用域鏈的作用是保證對執行環境有權訪問的所有變量和函數的有序訪問,通過作用域鏈,我們可以訪問到外層環境的變量和
函數。

作用域鏈的本質上是一個指向變量對象的指針列表。變量對象是一個包含了執行環境中所有變量和函數的對象。作用域鏈的前
端始終都是當前執行上下文的變量對象。全局執行上下文的變量對象(也就是全局對象)始終是作用域鏈的最後一個對象。

當我們查找一個變量時,如果當前執行環境中沒有找到,我們可以沿著作用域鏈向後查找。

作用域鏈的創建過程跟執行上下文的建立有關....

詳細資料可以參考:《JavaScript 深入理解之作用域鏈》

38. 談談 This 對象的理解。

this 是執行上下文中的一個屬性,它指向最後一次調用這個方法的對象。在實際開發中,this 的指向可以通過四種調用模
式來判斷。
  • 1.第一種是函數調用模式,當一個函數不是一個對象的屬性時,直接作為函數來調用時,this 指向全局對象。
  • 2.第二種是方法調用模式,如果一個函數作為一個對象的方法來調用時,this 指向這個對象。
  • 3.第三種是構造器調用模式,如果一個函數用 new 調用時,函數執行前會新創建一個對象,this 指向這個新創建的對象。
  • 4.第四種是 apply 、 call 和 bind 調用模式,這三個方法都可以顯示的指定調用函數的 this 指向。其中 apply 方法接收兩個參數:一個是 this 綁定的對象,一個是參數數組。call 方法接收的參數,第一個是 this 綁定的對象,後面的其餘參數是傳入函數執行的參數。也就是說,在使用 call() 方法時,傳遞給函數的參數必須逐個列舉出來。bind 方法通過傳入一個對象,返回一個 this 綁定了傳入對象的新函數。這個函數的 this 指向除了使用 new 時會被改變,其他情況下都不會改變。
這四種方式,使用構造器調用模式的優先級最高,然後是 apply 、 call 和 bind 調用模式,然後是方法調用模式,然後
是函數調用模式。

《JavaScript 深入理解之 this 詳解》

39. eval 是做什麼的?

它的功能是把對應的字符串解析成 JS 代碼並運行。

應該避免使用 eval,不安全,非常耗性能(2次,一次解析成 js 語句,一次執行)。

詳細資料可以參考:《eval()》

40. 什麼是 DOM 和 BOM?

DOM 指的是文檔對象模型,它指的是把文檔當做一個對象來對待,這個對象主要定義了處理網頁內容的方法和接口。

BOM 指的是瀏覽器對象模型,它指的是把瀏覽器當做一個對象來對待,這個對象主要定義了與瀏覽器進行交互的法和接口。BOM
的核心是 window,而 window 對象具有雙重角色,它既是通過 js 訪問瀏覽器窗口的一個接口,又是一個 Global(全局)
對象。這意味著在網頁中定義的任何對象,變量和函數,都作為全局對象的一個屬性或者方法存在。window 對象含有 locati
on 對象、navigator 對象、screen 對象等子對象,並且 DOM 的最根本的對象 document 對象也是 BOM 的 window 對
象的子對象。

詳細資料可以參考:《DOM, DOCUMENT, BOM, WINDOW 有什麼區別?》《Window 對象》《DOM 與 BOM 分別是什麼,有何關聯?》《JavaScript 學習總結(三)BOM 和 DOM 詳解》

41. 寫一個通用的事件偵聽器函數。

const EventUtils = {
  // 視能力分別使用dom0||dom2||IE方式 來綁定事件
  // 添加事件
  addEvent: function(element, type, handler) {
    if (element.addEventListener) {
      element.addEventListener(type, handler, false);
    } else if (element.attachEvent) {
      element.attachEvent("on" + type, handler);
    } else {
      element["on" + type] = handler;
    }
  },

  // 移除事件
  removeEvent: function(element, type, handler) {
    if (element.removeEventListener) {
      element.removeEventListener(type, handler, false);
    } else if (element.detachEvent) {
      element.detachEvent("on" + type, handler);
    } else {
      element["on" + type] = null;
    }
  },

  // 獲取事件目標
  getTarget: function(event) {
    return event.target || event.srcElement;
  },

  // 獲取 event 對象的引用,取到事件的所有信息,確保隨時能使用 event
  getEvent: function(event) {
    return event || window.event;
  },

  // 阻止事件(主要是事件冒泡,因為 IE 不支持事件捕獲)
  stopPropagation: function(event) {
    if (event.stopPropagation) {
      event.stopPropagation();
    } else {
      event.cancelBubble = true;
    }
  },

  // 取消事件的默認行為
  preventDefault: function(event) {
    if (event.preventDefault) {
      event.preventDefault();
    } else {
      event.returnValue = false;
    }
  }
};

詳細資料可以參考:《JS 事件模型》

42. 事件是什麼?IE 與火狐的事件機制有什麼區別? 如何阻止冒泡?

  • 1.事件是用戶操作網頁時發生的交互動作,比如 click/move, 事件除了用戶觸發的動作外,還可以是文檔加載,窗口滾動和大小調整。事件被封裝成一個 event 對象,包含了該事件發生時的所有相關信息( event 的屬性)以及可以對事件進行的操作( event 的方法)。
  • 2.事件處理機制:IE 支持事件冒泡、Firefox 同時支持兩種事件模型,也就是:事件冒泡和事件捕獲。
  • 3.event.stopPropagation() 或者 ie 下的方法 event.cancelBubble = true;

詳細資料可以參考:《Javascript 事件模型系列(一)事件及事件的三種模型》《Javascript 事件模型:事件捕獲和事件冒泡》

43. 三種事件模型是什麼?

事件是用戶操作網頁時發生的交互動作或者網頁本身的一些操作,現代瀏覽器一共有三種事件模型。

第一種事件模型是最早的 DOM0 級模型,這種模型不會傳播,所以沒有事件流的概念,但是現在有的瀏覽器支持以冒泡的方式實
現,它可以在網頁中直接定義監聽函數,也可以通過 js 屬性來指定監聽函數。這種方式是所有瀏覽器都兼容的。

第二種事件模型是 IE 事件模型,在該事件模型中,一次事件共有兩個過程,事件處理階段,和事件冒泡階段。事件處理階段會首先執行目標元素綁定的監聽事件。然後是事件冒泡階段,冒泡指的是事件從目標元素冒泡到 document,依次檢查經過的節點是否綁定了事件監聽函數,如果有則執行。這種模型通過 attachEvent 來添加監聽函數,可以添加多個監聽函數,會按順序依次執行。

第三種是 DOM2 級事件模型,在該事件模型中,一次事件共有三個過程,第一個過程是事件捕獲階段。捕獲指的是事件從 document 一直向下傳播到目標元素,依次檢查經過的節點是否綁定了事件監聽函數,如果有則執行。後面兩個階段和 IE 事件模型的兩個階段相同。這種事件模型,事件綁定的函數是 addEventListener,其中第三個參數可以指定事件是否在捕獲階段執行。

詳細資料可以參考:《一個 DOM 元素綁定多個事件時,先執行冒泡還是捕獲》

44. 事件委託是什麼?

事件委託本質上是利用了瀏覽器事件冒泡的機制。因為事件在冒泡過程中會上傳到父節點,並且父節點可以通過事件對象獲取到
目標節點,因此可以把子節點的監聽函數定義在父節點上,由父節點的監聽函數統一處理多個子元素的事件,這種方式稱為事件代理。

使用事件代理我們可以不必要為每一個子元素都綁定一個監聽事件,這樣減少了內存上的消耗。並且使用事件代理我們還可以實現事件的動態綁定,比如說新增了一個子節點,我們並不需要單獨地為它添加一個監聽事件,它所發生的事件會交給父元素中的監聽函數來處理。

詳細資料可以參考:《JavaScript 事件委託詳解》

45. ["1", "2", "3"].map(parseInt) 答案是多少?

parseInt() 函數能解析一個字符串,並返回一個整數,需要兩個參數 (val, radix),其中 radix 表示要解析的數字的基數。(該值介於 2 ~ 36 之間,並且字符串中的數字不能大於 radix 才能正確返回數字結果值)。


此處 map 傳了 3 個參數 (element, index, array),默認第三個參數被忽略掉,因此三次傳入的參數分別為 "1-0", "2-1", "3-2"

因為字符串的值不能大於基數,因此後面兩次調用均失敗,返回 NaN ,第一次基數為 0 ,按十進位解析返回 1。

詳細資料可以參考:[《為什麼 ["1", "2", "3"].map(parseInt) 返回 [1,NaN,NaN]?》](https://blog.csdn.net/justjav...

46. 什麼是閉包,為什麼要用它?

閉包是指有權訪問另一個函數作用域中變量的函數,創建閉包的最常見的方式就是在一個函數內創建另一個函數,創建的函數可以
訪問到當前函數的局部變量。

閉包有兩個常用的用途。

閉包的第一個用途是使我們在函數外部能夠訪問到函數內部的變量。通過使用閉包,我們可以通過在外部調用閉包函數,從而在外
部訪問到函數內部的變量,可以使用這種方法來創建私有變量。

函數的另一個用途是使已經運行結束的函數上下文中的變量對象繼續留在內存中,因為閉包函數保留了這個變量對象的引用,所以
這個變量對象不會被回收。

其實閉包的本質就是作用域鏈的一個特殊的應用,只要了解了作用域鏈的創建過程,就能夠理解閉包的實現原理。

詳細資料可以參考:《JavaScript 深入理解之閉包》

47. javascript 代碼中的 "use strict"; 是什麼意思 ? 使用它區別是什麼?

相關知識點:

use strict 是一種 ECMAscript5 添加的(嚴格)運行模式,這種模式使得 Javascript 在更嚴格的條件下運行。

設立"嚴格模式"的目的,主要有以下幾個:
  • 消除 Javascript 語法的一些不合理、不嚴謹之處,減少一些怪異行為;
  • 消除代碼運行的一些不安全之處,保證代碼運行的安全;
  • 提高編譯器效率,增加運行速度;
  • 為未來新版本的 Javascript 做好鋪墊。

區別:

  • 1.禁止使用 with 語句。
  • 2.禁止 this 關鍵字指向全局對象。
  • 3.對象不能有重名的屬性。

回答:

use strict 指的是嚴格運行模式,在這種模式對 js 的使用添加了一些限制。比如說禁止 this 指向全局對象,還有禁止使
用 with 語句等。設立嚴格模式的目的,主要是為了消除代碼使用中的一些不安全的使用方式,也是為了消除 js 語法本身的一
些不合理的地方,以此來減少一些運行時的怪異的行為。同時使用嚴格運行模式也能夠提高編譯的效率,從而提高代碼的運行速度。
我認為嚴格模式代表了 js 一種更合理、更安全、更嚴謹的發展方向。

詳細資料可以參考:《Javascript 嚴格模式詳解》

48. 如何判斷一個對象是否屬於某個類?

第一種方式是使用 instanceof 運算符來判斷構造函數的 prototype 屬性是否出現在對象的原型鏈中的任何位置。

第二種方式可以通過對象的 constructor 屬性來判斷,對象的 constructor 屬性指向該對象的構造函數,但是這種方式不是很安全,因為 constructor 屬性可以被改寫。

第三種方式,如果需要判斷的是某個內置的引用類型的話,可以使用 Object.prototype.toString() 方法來列印對象的
[[Class]] 屬性來進行判斷。

詳細資料可以參考:《js 判斷一個對象是否屬於某一類》

49. instanceof 的作用?

// instanceof 運算符用於判斷構造函數的 prototype 屬性是否出現在對象的原型鏈中的任何位置。
// 實現:

function myInstanceof(left, right) {
  let proto = Object.getPrototypeOf(left), // 獲取對象的原型
    prototype = right.prototype; // 獲取構造函數的 prototype 對象

  // 判斷構造函數的 prototype 對象是否在對象的原型鏈上
  while (true) {
    if (!proto) return false;
    if (proto === prototype) return true;

    proto = Object.getPrototypeOf(proto);
  }
}

詳細資料可以參考:《instanceof》

50. new 操作符具體幹了什麼呢?如何實現?

// (1)首先創建了一個新的空對象
// (2)設置原型,將對象的原型設置為函數的 prototype 對象。
// (3)讓函數的 this 指向這個對象,執行構造函數的代碼(為這個新對象添加屬性)
// (4)判斷函數的返回值類型,如果是值類型,返回創建的對象。如果是引用類型,就返回這個引用類型的對象。

// 實現:

function objectFactory() {
  let newObject = null,
    constructor = Array.prototype.shift.call(arguments),
    result = null;

  // 參數判斷
  if (typeof constructor !== "function") {
    console.error("type error");
    return;
  }

  // 新建一個空對象,對象的原型為構造函數的 prototype 對象
  newObject = Object.create(constructor.prototype);

  // 將 this 指向新建對象,並執行函數
  result = constructor.apply(newObject, arguments);

  // 判斷返回對象
  let flag =
    result && (typeof result === "object" || typeof result === "function");

  // 判斷返回結果
  return flag ? result : newObject;
}

// 使用方法
// objectFactory(構造函數, 初始化參數);

詳細資料可以參考:《new 操作符具體幹了什麼?》《JavaScript 深入之 new 的模擬實現》

推薦

筆者再次牆裂推薦收藏這個倉庫,收錄於CavsZhouyou - 前端面試複習筆記,這個倉庫是原作者校招時的前端複習筆記,主要總結一些比較重要的知識點和前端面試問題,希望對大家有所幫助。

最後如果文章和筆記能帶您一絲幫助或者啟發,請不要吝嗇你的贊和收藏,你的肯定是我前進的最大動力

作者:CavsZhouyou

轉載連結:https://github.com/CavsZhouyou/Front-End-Interview-Notebook

關鍵字: