JavaScript 中常常使用構造函數建立對象(經過 new
操做符調用一個函數),那在使用 new
調用一個函數的時候到底發生了什麼?先看幾個例子,再解釋背後發生了什麼。html
構造函數最後沒有 return
語句,這也是使用構造函數時默認狀況,最後會返回一個新對象,以下:函數
function Foo(age) { this.age = age; } var o = new Foo(111); console.log(o);
這是常見的使用構造函數建立對象的過程,打印出來的是 {age: 111}
。ui
構造函數最後 return
對象類型數據:this
function Foo(age) { this.age = age; return { type: "我是顯式返回的" }; } var o = new Foo(222); console.log(o);
打印出來的是 {type: '我是顯式返回的'}
,也就是說,return
以前的工做都白作了,最後返回 return
後面的對象。prototype
那是否是隻要構造函數體內最後有 return
,返回都是 return
後面的數據呢?code
咱們看下返回基本類型數據的狀況:htm
function Foo(age) { this.age = age; return 1; } var o = new Foo(333); console.log(o);
打印出來的是 {age: 333}
,和沒有 return
時效果同樣。跟預期不同,背後你原理看下面分析。對象
當使用 new
操做符建立對象是,ES5 官方文檔在 函數定義 一節中作了以下定義 13.2.2 [[Construct]]
:繼承
When the [[Construct]]
internal method for a Function
object F
is called with a possibly empty list of arguments, the following steps are taken:ip
看第 八、9 步:
8)調用函數F
,將其返回值賦給result
;其中,F
執行時的實參爲傳遞給[[Construct]]
(即F
自己) 的參數,F
內部this
指向obj
;
9)若是result
是Object
類型,返回result
;
這也就解釋了若是構造函數顯式返回對象類型,則直接返回這個對象,而不是返回最開始建立的對象。
最後在看第 10 步:
10)若是F
返回的不是對象類型(第 9 步不成立),則返回建立的對象obj
。
若是構造函數沒有顯式返回對象類型(顯式返回基本數據類型或者直接不返回),則返回最開始建立的對象。
那若是構造函數是箭頭函數怎麼辦?
箭頭函數中沒有 [[Construct]]
方法,不能使用 new
調用,會報錯。
NOTICE:其中 [[Construct]]
就是指構造函數自己。
相關規範在 ES6 的官方文檔 中有提,但自從 ES6 以來的官方文檔巨難懂,在此不作表述。
除了箭頭函數以外的任何函數,均可以使用 new
進行調用,背後發生了什麼,上節英文講述的很清楚了,再用中文描述以下:
1)建立 ECMAScript 原生對象 obj
;
2)給 obj
設置原生對象的內部屬性;(和原型屬性不一樣,內部屬性表示爲 [[PropertyName]]
,兩個方括號包裹屬性名,而且屬性名大寫,好比常見 [[Prototype]]
、[[Constructor]]
)
3)設置 obj
的內部屬性 [[Class]]
爲 Object
;
4)設置 obj
的內部屬性 [[Extensible]]
爲 true
;
5)將 proto
的值設置爲 F
的 prototype
屬性值;
6)若是 proto
是對象類型,則設置 obj
的內部屬性 [[Prototype]]
值爲 proto
;(進行原型鏈關聯,實現繼承的關鍵)
7)若是 proto
是不對象類型,則設置 obj
的內部屬性 [[Prototype]]
值爲內建構造函數 Object 的 prototype
值;(函數 prototype
屬性能夠被改寫,若是改爲非對象類型,obj
的 [[Prototype]]
就指向 Object 的原型對象)
8)9)10)見上節分析。(決定返回什麼)
對於第 7 步的狀況,見下面代碼:
function Foo(name) { this.name = name; } var o1 = new Foo("xiaoming"); console.log(o1.__proto__ === Foo.prototype); // true // 重寫構造函數原型屬性爲非對象類型,實例內部 [[Prototype]] 屬性指向 Object 原型對象 // 由於實例是一個對象類型的數據,默認會繼承內建對象的原型, // 若是構造函數的原型不知足造成原型鏈的要求,那就跳過直接和內建對象原型關聯 Foo.prototype = 1; var o2 = new Foo("xiaohong"); console.log(o2.__proto__ === Foo.prototype); // false console.log(o2.__proto__ === Object.prototype); // true
若執行 new Foo()
,過程以下:
1)建立新對象 o
;
2)給新對象的內部屬性賦值,關鍵是給[[Prototype]]
屬性賦值,構造原型鏈(若是構造函數的原型是 Object 類型,則指向構造函數的原型;否則指向 Object 對象的原型);
3)執行函數 Foo
,執行過程當中內部 this
指向新建立的對象 o
;
4)若是 Foo
內部顯式返回對象類型數據,則,返回該數據,執行結束;否則返回新建立的對象 o
。
關於一個數據是不是 Object
類型,能夠經過 instanceof
操做符進行判斷:若是 x instanceof Object
返回 true
,則 x
爲 Object
類型。
由上可知,null instanceof Object
返回 false
,因此 null
不是 Object
類型,儘管typeof null
返回 "Object"。
instanceof
的工做原理是:在表達式 x instanceof Foo
中,若是 Foo
的原型(即 Foo.prototype
)出如今 x
的原型鏈中,則返回 true
,否則,返回 false
。
由於函數的原型能夠被改寫,因此會出如今 x
經過 Foo
new 出來以後徹底改寫 Foo
的原型 x instanceof Foo
返回 false
的狀況。由於實例建立以後重寫構造函數原型,實例指向的原型已經不是構造函數的新的原型了,見下面代碼:
const Foo = function() {}; const o = new Foo(); o instanceof Foo; // true // 重寫 Foo 原型 Foo.prototype = {}; o instanceof Foo; // false
What values can a constructor return to avoid returning this? [[Construct]]
internal method