101道經典JavaScript面試題總結(附答案,建議收藏)

最近在整理 JavaScript 的時候發現遇到了不少面試中常見的面試題,本部分主要是做者在 Github 等各大論壇收錄的 JavaScript 相關知識和一些相關面試題時所作的筆記,分享這份總結給你們,對你們對 JavaScript 的能夠來一次全方位的檢漏和排查。文章末尾有彩蛋不要錯過!javascript

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

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

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

js 能夠分爲兩種類型的值,一種是基本數據類型,一種是複雜數據類型。前端

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

複雜數據類型指的是 Object 類型,全部其餘的如 Array、Date 等數據類型均可以理解爲 Object 類型的子類。java

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

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

堆和棧的概念存在於數據結構中和操做系統內存中。程序員

在數據結構中,棧中數據的存取方式爲先進後出。而堆是一個優先隊列,是按優先級來進行排序的,優先級能夠按照大小來規定。徹底二叉樹是堆的一種實現方式。es6

在操做系統中,內存被分爲棧區和堆區。web

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

堆區內存通常由程序員分配釋放,若程序員不釋放,程序結束時可能由垃圾回收機制回收。

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 有哪些內置對象?

js 中的內置對象主要指的是在程序執行前存在全局做用域裏的由 js 定義的一些全局值屬性、函數和用來實例化其餘對象的構造函數對象。

通常咱們常常用到的如全局變量值 NaN、undefined,全局函數如 parseInt()、parseFloat() 用來實例化對象的構造函數如 Date、Object 等,還有提供數學計算的單體內置對象如 Math 對象。

6. undefined 與 undeclared 的區別?

已在做用域中聲明但尚未賦值的變量,是 undefined 的。相反,尚未在做用域中聲明過的變量,是 undeclared 的。

對於 undeclared 變量的引用,瀏覽器會報引用錯誤,如 ReferenceError: b is not defined 。可是咱們可使用 typeof 的安全防範機制來避免報錯,由於對於 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。

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 對象是經過引用來傳遞的,咱們建立的每一個新對象實體中並無一份屬於本身的原型副本。當咱們修改原型時,與之相關的對象也會繼承這一改變。

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() )中含有非數字字符,解析按從左到右的順序,若是遇到非數字字符就中止。而轉換(如 Number ())不容許出現非數字字符,不然會失敗並返回 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。

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

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

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

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

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

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}$/;

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

// (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;
}

33. javascript 建立對象的幾種方式?

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

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

(2)第二種是構造函數模式。js 中每個函數均可以做爲構造函數,只要一個函數是經過 new 來調用的,那麼咱們就能夠把它稱爲構造函數。執行構造函數首先會建立一個對象,而後將對象的原型指向構造函數的 prototype 屬性,而後將執行上下文中的 this 指向這個對象,最後再執行整個函數,若是返回值不是對象,則返回新建的對象。由於 this 的值指向了新建的對象,所以咱們可使用 this 給對象賦值。構造函數模式相對於工廠模式的優勢是,所建立的對象和構造函數創建起了聯繫,所以咱們能夠經過原型來識別對象的類型。

可是構造函數存在一個缺點就是,形成了沒必要要的函數對象的建立,由於在 js 中函數也是一個對象,所以若是對象屬性中若是包含函數的話,那麼每次咱們都會新建一個函數對象,浪費了沒必要要的內存空間,由於函數是全部的實例均可以通用的。

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

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

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

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

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

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

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

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

(3)第三種方式是組合繼承,組合繼承是將原型鏈和借用構造函數組合起來使用的一種方式。經過借用構造函數的方式來實現類型的屬性的繼承,經過將子類型的原型設置爲超類型的實例來實現方法的繼承。這種方式解決了上面的兩種模式單獨使用時的問題,可是因爲咱們是以超類型的實例來做爲子類型的原型,因此調用了兩次超類的構造函數,形成了子類型的原型中多了不少沒必要要的屬性。

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

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

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

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

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 + ".");
};

36. Javascript 的做用域鏈?

做用域鏈的做用是保證對執行環境有權訪問的全部變量和函數的有序訪問,經過做用域鏈,咱們能夠訪問到外層環境的變量和函數。

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

當咱們查找一個變量時,若是當前執行環境中沒有找到,咱們能夠沿着做用域鏈向後查找。

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

37. 談談 This 對象的理解。

1.第一種是函數調用模式,當一個函數不是一個對象的屬性時,直接做爲函數來調用時,this 指向全局對象。

2.第二種是方法調用模式,若是一個函數做爲一個對象的方法來調用時,this 指向這個對象。

3.第三種是構造器調用模式,若是一個函數用 new 調用時,函數執行前會新建立一個對象,this 指向這個新建立的對象。

4.第四種是 apply 、 call 和 bind 調用模式,這三個方法均可以顯示的指定調用函數的 this 指向。其中 apply 方法接收兩個參數:一個是 this 綁定的對象,一個是參數數組。call 方法接收的參數,第一個是 this 綁定的對象,後面的其他參數是傳入函數執行的參數。也就是說,在使用 call() 方法時,傳遞給函數的參數必須逐個列舉出來。bind 方法經過傳入一個對象,返回一個 this 綁定了傳入對象的新函數。這個函數的 this 指向除了使用 new 時會被改變,其餘狀況下都不會改變。

38. eval 是作什麼的?

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

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

49. 什麼是 DOM 和 BOM?

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

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

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

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;
    }
  }
};

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

1.事件是用戶操做網頁時發生的交互動做,好比 click/move, 事件除了用戶觸發的動做外,還能夠是文檔加載,窗口滾動和大小調整。事件被封裝成一個 event 對象,包含了該事件發生時的全部相關信息( event 的屬性)以及能夠對事件進行的操做( event 的方法)。

2.事件處理機制:IE 支持事件冒泡、Firefox 同時支持兩種事件模型,也就是:事件冒泡和事件捕獲。

3.event.stopPropagation() 或者 ie 下的方法 event.cancelBubble = true;

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

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

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

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

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

43. 事件委託是什麼?

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

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

44. ["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。

45. 什麼是閉包,爲何要用它?

閉包是指有權訪問另外一個函數做用域中變量的函數,建立閉包的最多見的方式就是在一個函數內建立另外一個函數,建立的函數能夠訪問到當前函數的局部變量。

閉包有兩個經常使用的用途。

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

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

其實閉包的本質就是做用域鏈的一個特殊的應用,只要瞭解了做用域鏈的建立過程,就可以理解閉包的實現原理。

46. 使用 Object.defineProperty() 來進行數據劫持有什麼缺點?

有一些對屬性的操做,使用這種方法沒法攔截,好比說經過下標方式修改數組數據或者給對象新增屬性,vue 內部經過重寫函數解決了這個問題。在 Vue3.0 中已經不使用這種方式了,而是經過使用 Proxy 對對象進行代理,從而實現數據劫持。使用 Proxy 的好處是它能夠完美的監聽到任何方式的數據改變,惟一的缺點是兼容性的問題,由於這是 ES6 的語法。

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

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

設立"嚴格模式"的目的,主要有如下幾個:

  • 消除 Javascript 語法的一些不合理、不嚴謹之處,減小一些怪異行爲;
  • 消除代碼運行的一些不安全之處,保證代碼運行的安全;
  • 提升編譯器效率,增長運行速度;
  • 爲將來新版本的 Javascript 作好鋪墊。

區別:

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

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

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

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

第二種方式能夠經過對象的 constructor 屬性來判斷,對象的 constructor 屬性指向該對象的構造函數,可是這種方式不是很安全,由於 constructor 屬性能夠被改寫。

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

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);
  }
}

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(構造函數, 初始化參數);

