最近在掘金上看到大佬 yck
的文章 重學(xué) JS 系列:聊聊 new 操作符
把new操作符的作用,如何自己實(shí)現(xiàn)new操作符寫得很清晰,遂把核心思想整理過來,加強(qiáng)理解彤蔽。
先通過下面的代碼,了解一下new
的作用:
/* 選自 yck 文章 */
function Test(name) {
this.name = name
}
Test.prototype.sayName = function () {
console.log(this.name)
}
const t = new Test('yck')
console.log(t.name) // 'yck'
t.sayName() // 'yck'
總結(jié)一下new
的作用
- new 通過構(gòu)造函數(shù) Test 創(chuàng)建出來的實(shí)例可以訪問到構(gòu)造函數(shù)中的屬性
- new 通過構(gòu)造函數(shù) Test 創(chuàng)建出來的實(shí)例可以訪問到構(gòu)造函數(shù)原型鏈中的屬性蚌斩,也就是說通過 new 操作符铆惑,實(shí)例與構(gòu)造函數(shù)通過原型鏈連接了起來
自己實(shí)現(xiàn)new操作符
首先我們?cè)賮砘仡櫹?new 操作符的幾個(gè)作用:
- new 操作符會(huì)返回一個(gè)對(duì)象,所以我們需要在內(nèi)部創(chuàng)建一個(gè)對(duì)象
- 這個(gè)對(duì)象送膳,也就是構(gòu)造函數(shù)中的 this,可以訪問到掛載在 this 上的任意屬性
- 這個(gè)對(duì)象可以訪問到構(gòu)造函數(shù)原型上的屬性丑蛤,所以需要將對(duì)象與構(gòu)造函數(shù)鏈接起來
- 返回原始值需要忽略叠聋,返回對(duì)象需要正常處理
function create(Con, ...args) {
let obj = {}
Object.setPrototypeOf(obj, Con.prototype)
let result = Con.apply(obj, args)
return result instanceof Object ? result : obj
}
這就是一個(gè)完整的實(shí)現(xiàn)代碼,我們通過以下幾個(gè)步驟實(shí)現(xiàn)了它:
1.首先函數(shù)接受不定量的參數(shù)受裹,第一個(gè)參數(shù)為構(gòu)造函數(shù)碌补,接下來的參數(shù)被構(gòu)造函數(shù)使用
2.然后內(nèi)部創(chuàng)建一個(gè)空對(duì)象 obj
3.因?yàn)?obj 對(duì)象需要訪問到構(gòu)造函數(shù)原型鏈上的屬性,所以我們通過 setPrototypeOf 將兩者聯(lián)系起來棉饶。這段代碼等同于 obj.proto = Con.prototype
4.將 obj 綁定到構(gòu)造函數(shù)上厦章,并且傳入剩余的參數(shù)
5.判斷構(gòu)造函數(shù)返回值是否為對(duì)象,如果為對(duì)象就使用構(gòu)造函數(shù)返回的值照藻,否則使用 obj袜啃,這樣就實(shí)現(xiàn)了忽略構(gòu)造函數(shù)返回的原始值
接下來我們來使用下該函數(shù),看看行為是否和 new 操作符一致
function Test(name, age) {
this.name = name
this.age = age
}
Test.prototype.sayName = function () {
console.log(this.name)
}
const a = create(Test, 'yck', 26)
console.log(a.name) // 'yck'
console.log(a.age) // 26
a.sayName() // 'yck'
原文:
new一般后面跟著一個(gè)構(gòu)造函數(shù)幸缕,比如我們熟悉的new Object(),new Array()
...
我們知道 new
會(huì)創(chuàng)造出一個(gè)實(shí)例群发,這個(gè)實(shí)例(對(duì)象)會(huì)有一個(gè)__proto__
屬性晰韵,這個(gè)屬性指向了這個(gè)構(gòu)造函數(shù)的共有屬性。那么,new 構(gòu)造函數(shù)
時(shí)發(fā)生了什么呢熟妓?我們看一下:
我們自己寫一個(gè)構(gòu)造函數(shù)
create士兵()
,它的結(jié)果是返回一個(gè)具有自有屬性和共有屬性的對(duì)象雪猪,自有屬性是由用戶傳進(jìn)來的,所有的共有屬性作為一個(gè)對(duì)象起愈,士兵的__proto__
會(huì)指向這個(gè)對(duì)象只恨。但如果按照上面的函數(shù),每次new一個(gè)對(duì)象的時(shí)候抬虽,就會(huì)在內(nèi)存里產(chǎn)生一個(gè)共有屬性坤次,這是很浪費(fèi)內(nèi)存的;如果把紅色方框的共有屬性對(duì)象移到函數(shù)外面斥赋,再把它的賦給this.__proto__
缰猴,構(gòu)造函數(shù)和其共有屬性聯(lián)系就會(huì)變得不那么緊密,一旦原型受到影響疤剑,構(gòu)造函數(shù)create士兵()
相當(dāng)于就沒有用了滑绒。JS之父最后想到的辦法是:把共有屬性對(duì)象存到構(gòu)造函數(shù)
create士兵()
的prototype屬性中。
當(dāng)new Function()
時(shí)隘膘,其實(shí)發(fā)生了:創(chuàng)建一個(gè)臨時(shí)對(duì)象{}疑故,將{}賦給this,把Function.prototype賦給this.__proto__
弯菊,上面幾步是不用我們自己寫的纵势,我們只需在new的時(shí)候,把自有屬性的值傳進(jìn)去管钳,就可以了钦铁。
另外,F(xiàn)unction.prototype最初是存有
constructor: Function
這個(gè)屬性的才漆,表示其構(gòu)造函數(shù)是Function牛曹。我們?cè)诰帉懸粋€(gè)構(gòu)造函數(shù)的共有屬性時(shí),要注意不能把它覆蓋掉醇滥。要么采用Function.prototype.xxx=XXX的形式賦值黎比,要么當(dāng)把共有屬性的集合作為一個(gè)對(duì)象賦給Function.prototype時(shí),要把constructor: Function
也寫進(jìn)去鸳玩。總結(jié)
new操作符創(chuàng)建對(duì)象可以分為四個(gè)步驟:
1、創(chuàng)建一個(gè)空對(duì)象不跟,這個(gè)對(duì)象的類型是object
2颓帝、將所創(chuàng)建的對(duì)象的proto屬性值設(shè)為prototype屬性值(關(guān)系:instance.constructor.prototype=instance.proto)
3、執(zhí)行構(gòu)造函數(shù)中的代碼,構(gòu)造函數(shù)中的this指向該對(duì)象
4躲履、返回該對(duì)象