JavaScript 中常常運用組織函數建立對象(經由歷程 new
操作符挪用一個函數),那在運用 new
挪用一個函數的時刻究竟發生了什麼?先看幾個例子,再詮釋背地發生了什麼。
1)看三個例子
1.1 無 return 語句
組織函數末了沒有 return
語句,這也是運用組織函數時默許狀況,末了會返回一個新對象,以下:
function Foo(age) {
this.age = age;
}
var o = new Foo(111);
console.log(o);
這是罕見的運用組織函數建立對象的歷程,打印出來的是 {age: 111}
。
1.2 return 對象範例數據
組織函數末了 return
對象範例數據:
function Foo(age) {
this.age = age;
return { type: "我是顯式返回的" };
}
var o = new Foo(222);
console.log(o);
打印出來的是 {type: '我是顯式返回的'}
,也就是說,return
之前的事情都白做了,末了返回 return
背面的對象。
1.3 return 基礎範例數據
那是不是只需組織函數體內末了有 return
,返回都是 return
背面的數據呢?
我們看下返回基礎範例數據的狀況:
function Foo(age) {
this.age = age;
return 1;
}
var o = new Foo(333);
console.log(o);
打印出來的是 {age: 333}
,和沒有 return
時結果一樣。跟預期不一樣,背地你道理看下面剖析。
2)背地道理
2.1 非箭頭函數的狀況
當運用 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:
- Let obj be a newly created native ECMAScript object.
- Set all the internal methods of obj as specified in 8.12.
- Set the [[Class]] internal property of obj to Object.
- Set the [[Extensible]] internal property of obj to true.
- Let proto be the value of calling the [[Get]] internal property of F with argument “prototype”.
- If Type(proto) is Object, set the [[Prototype]] internal property of obj to proto.
- If Type(proto) is not Object, set the [[Prototype]] internal property of obj to the standard built-in Object prototype object as described in 15.2.4.
- Let result be the result of calling the [[Call]] internal property of F, providing obj as the this value and providing the argument list passed into [[Construct]] as args.
- If Type(result) is Object then return result.
- Return obj.
看第 8、9 步:
8)挪用函數
F
,將其返回值賦給
result
;个中,
F
實行時的實參為傳遞給
[[Construct]]
(即
F
自身) 的參數,
F
內部
this
指向
obj
;9)假如
result
是
Object
範例,返回
result
;
這也就詮釋了假如組織函數顯式返回對象範例,則直接返回這個對象,而不是返回最最先建立的對象。
末了在看第 10 步:
10)假如
F
返回的不是對象範例(第 9 步不成立),則返回建立的對象
obj
。
假如組織函數沒有顯式返回對象範例(顯式返回基礎數據範例或許直接不返回),則返回最最先建立的對象。
2.2 箭頭函數的狀況
那假如組織函數是箭頭函數怎麼辦?
箭頭函數中沒有 [[Construct]]
要領,不能運用 new
挪用,會報錯。
NOTICE:个中 [[Construct]]
就是指組織函數自身。
相干範例在
ES6 的官方文檔 中有提,但自從 ES6 以來的官方文檔巨難明,在此不做表述。
3)new 挪用函數完整歷程
3.1 中文形貌及相干代碼剖析
除了箭頭函數以外的任何函數,都能夠運用 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
3.2 更簡約的言語形貌
若實行 new Foo()
,歷程以下:
1)建立新對象 o
;
2)給新對象的內部屬性賦值,癥結是給[[Prototype]]
屬性賦值,組織原型鏈(假如組織函數的原型是 Object 範例,則指向組織函數的原型;不然指向 Object 對象的原型);
3)實行函數 Foo
,實行歷程當中內部 this
指向新建立的對象 o
;
4)假如 Foo
內部顯式返回對象範例數據,則,返回該數據,實行完畢;不然返回新建立的對象 o
。
4)幾點申明
4.1 推斷是不是是 Object 範例
關於一個數據是不是是 Object
範例,能夠經由歷程 instanceof
操作符舉行推斷:假如 x instanceof Object
返回 true
,則 x
為 Object
範例。
由上可知,null instanceof Object
返回 false
,所以 null
不是 Object
範例,只管typeof null
返回 “Object”。
4.2 instanceof 道理
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