51. Javascript 中,有一個函數,執行時對象查找時,永遠不會去查找原型,這個函數是?

hasOwnProperty

全部繼承了 Object 的對象都會繼承到 hasOwnProperty 方法。這個方法能夠用來檢測一個對象是否含有特定的自身屬性,和in 運算符不一樣,該方法會忽略掉那些從原型鏈上繼承到的屬性。

52. 對於 JSON 的瞭解?

JSON 是一種基於文本的輕量級的數據交換格式。它能夠被任何的編程語言讀取和做爲數據格式來傳遞。

在項目開發中,咱們使用 JSON 做爲先後端數據交換的方式。在前端咱們經過將一個符合 JSON 格式的數據結構序列化爲 JSON 字符串,而後將它傳遞到後端,後端經過 JSON 格式的字符串解析後生成對應的數據結構,以此來實現先後端數據的一個傳遞。

由於 JSON 的語法是基於 js 的,所以很容易將 JSON 和 js 中的對象弄混,可是咱們應該注意的是 JSON 和 js 中的對象不是一回事,JSON 中對象格式更加嚴格,好比說在 JSON 中屬性值不能爲函數,不能出現 NaN 這樣的屬性值等,所以大多數的 js 對象是不符合 JSON 對象的格式的。

在 js 中提供了兩個函數來實現 js 數據結構和 JSON 格式的轉換處理,一個是 JSON.stringify 函數,經過傳入一個符合 JSON 格式的數據結構,將其轉換爲一個 JSON 字符串。若是傳入的數據結構不符合 JSON 格式,那麼在序列化的時候會對這些值進行對應的特殊處理,使其符合規範。在前端向後端發送數據時,咱們能夠調用這個函數將數據對象轉化爲 JSON 格式的字符串。

另外一個函數 JSON.parse() 函數,這個函數用來將 JSON 格式的字符串轉換爲一個 js 數據結構,若是傳入的字符串不是標準的 JSON 格式的字符串的話,將會拋出錯誤。當咱們從後端接收到 JSON 格式的字符串時,咱們能夠經過這個方法來將其解析爲一個 js 數據結構,以此來進行數據的訪問。

53. [].forEach.call($$(""),function(a){a.style.outline="1px solid #"+(~~(Math.random()(1<<24))).toString(16)}) 能解釋一下這段代碼的意思嗎?

(1)選取頁面全部 DOM 元素。在瀏覽器的控制檯中可使用$$()方法來獲取頁面中相應的元素,這是現代瀏覽器提供的一個命令行 API 至關於 document.querySelectorAll 方法。

(2)循環遍歷 DOM 元素

(3)給元素添加 outline 。因爲渲染的 outline 是不在 CSS 盒模型中的,因此爲元素添加 outline 並不會影響元素的大小和頁面的佈局。

(4)生成隨機顏色函數。Math.random()*(1<<24) 能夠獲得 0~2^24 - 1 之間的隨機數,由於獲得的是一個浮點數,但咱們只須要整數部分,使用取反操做符 ~ 連續兩次取反得到整數部分,而後再用 toString(16) 的方式,轉換爲一個十六進制的字符串。

54. js 延遲加載的方式有哪些?

js 的加載、解析和執行會阻塞頁面的渲染過程,所以咱們但願 js 腳本可以儘量的延遲加載,提升頁面的渲染速度。

我瞭解到的幾種方式是:

第一種方式是咱們通常採用的是將 js 腳本放在文檔的底部,來使 js 腳本儘量的在最後來加載執行。

第二種方式是給 js 腳本添加 defer 屬性,這個屬性會讓腳本的加載與文檔的解析同步解析,而後在文檔解析完成後再執行這個腳本文件,這樣的話就能使頁面的渲染不被阻塞。多個設置了 defer 屬性的腳本按規範來講最後是順序執行的,可是在一些瀏覽器中可能不是這樣。

第三種方式是給 js 腳本添加 async 屬性,這個屬性會使腳本異步加載,不會阻塞頁面的解析過程,可是當腳本加載完成後當即執行 js 腳本,這個時候若是文檔沒有解析完成的話一樣會阻塞。多個 async 屬性的腳本的執行順序是不可預測的,通常不會按照代碼的順序依次執行。

第四種方式是動態建立 DOM 標籤的方式,咱們能夠對文檔的加載事件進行監聽,當文檔加載完成後再動態的建立 script 標籤來引入 js 腳本。

55. Ajax 是什麼? 如何建立一個 Ajax?

我對 ajax 的理解是,它是一種異步通訊的方法,經過直接由 js 腳本向服務器發起 http 通訊,而後根據服務器返回的數據,更新網頁的相應部分,而不用刷新整個頁面的一種方法。

建立一個 ajax 有這樣幾個步驟

首先是建立一個 XMLHttpRequest 對象。

而後在這個對象上使用 open 方法建立一個 http 請求,open 方法所須要的參數是請求的方法、請求的地址、是否異步和用戶的認證信息。

在發起請求前,咱們能夠爲這個對象添加一些信息和監聽函數。好比說咱們能夠經過setRequestHeader 方法來爲請求添加頭信息。咱們還能夠爲這個對象添加一個狀態監聽函數。一個 XMLHttpRequest 對象一共有 5 個狀態,當它的狀態變化時會觸發onreadystatechange 事件,咱們能夠經過設置監聽函數,來處理請求成功後的結果。當對象的 readyState 變爲 4 的時候,表明服務器返回的數據接收完成,這個時候咱們能夠經過判斷請求的狀態,若是狀態是 2xx 或者 304 的話則表明返回正常。這個時候咱們就能夠經過 response 中的數據來對頁面進行更新了。

當對象的屬性和監聽函數設置完成後,最後咱們調用 sent 方法來向服務器發起請求,能夠傳入參數做爲發送的數據體。

56. 談一談瀏覽器的緩存機制?

瀏覽器的緩存機制指的是經過在一段時間內保留已接收到的 web 資源的一個副本,若是在資源的有效時間內,發起了對這個資源的再一次請求,那麼瀏覽器會直接使用緩存的副本,而不是向服務器發起請求。使用 web 緩存能夠有效地提升頁面的打開速度,減小沒必要要的網絡帶寬的消耗。

web 資源的緩存策略通常由服務器來指定,能夠分爲兩種,分別是強緩存策略和協商緩存策略。

使用強緩存策略時,若是緩存資源有效,則直接使用緩存資源,沒必要再向服務器發起請求。強緩存策略能夠經過兩種方式來設置,分別是 http 頭信息中的 Expires 屬性和 Cache-Control 屬性。

服務器經過在響應頭中添加 Expires 屬性,來指定資源的過時時間。在過時時間之內,該資源能夠被緩存使用,沒必要再向服務器發送請求。這個時間是一個絕對時間,它是服務器的時間,所以可能存在這樣的問題,就是客戶端的時間和服務器端的時間不一致,或者用戶能夠對客戶端時間進行修改的狀況,這樣就可能會影響緩存命中的結果。

Expires 是 http1.0 中的方式,由於它的一些缺點,在 http 1.1 中提出了一個新的頭部屬性就是 Cache-Control 屬性,它提供了對資源的緩存的更精確的控制。它有不少不一樣的值,經常使用的好比咱們能夠經過設置 max-age 來指定資源可以被緩存的時間的大小,這是一個相對的時間,它會根據這個時間的大小和資源第一次請求時的時間來計算出資源過時的時間,所以相對於 Expires來講,這種方式更加有效一些。

