JavaScript 中創(chuàng)建對(duì)象的方式有很多,比如對(duì)象字面量模式或者使用 Object
創(chuàng)建:
// 創(chuàng)建 obj1 對(duì)象
let obj1 = {
name:"",
showName(){ return this.name }
}
// 創(chuàng)建 obj2 對(duì)象
let obj2 = new Object()
obj2.name = ""
obj2.showName = function(){ return this.name }
使用這兩種方式(特別是對(duì)象字面量方式)創(chuàng)建對(duì)象十分方便,可以拿來(lái)即用。但也有一些缺點(diǎn):
- 過(guò)程過(guò)于繁瑣,如果需要?jiǎng)?chuàng)建多個(gè)對(duì)象,就需要書(shū)寫(xiě)多次創(chuàng)建代碼
- 封裝性不夠,因?yàn)榘凑粘R?guī)理念, 對(duì)象應(yīng)該由一個(gè)公共的接口(類(lèi)、函數(shù))來(lái)進(jìn)行統(tǒng)一創(chuàng)建,需要?jiǎng)?chuàng)建對(duì)象時(shí),直接初始化某個(gè)類(lèi)或者調(diào)用函數(shù)來(lái)進(jìn)行創(chuàng)建。
上面的兩個(gè)缺陷都指向了一點(diǎn):我們需要一個(gè)函數(shù)(類(lèi))來(lái)進(jìn)行對(duì)象創(chuàng)建,基于這個(gè)理念,出現(xiàn)了使用工廠(chǎng)模式來(lái)創(chuàng)建對(duì)象的方式。
工廠(chǎng)模式
工廠(chǎng)模式很簡(jiǎn)單,對(duì)原料(原生 Object
對(duì)象)進(jìn)行一些加工(參數(shù)),然后返回一個(gè)產(chǎn)品(被加工后的對(duì)象)。
function createPerson(name,age){
// 創(chuàng)建一個(gè)原生對(duì)象
let o = new Object()
o.name = name;
o.age = age
return o;
}
如果我們需要?jiǎng)?chuàng)建某個(gè)對(duì)象,只需調(diào)用相應(yīng)的工廠(chǎng)函數(shù):
let p1 = createPerson("MIKE",20)
let p2 = createPerson("JACK",22)
工廠(chǎng)模式的缺點(diǎn)
工廠(chǎng)模式的解決了批量創(chuàng)建對(duì)象的問(wèn)題,但也有一個(gè)明顯的缺點(diǎn):沒(méi)有“類(lèi)”的概念,除了能夠批量創(chuàng)建對(duì)象,無(wú)法對(duì)這些對(duì)象進(jìn)行判斷,無(wú)法知道這些對(duì)象是由誰(shuí)(類(lèi))創(chuàng)建出來(lái)的。基于這個(gè)問(wèn)題,出現(xiàn)了構(gòu)造函數(shù)模式。
構(gòu)造函數(shù)模式
函數(shù)是 JavaScript 中的一等公民,可以做很多事情,其中有一項(xiàng)功能就是可以被 new
操作符調(diào)用。在 ES6 之前,JavaScript 中是沒(méi)有 class
關(guān)鍵字的,于是有了通過(guò) new
操作符來(lái)調(diào)用函數(shù)創(chuàng)建一個(gè)對(duì)象的方式,很明顯,通過(guò) new
操作符調(diào)用的函數(shù)就是所謂的“類(lèi)”。
function Person(name,age){
this.name = name;
this.age = age;
this.intro = function(){
console.log(`name:${this.name},age:${this.age}`)
}
}
接下來(lái)通過(guò) Person
類(lèi)來(lái)創(chuàng)建對(duì)象:
let p1 = new Person("MIKE",20)
let p2 = new Person("JACK","22")
構(gòu)造函數(shù)中的 this
關(guān)鍵字就指向了當(dāng)前被創(chuàng)建的對(duì)象。
通過(guò)這種方式創(chuàng)建對(duì)象以后,我們就可以知道對(duì)象是被哪個(gè)“類(lèi)”創(chuàng)建的了。
p1 instanceof Person //true
p1 instanceof Object //true
p1.constructor === Person //true
當(dāng)對(duì)象被創(chuàng)建后,其會(huì)擁有一個(gè) constructor
屬性,指向其的構(gòu)造函數(shù)。不過(guò)由于 JavaScript 太靈活了,constructor
屬性是可以被修改的,因此通過(guò) constructor
來(lái)對(duì)對(duì)象的類(lèi)進(jìn)行判斷是不準(zhǔn)確的,使用
instanceof
操作符更加可靠。
p1.constructor = Array
p1.constructor // Array
p1.constructor === Person //false
p1 instanceof Person // true
構(gòu)造函數(shù)創(chuàng)建對(duì)象的流程
使用構(gòu)造函數(shù)創(chuàng)建對(duì)象,大概有如下幾個(gè)流程:
- 創(chuàng)建一個(gè)新對(duì)象
- 將構(gòu)造函數(shù)的作用域賦值給這個(gè)對(duì)象(因此
this
就指向了這個(gè)對(duì)象) - 執(zhí)行構(gòu)造函數(shù)中的代碼,為對(duì)象添加屬性方法
- 函數(shù)執(zhí)行完畢,對(duì)象被銷(xiāo)毀
構(gòu)造函數(shù)作為函數(shù)
構(gòu)造函數(shù)本身也是函數(shù),因此其可以作為函數(shù)調(diào)用,由于構(gòu)造函數(shù)中使用 this
關(guān)鍵字,我們可以通過(guò)構(gòu)造函數(shù)為某個(gè)對(duì)象進(jìn)行賦值。this
為哪個(gè)對(duì)象賦值,決定于這個(gè)函數(shù)執(zhí)行時(shí)的上下文。
function Person(name,age){
this.name = name;
this.age = age;
this.intro = function(){
console.log(`name:${this.name},age:${this.age}`)
}
}
let p1 = Person("MIKE","20")
p1 //undefined
window.name //"MIKE"
window.age //"20"
如果不指定函數(shù)的上下文,默認(rèn)為 window
對(duì)象,因此 Person
函數(shù)執(zhí)行時(shí),為 window
對(duì)象添加了屬性和方法。
let o = {}
Person.call(o,"JACK","22")
o //{name:"JACK",age:"22"}
這里明確指定函數(shù)的上下文為對(duì)象 o
后,調(diào)用 Person
函數(shù)就為 o
對(duì)象添加屬性方法了。
構(gòu)造函數(shù)模式的問(wèn)題
使用構(gòu)造函數(shù)模式創(chuàng)建對(duì)象看起來(lái)是個(gè)不錯(cuò)的方式,但這樣有沒(méi)有缺陷呢?也是有的。這個(gè)缺陷就在于每次使用構(gòu)造函數(shù)創(chuàng)建對(duì)象時(shí),都會(huì)為每個(gè)對(duì)象重新創(chuàng)建一份屬性和方法的副本,無(wú)法實(shí)現(xiàn)復(fù)用。
為什么會(huì)這樣呢?因?yàn)闃?gòu)造函數(shù)本質(zhì)也是一個(gè)函數(shù),函數(shù)在運(yùn)行時(shí)會(huì)有一個(gè)獨(dú)立的作用域,創(chuàng)建多個(gè)對(duì)象時(shí)會(huì)多次調(diào)用構(gòu)造函數(shù),并把這些函數(shù)的作用域賦值給對(duì)象,然后為對(duì)象添加屬性。但是這些函數(shù)的作用域是獨(dú)立的,因此我們?cè)跇?gòu)造函數(shù)體內(nèi)所做的任何變量聲明、函數(shù)聲明,都會(huì)在運(yùn)行時(shí)重新創(chuàng)建一次,然后添加到對(duì)象上。
function Person(name,age){
this.name = name;
this.age = age;
this.intro = function(){
console.log(`name:${this.name},age:${this.age}`)
}
}
let p1 = new Person("MIKE","20")
let p2 = new Person("JACK","22")
p1.intro === p2.intro // false
以上就是使用構(gòu)造函數(shù)創(chuàng)建對(duì)象無(wú)法實(shí)現(xiàn)復(fù)用的原因,既然無(wú)法復(fù)用的原因是由于在獨(dú)立作用于中創(chuàng)建變量和函數(shù),那我們把這些變量和函數(shù)放到函數(shù)的獨(dú)立作用于之外不就可以了?確實(shí)如此。
function Person(name,age){
this.name = name
this.age = age
this.intro = intro
}
function intro(){
console.log(`name:${this.name},age:${this.age}`)
}
let p1 = new Person("MIKE","20")
let p2 = new Person("JACK","22")
p1.intro === p2.intro // true
上面的 intro
方法就實(shí)現(xiàn)了代碼復(fù)用,節(jié)約了資源。但這種方式也是有問(wèn)題的:增加了全局變量,而且破壞了封裝性。后面將會(huì)介紹更多創(chuàng)建對(duì)象的方法,一步步進(jìn)行完善。
完。