目錄html
1.序言es6
在 深刻理解JS中的對象(一):原型、原型鏈和構造函數 中,咱們分析了JS中是否一切皆對象以及對象的原型、原型鏈和構造函數。在談到構造函數時,應該有注意到箭頭函數是不能做爲構造函數的,也就是不能使用 new 關鍵字調用箭頭函數,這是爲何呢?咱們將在本篇深刻討論剖析對象的構造(new)的工做原理。編程
2.不一樣返回值的構造函數瀏覽器
先看幾個示例:app
(1)沒有 return 的構造函數函數
function Foo(x) { this.x = x } var foo = new Foo(10) console.log(foo.x) // 10
(2) return 一個 object 的構造函數post
function Foo(x) { this.x = x return { y: 20 } } var foo = new Foo(10) console.log(foo) // { y: 20 } console.log(foo.x) // undifined console.log(foo.y) // 20
(3) return 一個非 object 的構造函數測試
function Foo(x) { this.x = x return 20 } var foo = new Foo(10) console.log(foo.x) // 10
簡單分析一下:this
第(1)中狀況中,在構造函數中,沒有任何顯式的 return,最終返回的是 this 值。es5
第(2)種狀況中,在構造函數中,彷佛this被捨棄掉了,最終返回的是顯式 return 的 object。
第(3)中狀況中,在構造函數中,雖然顯式 return 了一個非對象的 number,但彷佛被捨棄掉了,最終返回的是 this 值。
從上述狀況能夠得出,構造函數顯式的返回了對象類型的值,會影響最終建立的對象。要弄明白這是爲何,咱們就須要明白 new 調用函數到底作了些什麼操做。
3.深刻 new 調用函數原理
咱們來看看 EcmaScript 5.1標準的規定,瞭解一下 new 運算符 的規範。
針對有無參數進行執行提供了兩種規範,因爲二者區別很小,這裏只選取無參規範分析:
產生式 NewExpression : new NewExpression 按照下面的過程執行 :
- 令 ref 爲解釋執行 NewExpression 的結果 .
- 令 constructor 爲 GetValue(ref).
- 若是 Type(constructor) is not Object ,拋出一個 TypeError 異常 .
- 若是 constructor 沒有實現 [[Construct]] 內部方法 ,拋出一個 TypeError 異常 .
- 返回調用 constructor 的 [[Construct]] 內部方法的結果 , 按無參數傳入參數列表 ( 就是一個空的參數列表 ).
簡單解析:
第1~3步,主要是從引用類型中獲得一個對象真正的值(constructor),並判斷其類型是否是一個對象。
第4步,判斷構造函數是否實現了 [[Construct]] 內部方法,若是沒有則拋出異常。
第5步,調用構造函數的 [[Construct]] 內部方法,並返回其結果。
解答第一個問題:箭頭函數爲何不能做爲構造函數?
箭頭函數恰好符合上述第4步中的狀況,其沒有實現 [[Construct]]
方法,如下來自ES6中 Arrow functions 規範參考:
An arrow function is different from a normal function in only two ways:
- The following constructs are lexical:
arguments
,super
,this
,new.target
- It can’t be used as a constructor: Normal functions support
new
via the internal method[[Construct]]
and the propertyprototype
. Arrow functions have neither, which is whynew (() => {})
throws an error.
在瀏覽器中測試用 new 調用箭頭函數報錯,以下圖:
解答第二個問題:爲何構造函數顯式的返回了對象類型的值會影響最終建立的對象?
從 new 運算符的規範來看,用 new 調用函數 F,至關於觸發 F 的 [[Construct]] 內部方法,因此咱們須要再看看 EcmaScript 5.1標準中的 [[Construct]] 的規範:
當以一個可能的空的參數列表調用函數對象 F 的 [[Construct]] 內部方法,採用如下步驟:
- 令 obj 爲新建立的 ECMAScript 原生對象。
- 依照 8.12 設定 obj 的全部內部屬性。
- 設定 obj 的 [[Class]] 內部屬性爲 "Object"。
- 設定 obj 的 [[Extensible]] 內部屬性爲 true。
- 令 proto 爲以參數 "prototype" 調用 F 的 [[Get]] 內部屬性的值。
- 若是 Type(proto) 是 Object,設定 obj 的 [[Prototype]] 內部屬性爲 proto。
- 若是 Type(proto) 不是 Object,設定 obj 的 [[Prototype]] 內部屬性爲 15.2.4 描述的標準內部的 Object 的 prototype 對象。
- 以 obj 爲 this 值, 傳遞給 [[Construct]] 的參數列表爲 args,調用 F 的 [[Call]] 內部方法,令 result 爲調用結果。
- 若是 Type(result) 是 Object,則返回 result。
- 返回 obj
簡單解析:
第1~7步,主要建立了一個原生對象 obj,並給這個 obj 設定各類屬性(包括 [[Prototype]] 內部屬性,即對象的原型)。
第8步,至關於 result = F.[[Call]].apply(obj, args)
,爲了更清楚 [[Call]] 內部方法作了些什麼,將在下面從規範層次作出解讀。
第九、10步,就是判斷 result 的類型是否是對象?若是是對象,則返回 result;若是不是,則返回 obj。
EcmaScript 5.1標準中的 [[Call]] 的規範:
當用一個 this 值,一個參數列表調用函數對象 F 的 [[Call]] 內部方法,採用如下步驟:
- 用 F 的 [[FormalParameters]] 內部屬性值,參數列表 args,10.4.3 描述的 this 值來創建 函數代碼 的一個新執行環境,令 funcCtx 爲其結果。
- 令 result 爲 FunctionBody(也就是 F 的 [[Code]] 內部屬性,即函數 F 自身)解釋執行的結果。若是 F 沒有 [[Code]] 內部屬性或其值是空的 FunctionBody,則 result 是 (normal, undefined, empty)。
- 退出 funcCtx 執行環境,恢復到以前的執行環境。
- 若是 result.type 是 throw 則拋出 result.value。
- 若是 result.type 是 return 則返回 result.value。
- 不然 result.type 一定是 normal。返回 undefined。
簡單解析:首先,建立根據相關參數和屬性建立一個新的執行上下文,而後執行函數 F 的代碼,並令 result 爲其調用結果, 而後退出當前執行上下文,最後根據 result.type 返回對應的值。(實質上就是執行了一遍函數,返回其結果)
所以,咱們能夠對上面所列舉的三個不一樣返回值的構造函數的示例一個合理的解釋了:
new 調用構造函數,若是構造函數中顯式的 return 了值而且其類型是一個對象,那麼這個值將替代建立的原生對象 obj 做爲最終返回值,不然最終將返回建立的原生對象 obj。
4.總結
new 調用函數 F:
constructor.[[Call]].apply(obj, args)
,其中 args 是傳遞給 [[Construct]] 的參數列表,[[Call]] 至關於函數 F 自身5.參考