經常使用的還有好比 private ,用來規定資源只能被客戶端緩存,不可以代理服務器所緩存。還有如 no-store ,用來指定資源不可以被緩存,no-cache 表明該資源可以被緩存,可是當即失效,每次都須要向服務器發起請求。

通常來講只須要設置其中一種方式就能夠實現強緩存策略,當兩種方式一塊兒使用時,Cache-Control 的優先級要高於 Expires 。

使用協商緩存策略時,會先向服務器發送一個請求,若是資源沒有發生修改,則返回一個 304 狀態,讓瀏覽器使用本地的緩存副本。若是資源發生了修改,則返回修改後的資源。協商緩存也能夠經過兩種方式來設置,分別是 http 頭信息中的 Etag 和 Last-Modified 屬性。

服務器經過在響應頭中添加 Last-Modified 屬性來指出資源最後一次修改的時間,當瀏覽器下一次發起請求時,會在請求頭中添加一個 If-Modified-Since 的屬性,屬性值爲上一次資源返回時的 Last-Modified 的值。當請求發送到服務器後服務器會經過這個屬性來和資源的最後一次的修改時間來進行比較,以此來判斷資源是否作了修改。若是資源沒有修改,那麼返回 304 狀態,讓客戶端使用本地的緩存。若是資源已經被修改了,則返回修改後的資源。使用這種方法有一個缺點,就是 Last-Modified 標註的最後修改時間只能精確到秒級,若是某些文件在1秒鐘之內,被修改屢次的話,那麼文件已將改變了可是 Last-Modified 卻沒有改變,這樣會形成緩存命中的不許確。

由於 Last-Modified 的這種可能發生的不許確性,http 中提供了另一種方式,那就是 Etag 屬性。服務器在返回資源的時候,在頭信息中添加了 Etag 屬性,這個屬性是資源生成的惟一標識符,當資源發生改變的時候,這個值也會發生改變。在下一次資源請求時,瀏覽器會在請求頭中添加一個 If-None-Match 屬性,這個屬性的值就是上次返回的資源的 Etag 的值。服務接收到請求後會根據這個值來和資源當前的 Etag 的值來進行比較,以此來判斷資源是否發生改變,是否須要返回資源。經過這種方式,比 Last-Modified 的方式更加精確。

當 Last-Modified 和 Etag 屬性同時出現的時候,Etag 的優先級更高。使用協商緩存的時候,服務器須要考慮負載平衡的問題,所以多個服務器上資源的 Last-Modified 應該保持一致,由於每一個服務器上 Etag 的值都不同,所以在考慮負載平衡時,最好不要設置 Etag 屬性。

強緩存策略和協商緩存策略在緩存命中時都會直接使用本地的緩存副本,區別只在於協商緩存會向服務器發送一次請求。它們緩存不命中時,都會向服務器發送請求來獲取資源。在實際的緩存機制中,強緩存策略和協商緩存策略是一塊兒合做使用的。瀏覽器首先會根據請求的信息判斷,強緩存是否命中,若是命中則直接使用資源。若是不命中則根據頭信息向服務器發起請求,使用協商緩存,若是協商緩存命中的話,則服務器不返回資源,瀏覽器直接使用本地資源的副本,若是協商緩存不命中,則瀏覽器返回最新的資源給瀏覽器。

57. Ajax 解決瀏覽器緩存問題?

1.在 ajax 發送請求前加上 anyAjaxObj.setRequestHeader("If-Modified-Since","0")。
2.在 ajax 發送請求前加上 anyAjaxObj.setRequestHeader("Cache-Control","no-cache")。
3.在 URL 後面加上一個隨機數: "fresh=" + Math.random();。
4.在 URL 後面加上時間戳:"nowtime=" + new Date().getTime();。
5.若是是使用 jQuery,直接這樣就能夠了$.ajaxSetup({cache:false})。這樣頁面的全部 ajax 都會執行這條語句就是不須要保存緩存記錄。

58. 同步和異步的區別?

同步指的是當一個進程在執行某個請求的時候,若是這個請求須要等待一段時間才能返回,那麼這個進程會一直等待下去,直到消息返
回爲止再繼續向下執行。

異步指的是當一個進程在執行某個請求的時候,若是這個請求須要等待一段時間才能返回,這個時候進程會繼續往下執行,不會阻塞等
待消息的返回,當消息返回時系統再通知進程進行處理。

59. 什麼是瀏覽器的同源政策?

我對瀏覽器的同源政策的理解是,一個域下的 js 腳本在未經容許的狀況下,不可以訪問另外一個域的內容。這裏的同源的指的是兩個域的協議、域名、端口號必須相同,不然則不屬於同一個域。

同源政策主要限制了三個方面

第一個是當前域下的 js 腳本不可以訪問其餘域下的 cookie、localStorage 和 indexDB。

第二個是當前域下的 js 腳本不可以操做訪問操做其餘域下的 DOM。

第三個是當前域下 ajax 沒法發送跨域請求。

同源政策的目的主要是爲了保證用戶的信息安全,它只是對 js 腳本的一種限制,並非對瀏覽器的限制,對於通常的 img、或者script 腳本請求都不會有跨域的限制,這是由於這些操做都不會經過響應結果來進行可能出現安全問題的操做。

60. 如何解決跨域問題?

解決跨域的方法咱們能夠根據咱們想要實現的目的來劃分。

首先咱們若是隻是想要實現主域名下的不一樣子域名的跨域操做,咱們可使用設置 document.domain 來解決。

(1)將 document.domain 設置爲主域名,來實現相同子域名的跨域操做,這個時候主域名下的 cookie 就可以被子域名所訪問。同時若是文檔中含有主域名相同,子域名不一樣的 iframe 的話,咱們也能夠對這個 iframe 進行操做。

若是是想要解決不一樣跨域窗口間的通訊問題,好比說一個頁面想要和頁面的中的不一樣源的 iframe 進行通訊的問題,咱們可使用 location.hash 或者 window.name 或者 postMessage 來解決。

(2)使用 location.hash 的方法,咱們能夠在主頁面動態的修改 iframe 窗口的 hash 值,而後在 iframe 窗口裏實現監聽函數來實現這樣一個單向的通訊。由於在 iframe 是沒有辦法訪問到不一樣源的父級窗口的,因此咱們不能直接修改父級窗口的 hash 值來實現通訊,咱們能夠在 iframe 中再加入一個 iframe ,這個 iframe 的內容是和父級頁面同源的,因此咱們能夠 window.parent.parent 來修改最頂級頁面的 src,以此來實現雙向通訊。

(3)使用 window.name 的方法,主要是基於同一個窗口中設置了 window.name 後不一樣源的頁面也能夠訪問,因此不一樣源的子頁面能夠首先在 window.name 中寫入數據,而後跳轉到一個和父級同源的頁面。這個時候級頁面就能夠訪問同源的子頁面中 window.name 中的數據了,這種方式的好處是能夠傳輸的數據量大。

(4)使用 postMessage 來解決的方法,這是一個 h5 中新增的一個 api。經過它咱們能夠實現多窗口間的信息傳遞,經過獲取到指定窗口的引用,而後調用 postMessage 來發送信息,在窗口中咱們經過對 message 信息的監聽來接收信息,以此來實現不一樣源間的信息交換。

若是是像解決 ajax 沒法提交跨域請求的問題,咱們可使用 jsonp、cors、websocket 協議、服務器代理來解決問題。

