JavaScript 中創(chuàng)建對象的方式有很多,比如對象字面量模式或者使用 Object
創(chuàng)建:
// 創(chuàng)建 obj1 對象
let obj1 = {
name:"",
showName(){ return this.name }
}
// 創(chuàng)建 obj2 對象
let obj2 = new Object()
obj2.name = ""
obj2.showName = function(){ return this.name }
使用這兩種方式(特別是對象字面量方式)創(chuàng)建對象十分方便耸携,可以拿來即用棵癣。但也有一些缺點:
- 過程過于繁瑣,如果需要創(chuàng)建多個對象夺衍,就需要書寫多次創(chuàng)建代碼
- 封裝性不夠狈谊,因為按照常規(guī)理念, 對象應(yīng)該由一個公共的接口(類沟沙、函數(shù))來進行統(tǒng)一創(chuàng)建河劝,需要創(chuàng)建對象時,直接初始化某個類或者調(diào)用函數(shù)來進行創(chuàng)建矛紫。
上面的兩個缺陷都指向了一點:我們需要一個函數(shù)(類)來進行對象創(chuàng)建丧裁,基于這個理念,出現(xiàn)了使用工廠模式來創(chuàng)建對象的方式含衔。
工廠模式
工廠模式很簡單煎娇,對原料(原生 Object
對象)進行一些加工(參數(shù)),然后返回一個產(chǎn)品(被加工后的對象)贪染。
function createPerson(name,age){
// 創(chuàng)建一個原生對象
let o = new Object()
o.name = name;
o.age = age
return o;
}
如果我們需要創(chuàng)建某個對象缓呛,只需調(diào)用相應(yīng)的工廠函數(shù):
let p1 = createPerson("MIKE",20)
let p2 = createPerson("JACK",22)
工廠模式的缺點
工廠模式的解決了批量創(chuàng)建對象的問題,但也有一個明顯的缺點:沒有“類”的概念杭隙,除了能夠批量創(chuàng)建對象哟绊,無法對這些對象進行判斷,無法知道這些對象是由誰(類)創(chuàng)建出來的痰憎∑彼瑁基于這個問題,出現(xiàn)了構(gòu)造函數(shù)模式铣耘。
構(gòu)造函數(shù)模式
函數(shù)是 JavaScript 中的一等公民洽沟,可以做很多事情,其中有一項功能就是可以被 new
操作符調(diào)用蜗细。在 ES6 之前裆操,JavaScript 中是沒有 class
關(guān)鍵字的怒详,于是有了通過 new
操作符來調(diào)用函數(shù)創(chuàng)建一個對象的方式,很明顯踪区,通過 new
操作符調(diào)用的函數(shù)就是所謂的“類”昆烁。
function Person(name,age){
this.name = name;
this.age = age;
this.intro = function(){
console.log(`name:${this.name},age:${this.age}`)
}
}
接下來通過 Person
類來創(chuàng)建對象:
let p1 = new Person("MIKE",20)
let p2 = new Person("JACK","22")
構(gòu)造函數(shù)中的 this
關(guān)鍵字就指向了當前被創(chuàng)建的對象。
通過這種方式創(chuàng)建對象以后缎岗,我們就可以知道對象是被哪個“類”創(chuàng)建的了静尼。
p1 instanceof Person //true
p1 instanceof Object //true
p1.constructor === Person //true
當對象被創(chuàng)建后,其會擁有一個 constructor
屬性传泊,指向其的構(gòu)造函數(shù)茅郎。不過由于 JavaScript 太靈活了,constructor
屬性是可以被修改的或渤,因此通過 constructor
來對對象的類進行判斷是不準確的,使用
instanceof
操作符更加可靠奕扣。
p1.constructor = Array
p1.constructor // Array
p1.constructor === Person //false
p1 instanceof Person // true
構(gòu)造函數(shù)創(chuàng)建對象的流程
使用構(gòu)造函數(shù)創(chuàng)建對象薪鹦,大概有如下幾個流程:
- 創(chuàng)建一個新對象
- 將構(gòu)造函數(shù)的作用域賦值給這個對象(因此
this
就指向了這個對象) - 執(zhí)行構(gòu)造函數(shù)中的代碼,為對象添加屬性方法
- 函數(shù)執(zhí)行完畢惯豆,對象被銷毀
構(gòu)造函數(shù)作為函數(shù)
構(gòu)造函數(shù)本身也是函數(shù)池磁,因此其可以作為函數(shù)調(diào)用,由于構(gòu)造函數(shù)中使用 this
關(guān)鍵字楷兽,我們可以通過構(gòu)造函數(shù)為某個對象進行賦值地熄。this
為哪個對象賦值,決定于這個函數(shù)執(zhí)行時的上下文芯杀。
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ù)的上下文端考,默認為 window
對象,因此 Person
函數(shù)執(zhí)行時揭厚,為 window
對象添加了屬性和方法却特。
let o = {}
Person.call(o,"JACK","22")
o //{name:"JACK",age:"22"}
這里明確指定函數(shù)的上下文為對象 o
后,調(diào)用 Person
函數(shù)就為 o
對象添加屬性方法了筛圆。
構(gòu)造函數(shù)模式的問題
使用構(gòu)造函數(shù)模式創(chuàng)建對象看起來是個不錯的方式裂明,但這樣有沒有缺陷呢?也是有的太援。這個缺陷就在于每次使用構(gòu)造函數(shù)創(chuàng)建對象時闽晦,都會為每個對象重新創(chuàng)建一份屬性和方法的副本,無法實現(xiàn)復(fù)用提岔。
為什么會這樣呢仙蛉?因為構(gòu)造函數(shù)本質(zhì)也是一個函數(shù),函數(shù)在運行時會有一個獨立的作用域碱蒙,創(chuàng)建多個對象時會多次調(diào)用構(gòu)造函數(shù)捅儒,并把這些函數(shù)的作用域賦值給對象,然后為對象添加屬性。但是這些函數(shù)的作用域是獨立的巧还,因此我們在構(gòu)造函數(shù)體內(nèi)所做的任何變量聲明鞭莽、函數(shù)聲明,都會在運行時重新創(chuàng)建一次麸祷,然后添加到對象上澎怒。
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)建對象無法實現(xiàn)復(fù)用的原因,既然無法復(fù)用的原因是由于在獨立作用于中創(chuàng)建變量和函數(shù)阶牍,那我們把這些變量和函數(shù)放到函數(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
方法就實現(xiàn)了代碼復(fù)用走孽,節(jié)約了資源惧辈。但這種方式也是有問題的:增加了全局變量,而且破壞了封裝性磕瓷。后面將會介紹更多創(chuàng)建對象的方法盒齿,一步步進行完善。
完困食。