JavaScript 中經(jīng)常使用構(gòu)造函數(shù)創(chuàng)建對(duì)象(通過 new
操作符調(diào)用一個(gè)函數(shù)),那在使用 new
調(diào)用一個(gè)函數(shù)的時(shí)候到底發(fā)生了什么瞬哼?先看幾個(gè)例子奄薇,再解釋背后發(fā)生了什么。
如果該函數(shù)沒有返回對(duì)象脑豹,則返回this
執(zhí)行函數(shù) Foo郑藏,執(zhí)行過程中內(nèi)部 this 指向新創(chuàng)建的對(duì)象 o;
如果 Foo 內(nèi)部顯式返回對(duì)象類型數(shù)據(jù)瘩欺,則必盖,返回該數(shù)據(jù),執(zhí)行結(jié)束俱饿;不然返回新創(chuàng)建的對(duì)象 o
1)看三個(gè)例子
1.1 無 return 語句
構(gòu)造函數(shù)最后沒有 return
語句歌粥,這也是使用構(gòu)造函數(shù)時(shí)默認(rèn)情況,最后會(huì)返回一個(gè)新對(duì)象拍埠,如下:
function Foo(age) {
this.age = age;
}
var o = new Foo(111);
console.log(o);
復(fù)制代碼
這是常見的使用構(gòu)造函數(shù)創(chuàng)建對(duì)象的過程失驶,打印出來的是 {age: 111}
。
1.2 return 對(duì)象類型數(shù)據(jù)
構(gòu)造函數(shù)最后 return
對(duì)象類型數(shù)據(jù):
function Foo(age) {
this.age = age;
return { type: "我是顯式返回的" };
}
var o = new Foo(222);
console.log(o);
復(fù)制代碼
打印出來的是 {type: '我是顯式返回的'}
枣购,也就是說突勇,return
之前的工作都白做了,最后返回 return
后面的對(duì)象坷虑。
1.3 return 基本類型數(shù)據(jù)
那是不是只要構(gòu)造函數(shù)體內(nèi)最后有 return
甲馋,返回都是 return
后面的數(shù)據(jù)呢?
我們看下返回基本類型數(shù)據(jù)的情況:
function Foo(age) {
this.age = age;
return 1;
}
var o = new Foo(333);
console.log(o);
復(fù)制代碼
打印出來的是 {age: 333}
迄损,和沒有 return
時(shí)效果一樣定躏。跟預(yù)期不一樣,背后你原理看下面分析芹敌。
2)背后原理
2.1 非箭頭函數(shù)的情況
當(dāng)使用 new
操作符創(chuàng)建對(duì)象是痊远,ES5 官方文檔在 函數(shù)定義 一節(jié)中做了如下定義 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)調(diào)用函數(shù)
F
氏捞,將其返回值賦給result
碧聪;其中,F
執(zhí)行時(shí)的實(shí)參為傳遞給[[Construct]]
(即F
本身) 的參數(shù)液茎,F
內(nèi)部this
指向obj
逞姿; 9)如果result
是Object
類型辞嗡,返回result
;
這也就解釋了如果構(gòu)造函數(shù)顯式返回對(duì)象類型滞造,則直接返回這個(gè)對(duì)象续室,而不是返回最開始創(chuàng)建的對(duì)象。
最后在看第 10 步:
10)如果
F
返回的不是對(duì)象類型(第 9 步不成立)谒养,則返回創(chuàng)建的對(duì)象obj
挺狰。
如果構(gòu)造函數(shù)沒有顯式返回對(duì)象類型(顯式返回基本數(shù)據(jù)類型或者直接不返回),則返回最開始創(chuàng)建的對(duì)象买窟。
2.2 箭頭函數(shù)的情況
那如果構(gòu)造函數(shù)是箭頭函數(shù)怎么辦丰泊?
箭頭函數(shù)中沒有 [[Construct]]
方法,不能使用 new
調(diào)用始绍,會(huì)報(bào)錯(cuò)瞳购。
NOTICE:其中 [[Construct]]
就是指構(gòu)造函數(shù)本身。
相關(guān)規(guī)范在 ES6 的官方文檔 中有提疆虚,但自從 ES6 以來的官方文檔巨難懂,在此不做表述满葛。
3)new 調(diào)用函數(shù)完整過程
3.1 中文描述及相關(guān)代碼分析
除了箭頭函數(shù)之外的任何函數(shù)径簿,都可以使用 new
進(jìn)行調(diào)用,背后發(fā)生了什么嘀韧,上節(jié)英文講述的很清楚了篇亭,再用中文描述如下:
1)創(chuàng)建 ECMAScript 原生對(duì)象 obj
;
2)給 obj
設(shè)置原生對(duì)象的內(nèi)部屬性锄贷;(和原型屬性不同译蒂,內(nèi)部屬性表示為 [[PropertyName]]
,兩個(gè)方括號(hào)包裹屬性名谊却,并且屬性名大寫柔昼,比如常見 [[Prototype]]
、[[Constructor]]
)
3)設(shè)置 obj
的內(nèi)部屬性 [[Class]]
為 Object
炎辨;
4)設(shè)置 obj
的內(nèi)部屬性 [[Extensible]]
為 true
捕透;
5)將 proto
的值設(shè)置為 F
的 prototype
屬性值;
6)如果 proto
是對(duì)象類型碴萧,則設(shè)置 obj
的內(nèi)部屬性 [[Prototype]]
值為 proto
乙嘀;(進(jìn)行原型鏈關(guān)聯(lián),實(shí)現(xiàn)繼承的關(guān)鍵)
7)如果 proto
是不對(duì)象類型破喻,則設(shè)置 obj
的內(nèi)部屬性 [[Prototype]]
值為內(nèi)建構(gòu)造函數(shù) Object 的 prototype
值虎谢;(函數(shù) prototype
屬性可以被改寫,如果改成非對(duì)象類型曹质,obj
的 [[Prototype]]
就指向 Object 的原型對(duì)象)
8)9)10)見上節(jié)分析婴噩。(決定返回什么)
對(duì)于第 7 步的情況擎场,見下面代碼:
function Foo(name) {
this.name = name;
}
var o1 = new Foo("xiaoming");
console.log(o1.__proto__ === Foo.prototype); // true
// 重寫構(gòu)造函數(shù)原型屬性為非對(duì)象類型,實(shí)例內(nèi)部 [[Prototype]] 屬性指向 Object 原型對(duì)象
// 因?yàn)閷?shí)例是一個(gè)對(duì)象類型的數(shù)據(jù)讳推,默認(rèn)會(huì)繼承內(nèi)建對(duì)象的原型顶籽,
// 如果構(gòu)造函數(shù)的原型不滿足形成原型鏈的要求,那就跳過直接和內(nèi)建對(duì)象原型關(guān)聯(lián)
Foo.prototype = 1;
var o2 = new Foo("xiaohong");
console.log(o2.__proto__ === Foo.prototype); // false
console.log(o2.__proto__ === Object.prototype); // true
復(fù)制代碼
3.2 更簡潔的語言描述
若執(zhí)行 new Foo()
银觅,過程如下:
1)創(chuàng)建新對(duì)象 o
礼饱;
2)給新對(duì)象的內(nèi)部屬性賦值,關(guān)鍵是給[[Prototype]]
屬性賦值究驴,構(gòu)造原型鏈(如果構(gòu)造函數(shù)的原型是 Object 類型镊绪,則指向構(gòu)造函數(shù)的原型;不然指向 Object 對(duì)象的原型)洒忧;
3)執(zhí)行函數(shù) Foo
蝴韭,執(zhí)行過程中內(nèi)部 this
指向新創(chuàng)建的對(duì)象 o
;
4)如果 Foo
內(nèi)部顯式返回對(duì)象類型數(shù)據(jù)熙侍,則榄鉴,返回該數(shù)據(jù),執(zhí)行結(jié)束蛉抓;不然返回新創(chuàng)建的對(duì)象 o
庆尘。
4)幾點(diǎn)說明
4.1 判斷是否是 Object 類型
關(guān)于一個(gè)數(shù)據(jù)是否是 Object
類型,可以通過 instanceof
操作符進(jìn)行判斷:如果 x instanceof Object
返回 true
巷送,則 x
為 Object
類型驶忌。
由上可知,null instanceof Object
返回 false
笑跛,所以 null
不是 Object
類型付魔,盡管typeof null
返回 "Object"。
4.2 instanceof 原理
instanceof
的工作原理是:在表達(dá)式 x instanceof Foo
中飞蹂,如果 Foo
的原型(即 Foo.prototype
)出現(xiàn)在 x
的原型鏈中几苍,則返回 true
,不然陈哑,返回 false
擦剑。
因?yàn)楹瘮?shù)的原型可以被改寫,所以會(huì)出現(xiàn)在 x
通過 Foo
new 出來之后完全改寫 Foo
的原型 x instanceof Foo
返回 false
的情況芥颈。因?yàn)閷?shí)例創(chuàng)建之后重寫構(gòu)造函數(shù)原型惠勒,實(shí)例指向的原型已經(jīng)不是構(gòu)造函數(shù)的新的原型了,見下面代碼:
const Foo = function() {};
const o = new Foo();
o instanceof Foo; // true
// 重寫 Foo 原型
Foo.prototype = {};
o instanceof Foo; // false
作者:L小庸
鏈接:https://juejin.im/post/5b397b526fb9a00e5d7999a4
來源:掘金
著作權(quán)歸作者所有爬坑。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)纠屋,非商業(yè)轉(zhuǎn)載請注明出處。