(5)使用 jsonp 來實現跨域請求,它的主要原理是經過動態構建 script 標籤來實現跨域請求,由於瀏覽器對 script 標籤的引入沒有跨域的訪問限制 。經過在請求的 url 後指定一個回調函數,而後服務器在返回數據的時候,構建一個 json 數據的包裝,這個包裝就是回調函數,而後返回給前端,前端接收到數據後,由於請求的是腳本文件,因此會直接執行,這樣咱們先前定義好的回調函數就能夠被調用,從而實現了跨域請求的處理。這種方式只能用於 get 請求。

(6)使用 CORS 的方式,CORS 是一個 W3C 標準,全稱是"跨域資源共享"。CORS 須要瀏覽器和服務器同時支持。目前,全部瀏覽器都支持該功能,所以咱們只須要在服務器端配置就行。瀏覽器將 CORS 請求分紅兩類:簡單請求和非簡單請求。對於簡單請求,瀏覽器直接發出 CORS 請求。具體來講,就是會在頭信息之中,增長一個 Origin 字段。Origin 字段用來講明本次請求來自哪一個源。服務器根據這個值,決定是否贊成此次請求。對於若是 Origin 指定的源,不在許可範圍內,服務器會返回一個正常的 HTTP 迴應。瀏覽器發現,這個迴應的頭信息沒有包含 Access-Control-Allow-Origin 字段,就知道出錯了,從而拋出一個錯誤,ajax 不會收到響應信息。若是成功的話會包含一些以 Access-Control- 開頭的字段。

非簡單請求,瀏覽器會先發出一次預檢請求,來判斷該域名是否在服務器的白名單中,若是收到確定回覆後纔會發起請求。

(7)使用 websocket 協議,這個協議沒有同源限制。

(8)使用服務器來代理跨域的訪問請求,就是有跨域的請求操做時發送請求給後端,讓後端代爲請求,而後最後將獲取的結果發返回。

61. js 拖拽功能的實現

一個元素的拖拽過程,咱們能夠分爲三個步驟,第一步是鼠標按下目標元素,第二步是鼠標保持按下的狀態移動鼠標,第三步是鼠標擡起,拖拽過程結束。

這三步分別對應了三個事件,mousedown 事件,mousemove 事件和 mouseup 事件。只有在鼠標按下的狀態移動鼠標咱們纔會執行拖拽事件,所以咱們須要在 mousedown 事件中設置一個狀態來標識鼠標已經按下,而後在 mouseup 事件中再取消這個狀態。在 mousedown 事件中咱們首先應該判斷,目標元素是否爲拖拽元素,若是是拖拽元素,咱們就設置狀態而且保存這個時候鼠標的位置。而後在 mousemove 事件中,咱們經過判斷鼠標如今的位置和之前位置的相對移動,來肯定拖拽元素在移動中的座標。

最後 mouseup 事件觸發後,清除狀態,結束拖拽事件。

個人理解是 cookie 是服務器提供的一種用於維護會話狀態信息的數據,經過服務器發送到瀏覽器,瀏覽器保存在本地,當下一次有同源的請求時,將保存的 cookie 值添加到請求頭部,發送給服務端。這能夠用來實現記錄用戶登陸狀態等功能。cookie 通常能夠存儲 4k 大小的數據,而且只可以被同源的網頁所共享訪問。

服務器端可使用 Set-Cookie 的響應頭部來配置 cookie 信息。一條cookie 包括了5個屬性值 expires、domain、path、secure、HttpOnly。其中 expires 指定了 cookie 失效的時間,domain 是域名、path是路徑,domain 和 path 一塊兒限制了 cookie 可以被哪些 url 訪問。secure 規定了 cookie 只能在確保安全的狀況下傳輸,HttpOnly 規定了這個 cookie 只能被服務器訪問,不能使用 js 腳本訪問。

在發生 xhr 的跨域請求的時候,即便是同源下的 cookie,也不會被自動添加到請求頭部,除非顯示地規定。

63. 模塊化開發怎麼作?

我對模塊的理解是,一個模塊是實現一個特定功能的一組方法。在最開始的時候,js 只實現一些簡單的功能,因此並無模塊的概念,但隨着程序愈來愈複雜,代碼的模塊化開發變得愈來愈重要。

因爲函數具備獨立做用域的特色,最原始的寫法是使用函數來做爲模塊,幾個函數做爲一個模塊,可是這種方式容易形成全局變量的污染,而且模塊間沒有聯繫。

後面提出了對象寫法,經過將函數做爲一個對象的方法來實現,這樣解決了直接使用函數做爲模塊的一些缺點,可是這種辦法會暴露全部的全部的模塊成員,外部代碼能夠修改內部屬性的值。

如今最經常使用的是當即執行函數的寫法,經過利用閉包來實現模塊私有做用域的創建,同時不會對全局做用域形成污染。

64. js 的幾種模塊規範?

js 中如今比較成熟的有四種模塊加載方案。

第一種是 CommonJS 方案,它經過 require 來引入模塊,經過 module.exports 定義模塊的輸出接口。這種模塊加載方案是服務器端的解決方案,它是以同步的方式來引入模塊的,由於在服務端文件都存儲在本地磁盤,因此讀取很是快,因此以同步的方式加載沒有問題。但若是是在瀏覽器端,因爲模塊的加載是使用網絡請求,所以使用異步加載的方式更加合適。

第二種是 AMD 方案,這種方案採用異步加載的方式來加載模塊,模塊的加載不影響後面語句的執行,全部依賴這個模塊的語句都定義在一個回調函數裏,等到加載完成後再執行回調函數。require.js 實現了 AMD 規範。

第三種是 CMD 方案,這種方案和 AMD 方案都是爲了解決異步模塊加載的問題,sea.js 實現了 CMD 規範。它和 require.js的區別在於模塊定義時對依賴的處理不一樣和對依賴模塊的執行時機的處理不一樣。參考60

第四種方案是 ES6 提出的方案,使用 import 和 export 的形式來導入導出模塊。這種方案和上面三種方案都不一樣。參考 61。

65. AMD 和 CMD 規範的區別?

它們之間的主要區別有兩個方面。

(1)第一個方面是在模塊定義時對依賴的處理不一樣。AMD 推崇依賴前置,在定義模塊的時候就要聲明其依賴的模塊。而 CMD 推崇
就近依賴,只有在用到某個模塊的時候再去 require。

(2)第二個方面是對依賴模塊的執行時機處理不一樣。首先 AMD 和 CMD 對於模塊的加載方式都是異步加載,不過它們的區別在於模塊的執行時機,AMD 在依賴模塊加載完成後就直接執行依賴模塊,依賴模塊的執行順序和咱們書寫的順序不必定一致。而 CMD在依賴模塊加載完成後並不執行,只是下載而已,等到全部的依賴模塊都加載好後,進入回調函數邏輯,遇到 require 語句的時候才執行對應的模塊,這樣模塊的執行順序就和咱們書寫的順序保持一致了。

// CMD
define(function(require, exports, module) {
  var a = require("./a");
  a.doSomething();
  // 此處略去 100 行
  var b = require("./b"); // 依賴能夠就近書寫
  b.doSomething();
  // ...
});

// AMD 默認推薦
define(["./a", "./b"], function(a, b) {
  // 依賴必須一開始就寫好
  a.doSomething();
  // 此處略去 100 行
  b.doSomething();
  // ...
});

66. ES6 模塊與 CommonJS 模塊、AMD、CMD 的差別。

