1. undefined 與 undeclared 的區別?

  • 已在作用域中聲明但還沒有賦值的變量,是 undefined。相反,還沒有在作用域中聲明過的變量,是 undeclared 的。
  • 對於 undeclared 變量的引用,瀏覽器會報引用錯誤,如 ReferenceError: b is not defined 。但是我們可以使用 typeof 的安全防範機制來避免報錯,因為對於 undeclared(或者 not defined )變量,typeof 會返回 “undefined”。

2. null 和 undefined 的區別?

  • 首先 Undefined 和 Null 都是基本數據類型,這兩個基本數據類型分別都只有一個值,就是 undefined 和 null。
  • undefined 代表的含義是未定義, null 代表的含義是空對象。一般變量聲明瞭但還沒有定義的時候會返回 undefined,null主要用於賦值給一些可能會返回對象的變量,作為初始化。 null 不是對象,雖然 typeof null 會輸出object,但是這只是 JS 存在的一個悠久 Bug。在 JS 的最初版本中使用的是 32位系統,為了性能考慮使用低位存儲變量的類型信息,000 開頭代表是對象,然而 null 表示為全零,所以將它錯誤的判斷為 object。雖然現在的內部類型判斷代碼已經改變了,但是對於這個 Bug 卻是一直流傳下來。
  • undefined 在 js 中不是一個保留字,這意味着我們可以使用 undefined來作為一個變量名,這樣的做法是非常危險的,它會影響我們對 undefined 值的判斷。但是我們可以通過一些方法獲得安全的undefined 值,比如説 void 0。
  • 當我們對兩種類型使用 typeof 進行判斷的時候,Null 類型化會返回“object”,這是一個歷史遺留的問題。當我們使用雙等號對兩種類型的值進行比較時會返回 true,使用三個等號時會返回 false。

3.Javascript 的作用域和作用域鏈

  1. 作用域:作用域是變量和函數能被訪問的區域或集合,分為全局作用域、函數作用域和塊級作用域(ES6引入)三種。
  2. 作用域鏈:作用域鏈是用來保證對執行環境有權訪問的所有變量和函數的有序訪問,通過作用域鏈,我們可以訪問到外層環境的變量和函數。作用域鏈的本質上是一個指向變量對象的指針列表。變量對象是一個包含了執行環境中所有變量和函數的對象。作用域鏈的前端始終都是當前執行上下文的變量對象。全局執行上下文的變量對象(也就是全局對象)始終是作用域鏈的最後一個對象。當我們查找一個變量時,如果當前執行環境中沒有找到,我們可以沿着作用域鏈向後查找。作用域鏈的創建過程跟執行上下文的建立有關。

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

  1. 工廠模式:工廠模式的主要工作原理是用函數來封裝創建對象的細節,從而通過調用函數來達到複用的目的。但是它有一個很大的問題就是創建出來的對象無法和某個類型聯繫起來,它只是簡單的封裝了複用代碼,而沒有建立起對象和類型間的關係。
  2. 構造函數模式:構造函數即函數通過 new 來調用,執行構造函數首先會創建一個對象,然後將對象的原型指向構造函數的 prototype 屬性,然後將執行上下文中的 this 指向這個對象,最後再執行整個函數,如果返回值不是對象,則返回新建的對象。構造函數模式相對於工廠模式的優點是,所創建的對象和構造函數建立起了聯繫,因此我們可以通過原型來識別對象的類型。但是構造函數存在一個缺點就是,造成了不必要的函數對象的創建,如果對象屬性中如果包含函數的話,那麼每次我們都會新建一個函數對象,浪費了不必要的內存空間,因為函數是所有的實例都可以通用的。
  3. 原型模式:因為每一個函數都有一個 prototype 屬性,這個屬性是一個對象,它包含了通過構造函數創建的所有實例都能共享的屬性和方法。因此我們可以使用原型對象來添加公用屬性和方法,從而實現代碼的複用。這種方式相對於構造函數模式來説,解決了函數對象的複用問題。但是這種模式也存在一些問題,一個是沒有辦法通過傳入參數來初始化值,另一個是如果存在一個引用類型如 Array 這樣的值,那麼所有的實例將共享一個對象,一個實例對引用類型值的改變會影響所有的實例。
  4. 組合使用構造函數模式和原型模式:這是創建自定義類型的最常見方式。因為構造函數模式和原型模式分開使用都存在一些問題,因此我們可以組合使用這兩種模式,通過構造函數來初始化對象的屬性,通過原型對象來實現函數方法的複用。這種方法很好的解決了兩種模式單獨使用時的缺點,但是有一點不足的就是,因為使用了兩種不同的模式,所以對於代碼的封裝性不夠好。
  5. 動態原型模式:這一種模式將原型方法賦值的創建過程移動到了構造函數的內部,通過對屬性是否存在的判斷,可以實現僅在第一次調用函數時對原型對象賦值一次的效果。這一種方式很好地對上面的混合模式進行了封裝。
  6. 寄生構造函數模式:這一種模式和工廠模式的實現基本相同,主要是基於一個已有的類型,在實例化時對實例化的對象進行擴展。這樣既不用修改原來的構造函數,也達到了擴展對象的目的。它的一個缺點和工廠模式一樣,無法實現對象的識別。

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

  1. 原型鏈繼承:直接讓子類的原型對象指向父類的實例,當子類實例找不到對象的屬性或方法時會沿着原型鏈一起找。這種實現方式存在的缺點是,在包含有引用類型的數據時,會被所有的實例對象所共享,容易造成修改的混亂。還有就是在創建子類型的時候不能向父類型傳遞參數。
  2. 構造函數繼承:通過在子類型的函數中調用父類型的構造函數來實現的,這一種方法解決了不能像父類型傳遞參數的缺點和指向同一原型的實例修改問題,但沒有繼承父類原型上的方法和屬性。
  3. 組合式繼承:使用原型鏈實現對原型屬性和方法的繼承,而通過構造函數來實現對實例屬性的繼承 這樣,既在原型上定義方法實現了函數複用,又保證每個實例都有它自己的屬性,但是導致父類的構造函數執行了兩次,一次是在構造函數式繼承中執行的;另一次是在類式繼中執行的。
  4. 原型式繼承:原型式繼承的主要思路就是基於已有的對象來創建新的對象,實現的原理是,向函數中傳入一個對象,然後返回一個以這個對象為原型的對象。這種繼承的思路主要不是為了實現創造一種新的類型,只是對某個對象實現一種簡單繼承,ES5 中定義的 Object.create() 方法就是原型式繼承的實現。缺點與原型鏈方式相同。
  5. 寄生式繼承:寄生式繼承的思路是創建一個用於封裝繼承過程的函數,通過傳入一個對象,然後複製一個對象的副本,然後對象進行擴展,最後返回這個對象。這個擴展的過程就可以理解是一種繼承。這種繼承的優點就是對一個簡單對象實現繼承,如果這個對象不是我們的自定義類型時。缺點是沒有辦法實現函數的複用。
  6. 寄生式組合繼承:使用父類型的原型的副本來作為子類型的原型,這樣就避免了創建不必要的屬性,但對子類原型的操作會影響到父類原型,使用淺拷貝可以解決。