1.CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用。CommonJS 模塊輸出的是值的拷貝,也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個值。ES6 模塊的運行機制與 CommonJS 不同。JS 引擎對腳本靜態分析的時候,遇到模塊加載命令 import,就會生成一個只讀引用。等到腳本真正執行時,再根據這個只讀引用,到被加載的那個模塊裏面去取值。

2.CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口。CommonJS 模塊就是對象,即在輸入時是先加載整個模塊,生成一個對象,而後再從這個對象上面讀取方法,這種加載稱爲「運行時加載」。而 ES6 模塊不是對象,它的對外接口只是一種靜態定義,在代碼靜態解析階段就會生成。

67. requireJS 的核心原理是什麼?(如何動態加載的?如何避免屢次加載的?如何 緩存的?)

require.js 的核心原理是經過動態建立 script 腳原本異步引入模塊,而後對每一個腳本的 load 事件進行監聽,若是每一個腳本都加載完成了,再調用回調函數。

68.什麼是 Promise 對象,什麼是 Promises/A+ 規範?

Promise 對象是異步編程的一種解決方案,最先由社區提出。Promises/A+ 規範是 JavaScript Promise 的標準,規定了一個 Promise 所必須具備的特性。

Promise 是一個構造函數,接收一個函數做爲參數,返回一個 Promise 實例。一個 Promise 實例有三種狀態,分別是 pending、resolved 和 rejected,分別表明了進行中、已成功和已失敗。實例的狀態只能由 pending 轉變 resolved 或者 rejected 狀態,而且狀態一經改變,就凝固了,沒法再被改變了。狀態的改變是經過 resolve() 和 reject() 函數來實現的,咱們能夠在異步操做結束後調用這兩個函數改變 Promise 實例的狀態,它的原型上定義了一個 then 方法,使用這個 then 方法能夠爲兩個狀態的改變註冊回調函數。這個回調函數屬於微任務,會在本輪事件循環的末尾執行。

69. ECMAScript6 怎麼寫 class,爲何會出現 class 這種東西?

在我看來 ES6 新添加的 class 只是爲了補充 js 中缺乏的一些面嚮對象語言的特性,但本質上來講它只是一種語法糖,不是一個新的東西,其背後仍是原型繼承的思想。經過加入 class 能夠有利於咱們更好的組織代碼。

在 class 中添加的方法,實際上是添加在類的原型上的。

70. documen.write 和 innerHTML 的區別?

document.write 的內容會代替整個文檔內容,會重寫整個頁面。

innerHTML 的內容只是替代指定元素的內容,只會重寫頁面中的部份內容。

71. DOM 操做——怎樣添加、移除、移動、複製、建立和查找節點?

(1)建立新節點

createDocumentFragment(node);
createElement(node);
createTextNode(text);

(2)添加、移除、替換、插入

appendChild(node)
removeChild(node)
replaceChild(new,old)
insertBefore(new,old)

(3)查找

getElementById();
getElementsByName();
getElementsByTagName();
getElementsByClassName();
querySelector();
querySelectorAll();

(4)屬性操做

getAttribute(key);
setAttribute(key, value);
hasAttribute(key);
removeAttribute(key);

72. innerHTML 與 outerHTML 的區別?

對於這樣一個 HTML 元素:

content

innerHTML:內部 HTML,content

outerHTML:外部 HTML,

content

innerText:內部文本,content ;
outerText:內部文本,content ;

73. .call() 和 .apply() 的區別?

它們的做用如出一轍,區別僅在於傳入參數的形式的不一樣。

apply 接受兩個參數,第一個參數指定了函數體內 this 對象的指向,第二個參數爲一個帶下標的集合,這個集合能夠爲數組,也能夠爲類數組,apply 方法把這個集合中的元素做爲參數傳遞給被調用的函數。

call 傳入的參數數量不固定,跟 apply 相同的是,第一個參數也是表明函數體內的 this 指向,從第二個參數開始日後,每一個參數被依次傳入函數。

74. JavaScript 類數組對象的定義?

常見的類數組轉換爲數組的方法有這樣幾種:
(1)經過 call 調用數組的 slice 方法來實現轉換

Array.prototype.slice.call(arrayLike);

(2)經過 call 調用數組的 splice 方法來實現轉換

Array.prototype.splice.call(arrayLike, 0);

(3)經過 apply 調用數組的 concat 方法來實現轉換

Array.prototype.concat.apply([], arrayLike);

(4)經過 Array.from 方法來實現轉換

Array.from(arrayLike);

75. 數組和對象有哪些原生方法,列舉一下?

數組和字符串的轉換方法:toString()、toLocalString()、join() 其中 join() 方法能夠指定轉換爲字符串時的分隔符。

數組尾部操做的方法 pop() 和 push(),push 方法能夠傳入多個參數。

數組首部操做的方法 shift() 和 unshift() 重排序的方法 reverse() 和 sort(),sort() 方法能夠傳入一個函數來進行比較,傳入先後兩個值,若是返回值爲正數,則交換兩個參數的位置。

數組鏈接的方法 concat() ,返回的是拼接好的數組,不影響原數組。

數組截取辦法 slice(),用於截取數組中的一部分返回,不影響原數組。

數組插入方法 splice(),影響原數組查找特定項的索引的方法,indexOf() 和 lastIndexOf() 迭代方法 every()、some()、filter()、map() 和 forEach() 方法

數組歸併方法 reduce() 和 reduceRight() 方法

76. 數組的 fill 方法?

fill() 方法用一個固定值填充一個數組中從起始索引到終止索引內的所有元素。不包括終止索引。
fill 方法接受三個參數 value,start 以及 end,start 和 end 參數是可選的,其默認值分別爲 0 和 this 對象的 length 屬性值。

77. [,,,] 的長度?

尾後逗號 (有時叫作「終止逗號」)在向 JavaScript 代碼添加元素、參數、屬性時十分有用。若是你想要添加新的屬性,而且上一行已經使用了尾後逗號,你能夠僅僅添加新的一行,而不須要修改上一行。這使得版本控制更加清晰,以及代碼維護麻煩更少。

JavaScript 一開始就支持數組字面值中的尾後逗號,隨後向對象字面值(ECMAScript 5)中添加了尾後逗號。最近(ECMAScript 2017),又將其添加到函數參數中。可是 JSON 不支持尾後逗號。

若是使用了多於一個尾後逗號,會產生間隙。 帶有間隙的數組叫作稀疏數組(密緻數組沒有間隙)。稀疏數組的長度爲逗號的數量。

78. JavaScript 中的做用域與變量聲明提高?

變量提高的表現是,不管咱們在函數中何處位置聲明的變量,好像都被提高到了函數的首部,咱們能夠在變量聲明前訪問到而不會報錯。

形成變量聲明提高的本質緣由是 js 引擎在代碼執行前有一個解析的過程,建立了執行上下文,初始化了一些代碼執行時須要用到的對象。當咱們訪問一個變量時,咱們會到當前執行上下文中的做用域鏈中去查找,而做用域鏈的首端指向的是當前執行上下文的變量對象,這個變量對象是執行上下文的一個屬性,它包含了函數的形參、全部的函數和變量聲明,這個對象的是在代碼解析的時候建立的。這就是會出現變量聲明提高的根本緣由。

79. 如何編寫高性能的 Javascript ?

1.使用位運算代替一些簡單的四則運算。
2.避免使用過深的嵌套循環。
3.不要使用未定義的變量。
4.當須要屢次訪問數組長度時,能夠用變量保存起來,避免每次都會去進行屬性查找。

80. 簡單介紹一下 V8 引擎的垃圾回收機制

v8 的垃圾回收機制基於分代回收機制,這個機制又基於世代假說,這個假說有兩個特色,一是新生的對象容易早死,另外一個是不死的對象會活得更久。基於這個假說,v8 引擎將內存分爲了新生代和老生代。

新建立的對象或者只經歷過一次的垃圾回收的對象被稱爲新生代。經歷過屢次垃圾回收的對象被稱爲老生代。

新生代被分爲 From 和 To 兩個空間,To 通常是閒置的。當 From 空間滿了的時候會執行Scavenge 算法進行垃圾回收。當咱們執行垃圾回收算法的時候應用邏輯將會中止,等垃圾回收結束後再繼續執行。這個算法分爲三步:

(1)首先檢查 From 空間的存活對象,若是對象存活則判斷對象是否知足晉升到老生代的條件,若是知足條件則晉升到老生代。若是不知足條件則移動 To 空間。

(2)若是對象不存活,則釋放對象的空間。

(3)最後將 From 空間和 To 空間角色進行交換。

新生代對象晉升到老生代有兩個條件:

(1)第一個是判斷是對象否已經通過一次 Scavenge 回收。若經歷過,則將對象從 From 空間複製到老生代中;若沒有經歷,則複製到 To 空間。

(2)第二個是 To 空間的內存使用佔比是否超過限制。當對象從 From 空間複製到 To 空間時,若 To 空間使用超過 25%,則對象直接晉升到老生代中。設置 25% 的緣由主要是由於算法結束後,兩個空間結束後會交換位置,若是 To 空間的內存過小,會影響後續的內存分配。

老生代採用了標記清除法和標記壓縮法。標記清除法首先會對內存中存活的對象進行標記,標記結束後清除掉那些沒有標記的對象。因爲標記清除後會形成不少的內存碎片,不便於後面的內存分配。因此瞭解決內存碎片的問題引入了標記壓縮法。

因爲在進行垃圾回收的時候會暫停應用的邏輯,對於新生代方法因爲內存小,每次停頓的時間不會太長,但對於老生代來講每次垃圾回收的時間長,停頓會形成很大的影響。 爲了解決這個問題 V8 引入了增量標記的方法,將一次停頓進行的過程分爲了多步,每次執行完一小步就讓運行邏輯執行一會,就這樣交替運行。

81. 哪些操做會形成內存泄漏?

第一種狀況是咱們因爲使用未聲明的變量,而意外的建立了一個全局變量,而使這個變量一直留在內存中沒法被回收。

第二種狀況是咱們設置了 setInterval 定時器,而忘記取消它,若是循環函數有對外部變量的引用的話,那麼這個變量會被一直留在內存中,而沒法被回收。

第三種狀況是咱們獲取一個 DOM 元素的引用,然後面這個元素被刪除,因爲咱們一直保留了對這個元素的引用,因此它也沒法被回收。

第四種狀況是不合理的使用閉包,從而致使某些變量一直被留在內存當中。

82. 需求:實現一個頁面操做不會整頁刷新的網站,而且能在瀏覽器前進、後退時正確響應。給出你的技術實現方案?

經過使用 pushState + ajax 實現瀏覽器無刷新前進後退,當一次 ajax 調用成功後咱們將一條 state 記錄加入到 history對象中。一條 state 記錄包含了 url、title 和 content 屬性,在 popstate 事件中能夠獲取到這個 state 對象,咱們可使用 content 來傳遞數據。最後咱們經過對 window.onpopstate 事件監聽來響應瀏覽器的前進後退操做。

使用 pushState 來實現有兩個問題,一個是打開首頁時沒有記錄,咱們可使用 replaceState 來將首頁的記錄替換,另外一個問題是當一個頁面刷新的時候,仍然會向服務器端請求數據,所以若是請求的 url 須要後端的配合將其重定向到一個頁面。

83. 如何判斷當前腳本運行在瀏覽器仍是 node 環境中?(阿里)

this === window ? 'browser' : 'node';

經過判斷 Global 對象是否爲 window,若是不爲 window,當前腳本沒有運行在瀏覽器中。

84.什麼是 Promise 對象,什麼是 Promises/A+ 規範?

Promise 對象是異步編程的一種解決方案,最先由社區提出。Promises/A+ 規範是 JavaScript Promise 的標準,規定了一個 Promise 所必須具備的特性。

Promise 是一個構造函數,接收一個函數做爲參數,返回一個 Promise 實例。一個 Promise 實例有三種狀態,分別是 pending、resolved 和 rejected,分別表明了進行中、已成功和已失敗。實例的狀態只能由 pending 轉變 resolved 或者 rejected 狀態,而且狀態一經改變,就凝固了,沒法再被改變了。狀態的改變是經過 resolve() 和 reject() 函數來實現的,咱們能夠在異步操做結束後調用這兩個函數改變 Promise 實例的狀態,它的原型上定義了一個 then 方法,使用這個 then 方法能夠爲兩個狀態的改變註冊回調函數。這個回調函數屬於微任務,會在本輪事件循環的末尾執行。

85. 移動端的點擊事件的有延遲,時間是多久,爲何會有? 怎麼解決這個延時?

移動端點擊有 300ms 的延遲是由於移動端會有雙擊縮放的這個操做,所以瀏覽器在 click 以後要等待 300ms,看用戶有沒有下一次點擊,來判斷此次操做是否是雙擊。

86. 什麼是「前端路由」?何時適合使用「前端路由」?「前端路由」有哪些優勢和缺點?

(1)什麼是前端路由?

前端路由就是把不一樣路由對應不一樣的內容或頁面的任務交給前端來作,以前是經過服務端根據 url 的不一樣返回不一樣的頁面實現的。

(2)何時使用前端路由?

在單頁面應用,大部分頁面結構不變,只改變部份內容的使用

(3)前端路由有什麼優勢和缺點?

優勢:用戶體驗好,不須要每次都從服務器所有獲取,快速展示給用戶

缺點:單頁面沒法記住以前滾動的位置,沒法在前進,後退的時候記住滾動的位置

前端路由一共有兩種實現方式,一種是經過 hash 的方式,一種是經過使用 pushState 的方式。

87.觀察者模式和發佈訂閱模式有什麼不一樣?

發佈訂閱模式其實屬於廣義上的觀察者模式

在觀察者模式中,觀察者須要直接訂閱目標事件。在目標發出內容改變的事件後,直接接收事件並做出響應。

而在發佈訂閱模式中,發佈者和訂閱者之間多了一個調度中心。調度中心一方面從發佈者接收事件,另外一方面向訂閱者發佈事件,訂閱者須要在調度中心中訂閱事件。經過調度中心實現了發佈者和訂閱者關係的解耦。使用發佈訂閱者模式更利於咱們代碼的可維護性。

88. 檢測瀏覽器版本版本有哪些方式?

檢測瀏覽器版本一共有兩種方式:

一種是檢測 window.navigator.userAgent 的值,但這種方式很不可靠,由於 userAgent 能夠被改寫,而且早期的瀏覽器如 ie,會經過假裝本身的 userAgent 的值爲 Mozilla 來躲過服務器的檢測。

第二種方式是功能檢測,根據每一個瀏覽器獨有的特性來進行判斷,如 ie 下獨有的 ActiveXObject。

89. 什麼是 Polyfill ?

Polyfill 指的是用於實現瀏覽器並不支持的原生 API 的代碼。

好比說 querySelectorAll 是不少現代瀏覽器都支持的原生 Web API,可是有些古老的瀏覽器並不支持,那麼假設有人寫了一段代碼來實現這個功能使這些瀏覽器也支持了這個功能,那麼這就能夠成爲一個 Polyfill。

一個 shim 是一個庫,有本身的 API,而不是單純實現原生不支持的 API。

90. 使用 JS 實現獲取文件擴展名?

// String.lastIndexOf() 方法返回指定值(本例中的'.')在調用該方法的字符串中最後出現的位置,若是沒找到則返回 -1。

// 對於 'filename' 和 '.hiddenfile' ,lastIndexOf 的返回值分別爲 0 和 -1 無符號右移操做符(>>>) 將 -1 轉換爲 4294967295 ,將 -2 轉換爲 4294967294 ,這個方法能夠保證邊緣狀況時文件名不變。

// String.prototype.slice() 從上面計算的索引處提取文件的擴展名。若是索引比文件名的長度大,結果爲""。
function getFileExtension(filename) {
  return filename.slice(((filename.lastIndexOf(".") - 1) >>> 0) + 2);
}

91. 介紹一下 js 的節流與防抖?

函數防抖是指在事件被觸發 n 秒後再執行回調,若是在這 n 秒內事件又被觸發,則從新計時。這可使用在一些點擊請求的事件上,避免由於用戶的屢次點擊向後端發送屢次請求。

函數節流是指規定一個單位時間,在這個單位時間內,只能有一次觸發事件的回調函數執行,若是在同一個單位時間內某事件被觸發屢次,只有一次能生效。節流可使用在 scroll 函數的事件監聽上,經過事件節流來下降事件調用的頻率。

92. Object.is() 與原來的比較操做符 「=」、「」 的區別?

使用雙等號進行相等判斷時,若是兩邊的類型不一致,則會進行強制類型轉化後再進行比較。

使用三等號進行相等判斷時,若是兩邊的類型不一致時,不會作強制類型準換,直接返回 false。

使用 Object.is 來進行相等判斷時,通常狀況下和三等號的判斷相同,它處理了一些特殊的狀況,好比 -0 和 +0 再也不相等,兩個 NaN 認定爲是相等的。

93.escape,encodeURI,encodeURIComponent 有什麼區別

encodeURI 是對整個 URI 進行轉義,將 URI 中的非法字符轉換爲合法字符,因此對於一些在 URI 中有特殊意義的字符不會進行轉義。

encodeURIComponent 是對 URI 的組成部分進行轉義,因此一些特殊字符也會獲得轉義。

escape 和 encodeURI 的做用相同,不過它們對於 unicode 編碼爲 0xff 以外字符的時候會有區別,escape 是直接在字符的 unicode 編碼前加上 %u,而 encodeURI 首先會將字符轉換爲 UTF-8 的格式,再在每一個字節前加上 %。

94. Unicode 和 UTF-8 之間的關係?

Unicode 是一種字符集合,如今可容納 100 多萬個字符。每一個字符對應一個不一樣的 Unicode 編碼,它只規定了符號的二進制代碼,卻沒有規定這個二進制代碼在計算機中如何編碼傳輸。

UTF-8 是一種對 Unicode 的編碼方式,它是一種變長的編碼方式,能夠用 1~4 個字節來表示一個字符。

95. js 的事件循環是什麼?

由於 js 是單線程運行的,在代碼執行的時候,經過將不一樣函數的執行上下文壓入執行棧中來保證代碼的有序執行。在執行同步代碼的時候,若是遇到了異步事件,js 引擎並不會一直等待其返回結果,而是會將這個事件掛起,繼續執行執行棧中的其餘任務。當異步事件執行完畢後,再將異步事件對應的回調加入到與當前執行棧中不一樣的另外一個任務隊列中等待執行。任務隊列能夠分爲宏任務對列和微任務對列,噹噹前執行棧中的事件執行完畢後,js 引擎首先會判斷微任務對列中是否有任務能夠執行,若是有就將微任務隊首的事件壓入棧中執行。當微任務對列中的任務都執行完成後再去判斷宏任務對列中的任務。

微任務包括了 promise 的回調、node 中的 process.nextTick 、對 Dom 變化監聽的 MutationObserver。

宏任務包括了 script 腳本的執行、setTimeout ,setInterval ,setImmediate 一類的定時事件,還有如 I/O 操做、UI 渲染等。

96. js 中的深淺拷貝實現?

淺拷貝指的是將一個對象的屬性值複製到另外一個對象,若是有的屬性的值爲引用類型的話,那麼會將這個引用的地址複製給對象,所以兩個對象會有同一個引用類型的引用。淺拷貝可使用 Object.assign 和展開運算符來實現。

深拷貝相對淺拷貝而言,若是遇到屬性值爲引用類型的時候,它新建一個引用類型並將對應的值複製給它,所以對象得到的一個新的引用類型而不是一個原有類型的引用。深拷貝對於一些對象可使用 JSON 的兩個函數來實現,可是因爲 JSON 的對象格式比 js 的對象格式更加嚴格,因此若是屬性值裏邊出現函數或者 Symbol 類型的值時,會轉換失敗。

97. 手寫 call、apply 及 bind 函數

call 函數的實現步驟:
1.判斷調用對象是否爲函數,即便咱們是定義在函數的原型上的,可是可能出現使用 call 等方式調用的狀況。
2.判斷傳入上下文對象是否存在,若是不存在,則設置爲 window 。
3.處理傳入的參數,截取第一個參數後的全部參數。
4.將函數做爲上下文對象的一個屬性。
5.使用上下文對象來調用這個方法,並保存返回結果。
6.刪除剛纔新增的屬性。
7.返回結果。
apply 函數的實現步驟:
1.判斷調用對象是否爲函數,即便咱們是定義在函數的原型上的,可是可能出現使用 call 等方式調用的狀況。
2.判斷傳入上下文對象是否存在,若是不存在,則設置爲 window 。
3.將函數做爲上下文對象的一個屬性。
4.判斷參數值是否傳入
4.使用上下文對象來調用這個方法,並保存返回結果。
5.刪除剛纔新增的屬性
6.返回結果
bind 函數的實現步驟:
1.判斷調用對象是否爲函數,即便咱們是定義在函數的原型上的,可是可能出現使用 call 等方式調用的狀況。
2.保存當前函數的引用,獲取其他傳入參數值。
3.建立一個函數返回
4.函數內部使用 apply 來綁定函數調用,須要判斷函數做爲構造函數的狀況,這個時候須要傳入當前函數的 this 給 apply 調用,其他狀況都傳入指定的上下文對象。

98. 函數柯里化的實現

// 函數柯里化指的是一種將使用多個參數的一個函數轉換成一系列使用一個參數的函數的技術。

function curry(fn, args) {
  // 獲取函數須要的參數長度
  let length = fn.length;

  args = args || [];

  return function() {
    let subArgs = args.slice(0);

    // 拼接獲得現有的全部參數
    for (let i = 0; i < arguments.length; i++) {
      subArgs.push(arguments[i]);
    }

    // 判斷參數的長度是否已經知足函數所需參數的長度
    if (subArgs.length >= length) {
      // 若是知足,執行函數
      return fn.apply(this, subArgs);
    } else {
      // 若是不知足,遞歸返回科裏化的函數,等待參數的傳入
      return curry.call(this, fn, subArgs);
    }
  };
}

// es6 實現
function curry(fn, ...args) {
  return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}

99. 爲何 0.1 + 0.2 != 0.3?如何解決這個問題?

當計算機計算 0.1+0.2 的時候,實際上計算的是這兩個數字在計算機裏所存儲的二進制,0.1 和 0.2 在轉換爲二進制表示的時候會出現位數無限循環的狀況。js 中是以 64 位雙精度格式來存儲數字的,只有 53 位的有效數字,超過這個長度的位數會被截取掉這樣就形成了精度丟失的問題。這是第一個會形成精度丟失的地方。在對兩個以 64 位雙精度格式的數據進行計算的時候,首先會進行對階的處理,對階指的是將階碼對齊,也就是將小數點的位置對齊後,再進行計算,通常是小階向大階對齊,所以小階的數在對齊的過程當中,有效數字會向右移動,移動後超過有效位數的位會被截取掉,這是第二個可能會出現精度丟失的地方。當兩個數據階碼對齊後,進行相加運算後,獲得的結果可能會超過 53 位有效數字,所以超過的位數也會被截取掉,這是可能發生精度丟失的第三個地方。

對於這樣的狀況,咱們能夠將其轉換爲整數後再進行運算,運算後再轉換爲對應的小數,以這種方式來解決這個問題。

咱們還能夠將兩個數相加的結果和右邊相減,若是相減的結果小於一個極小數,那麼咱們就能夠認定結果是相等的,這個極小數可使用 es6 的 Number.EPSILON

100. 什麼是 XSS 攻擊?如何防範 XSS 攻擊?

XSS 攻擊指的是跨站腳本攻擊,是一種代碼注入攻擊。攻擊者經過在網站注入惡意腳本,使之在用戶的瀏覽器上運行,從而盜取用戶的信息如 cookie 等。

XSS 的本質是由於網站沒有對惡意代碼進行過濾,與正常的代碼混合在一塊兒了,瀏覽器沒有辦法分辨哪些腳本是可信的,從而致使了惡意代碼的執行。

XSS 通常分爲存儲型、反射型和 DOM 型。

存儲型指的是惡意代碼提交到了網站的數據庫中,當用戶請求數據的時候,服務器將其拼接爲 HTML 後返回給了用戶,從而致使了惡意代碼的執行。

反射型指的是攻擊者構建了特殊的 URL,當服務器接收到請求後,從 URL 中獲取數據,拼接到 HTML 後返回,從而致使了惡意代碼的執行。

DOM 型指的是攻擊者構建了特殊的 URL,用戶打開網站後,js 腳本從 URL 中獲取數據,從而致使了惡意代碼的執行。

XSS 攻擊的預防能夠從兩個方面入手,一個是惡意代碼提交的時候,一個是瀏覽器執行惡意代碼的時候。

對於第一個方面,若是咱們對存入數據庫的數據都進行的轉義處理,可是一個數據可能在多個地方使用,有的地方可能不須要轉義,因爲咱們沒有辦法判斷數據最後的使用場景,因此直接在輸入端進行惡意代碼的處理,實際上是不太可靠的。

所以咱們能夠從瀏覽器的執行來進行預防,一種是使用純前端的方式,不用服務器端拼接後返回。另外一種是對須要插入到 HTML 中的代碼作好充分的轉義。對於 DOM 型的攻擊,主要是前端腳本的不可靠而形成的,咱們對於數據獲取渲染和字符串拼接的時候應該對可能出現的惡意代碼狀況進行判斷。

還有一些方式,好比使用 CSP ,CSP 的本質是創建一個白名單,告訴瀏覽器哪些外部資源能夠加載和執行,從而防止惡意代碼的注入攻擊。

還能夠對一些敏感信息進行保護,好比 cookie 使用 http-only ,使得腳本沒法獲取。也可使用驗證碼,避免腳本假裝成用戶執行一些操做。

101. 什麼是 CSRF 攻擊?如何防範 CSRF 攻擊?

CSRF 攻擊指的是跨站請求僞造攻擊,攻擊者誘導用戶進入一個第三方網站,而後該網站向被攻擊網站發送跨站請求。若是用戶在被攻擊網站中保存了登陸狀態,那麼攻擊者就能夠利用這個登陸狀態,繞事後臺的用戶驗證,冒充用戶向服務器執行一些操做。

CSRF 攻擊的本質是利用了 cookie 會在同源請求中攜帶發送給服務器的特色,以此來實現用戶的冒充。

通常的 CSRF 攻擊類型有三種:

第一種是 GET 類型的 CSRF 攻擊,好比在網站中的一個 img 標籤裏構建一個請求,當用戶打開這個網站的時候就會自動發起提
交。

第二種是 POST 類型的 CSRF 攻擊,好比說構建一個表單,而後隱藏它,當用戶進入頁面時,自動提交這個表單。

第三種是連接類型的 CSRF 攻擊,好比說在 a 標籤的 href 屬性裏構建一個請求,而後誘導用戶去點擊。

CSRF 能夠用下面幾種方法來防禦:

第一種是同源檢測的方法,服務器根據 http 請求頭中 origin 或者 referer 信息來判斷請求是否爲容許訪問的站點,從而對請求進行過濾。當 origin 或者 referer 信息都不存在的時候,直接阻止。這種方式的缺點是有些狀況下 referer 能夠被僞造。還有就是咱們這種方法同時把搜索引擎的連接也給屏蔽了,因此通常網站會容許搜索引擎的頁面請求,可是相應的頁面請求這種請求方式也可能被攻擊者給利用。

第二種方法是使用 CSRF Token 來進行驗證,服務器向用戶返回一個隨機數 Token ,當網站再次發起請求時,在請求參數中加入服務器端返回的 token ,而後服務器對這個 token 進行驗證。這種方法解決了使用 cookie 單一驗證方式時,可能會被冒用的問題,可是這種方法存在一個缺點就是,咱們須要給網站中的全部請求都添加上這個 token,操做比較繁瑣。還有一個問題是通常不會只有一臺網站服務器,若是咱們的請求通過負載平衡轉移到了其餘的服務器,可是這個服務器的 session 中沒有保留這個 token 的話,就沒有辦法驗證了。這種狀況咱們能夠經過改變 token 的構建方式來解決。

第三種方式使用雙重 Cookie 驗證的辦法,服務器在用戶訪問網站頁面時,向請求域名注入一個Cookie,內容爲隨機字符串,而後當用戶再次向服務器發送請求的時候,從 cookie 中取出這個字符串,添加到 URL 參數中,而後服務器經過對 cookie 中的數據和參數中的數據進行比較,來進行驗證。使用這種方式是利用了攻擊者只能利用 cookie,可是不能訪問獲取 cookie 的特色。而且這種方法比 CSRF Token 的方法更加方便,而且不涉及到分佈式訪問的問題。這種方法的缺點是若是網站存在 XSS 漏洞的,那麼這種方式會失效。同時這種方式不能作到子域名的隔離。

第四種方式是使用在設置 cookie 屬性的時候設置 Samesite ,限制 cookie 不能做爲被第三方使用,從而能夠避免被攻擊者利用。Samesite 一共有兩種模式,一種是嚴格模式,在嚴格模式下 cookie 在任何狀況下都不可能做爲第三方 Cookie 使用,在寬鬆模式下,cookie 能夠被請求是 GET 請求,且會發生頁面跳轉的請求所使用。

我把JavaScript面試題整理成兩份文檔,有一份是按分類來整理,一共有19個內容,具體下圖所示:

另外一份是有191道題目,是文中題目的延續,整理的文中的面試題在這份文檔中,下圖所示:

如何獲取這兩份優質的資料呢?
快速入手通道:點擊這個,免費下載!誠意滿滿!!!

整理不易,以爲有幫助的朋友能夠幫忙點贊分享支持一下小編謝謝~

相關文章
相關標籤/搜索