創(chuàng)建對象
創(chuàng)建對象的方式有以下幾種:最簡單的就是對象字面量形式。
工廠模式
說起工廠模式這個稱呼的由來蚓庭,可以根據(jù)它的書寫方式來理解:
function factory(name, speed) {
var obj = new Object()
obj.name = name
obj.speed = speed
return obj
}
var car1 = factory('保時捷', 300)
var car2 = factory('保時捷', 280)
car1.constructor // function Object() {}
car2.constructor // function Object() {}
如上代碼所示,你可以把factory()
當(dāng)成一個生產(chǎn)保時捷的工廠仅仆,你可以給他傳入一些零件(即參數(shù))器赞,工廠內(nèi)部就可以根據(jù)你傳入的零件,在一個車架的基礎(chǔ)上(即 new Object()
)墓拜,建造一輛汽車港柜。當(dāng)你提起這個工廠,自然而然就跟保時捷劃等號了。
不過夏醉,有可能好幾個工廠同時生產(chǎn)同一版本的車爽锥,單從外表你是不知道它到底是哪個工廠生產(chǎn)出來的,只知道都長的差不多畔柔。
模式存在的問題
這也就是工廠模式的問題氯夷,不能確定對象屬于哪一類。
構(gòu)造函數(shù)模式
都知道靶擦,可以用構(gòu)造函數(shù)來創(chuàng)建對象腮考。可以用原生構(gòu)造函數(shù)也可以用自定義的構(gòu)造函數(shù)玄捕。相對于工廠模式來創(chuàng)建對象的方式踩蔚,構(gòu)造函數(shù)不會顯示的創(chuàng)建一個對象,而且不會直接在對象上添加屬性和方法枚粘,而是通過this對象馅闽,最后,并沒有return語句馍迄。
function Person(name, age, sex) {
this.name = name
this.age = age
this.sex = sex
this.say = function() {
console.log(this.name)
}
}
var person1 = new Person('zhd', 18, 'boy')
var person2 = new Person('zy', 20, 'girl')
如上所示福也,,通過Person()
這個構(gòu)造函數(shù)生成了person1
和person2
兩個對象柬姚,也就是兩個構(gòu)造函數(shù)的實例拟杉,這兩個實例的屬性和方法都來自于 Person()
構(gòu)造函數(shù)。不過量承,重點是new
操作符搬设。
首先來看一下new
操作符都干了些啥?
new操作符干了啥
1)最直接的就是通過new
操作符創(chuàng)建了一個新對象
2)將構(gòu)造函數(shù)內(nèi)部this的作用域指向了創(chuàng)建的這個新的對象
3)將構(gòu)造函數(shù)內(nèi)部的屬性和方法添加到新對象中
4)返回這個新對象撕捍,并將這個對象當(dāng)做值賦給變量person1
和person2
回到工廠模式存在的問題拿穴,構(gòu)造函數(shù)創(chuàng)建的對象是否就知道是哪里一類嗎?試驗一下:
person1.constructor // function Person() {}
person2.constructor // function Person() {}
從上面代碼來看忧风,確實解決了工廠模式創(chuàng)建對象的問題默色。不過也可以用另一種方式實驗:
person1 instanceof Person // true
person1 instanceof Object // true
person2 instanceof Person // true
person2 instanceof Object // true
如果把構(gòu)造函數(shù)當(dāng)做正常的一個函數(shù)來調(diào)用呢,那內(nèi)部的屬性和方法都跑哪里去了狮腿?實際上腿宰,如果不通過new
調(diào)用構(gòu)造函數(shù)的話,直接運(yùn)行構(gòu)造函數(shù)缘厢,會把構(gòu)造函數(shù)內(nèi)部的屬性和方法放在window
上吃度。
工廠模式創(chuàng)建的對象不知道屬于哪種類型,那構(gòu)造函數(shù)有什么問題呢贴硫?
通過new
確實會創(chuàng)建一個對象椿每,不過沒創(chuàng)建一個對象伊者,都會把構(gòu)造函數(shù)內(nèi)部所有的方法都添加到新對象上,這樣间护,如果基于這個構(gòu)造函數(shù)創(chuàng)建很多對象亦渗,每個方法都會在實例內(nèi)部創(chuàng)建一遍,但是每個實例內(nèi)部的方法干的都是同一件事汁尺,不僅造成了多余方法的冗余法精,也無形增加了內(nèi)存消耗。
模式存在的問題
每個方法都要在實例上重新創(chuàng)建一遍
那有什么解決的辦法呢均函?實際上亿虽,創(chuàng)建的對象內(nèi)部的方法菱涤,對于實例來說干的都是同一件事苞也,那為什么不寫成公用的一個方法呢?如下:
function Person(name, age, sex) {
this.name = name
this.age = age
this.sex = sex
this.say = say
}
function say () {
console.log(this.name)
}
但是這種解決方案帶來了一個無法讓人接受的問題:那就是在做封裝的時候粘秆,通常會定義很多方法如迟,如果這個對象需要定義很多方法究西,也就是定義很多全局變量堵漱,這樣既沒有封裝性可言戈鲁,而且增加了全局變量细卧。
原型模式
上面提到的構(gòu)造函數(shù)的問題瑞躺,可以通過第三種模式树叽,原型模式解決它镐捧。
function Person() {
}
Person.prototype.name = 'zhd'
Person.prototype.age = 18
Person.prototype.sex = 'boy'
Person.prototype.say = function() {
console.log(this.name)
}
var person1 = new Person()
var person2 = new Person()
person1.say() // 'zhd'
person2.say() // 'zy'
通過以上的方式就可以看出支救,將所有實例共用的方法添加到構(gòu)造函數(shù)的原型上摘符,這樣既減少了定義函數(shù)的次數(shù)贤斜,又沒有增加全局變量。
理解原型
這里只簡單說幾點
1)除了函數(shù)逛裤,其他所有對象都沒有prototype
屬性瘩绒。
2)所有對象都有一個_proto_
屬性,指向構(gòu)造函數(shù)的原型带族。
3)除了null和undefined锁荔,其他所有對象都有constructor
屬性。
相關(guān)的方法
isPrototypeOf()
:通過這個方法可以確定一個對象是否是另一個對象的原型蝙砌。
例如:
Object.prototype.isPrototypeOf(person1)
// true
hasOwnProperty()
:通過這個方法可以檢測一個屬性是否存在于實例中還是原型中阳堕。這個方法接收一個屬性名作為參數(shù),由所要查找屬性所在的對象調(diào)用择克。
例如:
person1.__proto__.hasOwnProperty('name')
// true
person1.hasOwnProperty('name')
// false
Object.getPrototypeOf()
:通過這個方法可以返回一個對象的原型對象恬总。
例如:
Object.getPrototypeOf(person1)
// {name:'zhd', age:18, sex:'boy', say:function(){...}}
Object.keys()
:這個方法返回的一個對象上所有可枚舉實例屬性,這個方法接收一個對象作為參數(shù)祠饺,返回一個包含所有屬性的字符串?dāng)?shù)組越驻。
例如:
Object.keys(Person.prototype)
// ["name", "age", "sex", "say"]
Object.getOwnPropertyNames():上面說的Object.keys()方法返回的是所有可枚舉的實例屬性,相反,這個方法更全面缀旁。返回的是所有屬性记劈,不管屬性是否可枚舉
例如:
Object.getOwnPropertyNames(Person.prototype)
// ["constructor", "name", "age", "sex", "say"]
這里有一點需要提醒的是,如果實例和原型存在相同的一個屬性或者方法并巍,實例的屬性或方法會覆蓋原型上的屬性或方法目木。
更簡單的原型語法
上面提到的原型模式,如果每添加一個屬性或方法懊渡,就要寫一遍Person.prototype
,為了更優(yōu)雅的封裝功能刽射,我們可以改變Person
的prototype
。
例如:
function Person() {}
Person.prototype = {
name: 'zhd',
age: 18,
sex: 'boy',
say: function() {
console.log(this.name)
}
}
這樣的寫法是不是就輕松多了剃执。不過這樣方式修改了Person
的原型對象誓禁,能帶來什么問題嗎?如下:
var person1 = new Person()
person1.constructor == Person // false
為什么會是false呢肾档?
之前介紹過摹恰,一旦創(chuàng)建一個函數(shù)怒见,就會同時創(chuàng)建它的prototype
對象,這個對象也會自動獲得一個constructor
屬性遣耍。就像上面一樣闺阱,一旦聲明一個Person
構(gòu)造函數(shù),就同時創(chuàng)建了一個它的原型對象({constructor:function() {}}
),同時這個原型對象也會獲得一個constructor
屬性酣溃。
所以這種寫法,聲明一個構(gòu)造函數(shù)以后棋傍,又修改了它的原型對象救拉,因此瘫拣,修改后的原型對象內(nèi)部的constructor
也不再指向Person
了,而是指向了原生構(gòu)造函數(shù)function Object() {}
麸拄。
為了解決這種寫法帶來的問題派昧,我們可以在改變構(gòu)造函數(shù)的原型對象后,手動的在原型對象中添加一個constructor
屬性拢切,其屬性值為Person
蒂萎。如下:
Person.prototype = {
constuctor: Person,
name: 'zhd',
age: 18,
sex: 'boy',
say: function() {
console.log(this.name)
}
}
我們這樣手動添加constructor
屬性確實解決了寫法帶來的問題。不過constructor
這個屬性本身是不可枚舉的淮椰,我們可以用Object.defineProperty()
方法來改變它的特性五慈。如下:
Object.definePropety(Person.prototype, 'constructor', {
enumerable: false,
value: Person
})
in 操作符的使用
有兩種方式使用in
操作符:
1)單獨使用纳寂,如propertyName in object
- 在
for-in
循環(huán)中使用
不過單獨使用in操作符,有一個缺點泻拦,如下:
function Person () {}
Person.prototype.name = 'zhd'
console.log('name' in Person) // true
所以毙芜,當(dāng)單獨用in
操作符檢測一個屬性是否在目標(biāo)對象上,結(jié)果并不準(zhǔn)確争拐,如果屬性在目標(biāo)對象的原型上腋粥,也會返回true
。
所以架曹,搭配hasOwnProperty()
方法一起使用隘冲,就可以確定要檢測的屬性是否在對象上還是在原型上。
模式存在的問題
首先绑雄,從原型模式的寫法上可以看出展辞,省略了構(gòu)造函數(shù)的傳參和初始化這一環(huán)節(jié),結(jié)果是所有實例會默認(rèn)取得相同的屬性和方法绳慎,增加了冗余纵竖。
但是,原型模式最大的問題是在共享的本質(zhì)杏愤。尤其是對于包含引用類型值得屬性。
如下:
function Person () {}
Person.prototype = {
constructor: Person,
name: ['person1', 'person2']
}
let person1 = new Person()
let person2 = new Person()
person1.name.push('person3')
console.log(person2.name) // ['person1', 'person2', 'person3']
開發(fā)者的意思只是修改一下person1
這個實例的name
屬性已脓,無意修改person2
這個實例的name
屬性珊楼。如果說這兩個實例共享的是一個name
,那就沒問題度液,如果是每個實例獨享一個name
屬性厕宗,并做相應(yīng)的業(yè)務(wù)邏輯處理,那豈不是翻天了堕担。為了避免這方面的問題已慢,建議構(gòu)造函數(shù)模式和原型模式組合使用。
構(gòu)造函數(shù)模式和原型模式組合使用
function Person(name, age, sex,fn) {
this.name = name
this.age = age
this.sex = sex
this.fn = fn
}
Person.prototype.sayName = function() {
console.log(this.name)
}
let person1 = new Person('zhd','18','boy')
這種組合模式的使用霹购,既照顧到了每個實例私有的屬性和方法,又把實例共享的方法或?qū)傩苑旁诹嗽蜕厦嬗踊荩慌e兩得。在這先提一下:也推薦借用構(gòu)造函數(shù)和原型鏈組合的繼承方式齐疙。這一點會在后面說出膜楷。
動態(tài)原型模式
其實,動態(tài)原型模式很簡單贞奋,基本上相當(dāng)于構(gòu)造函數(shù)模式和原型模式的組合使用赌厅,區(qū)別就是加了一個if
判斷,而且把原型模式寫在了構(gòu)造函數(shù)內(nèi)部轿塔。如下:
function Person(name) {
this.name = name
if(typeof this.sayName != 'function') {
Person.prototype.sayName = function() {
console.log(this.name)
}
}
}
之前提到了構(gòu)造函數(shù)模式和原型模式的組合模式的使用特愿,而動態(tài)原型模式其實也是一樣仲墨,只是判斷了一下構(gòu)造函數(shù)內(nèi)部有沒有對象的方法,沒有的話直接把方法放到原型上揍障。
其實宗收,講到這里,項目中常用的方法就已經(jīng)說的差不多了亚兄。創(chuàng)建對象無非就是以上的幾種方法混稽。不過還有兩種,不常用审胚,但咱們一起來了解一下匈勋。
寄生構(gòu)造函數(shù)模式(工廠構(gòu)造模式)
其實,剛開始看到這一個模式的寫法的時候膳叨,我感覺與其叫寄生構(gòu)造函數(shù)模式洽洁,不如叫工廠構(gòu)造模式更好理解,為啥這么說呢菲嘴,我們來看一看它的寫法:
function Person(name, age, sex) {
var o = new Object()
o.name = name
o.age = age
o.sex = sex
o.joins = function() {
this.name = '111'
}
return o
}
let person1 = new Person('zhd','19','boy')
對工廠模式和構(gòu)造函數(shù)模式特別熟悉的同學(xué)一看就知道饿自,這不就是工廠模式和構(gòu)造函數(shù)模式組合使用嗎×淦海或者說昭雌,在工廠模式的基礎(chǔ)上,生成對象的時候健田,在構(gòu)造函數(shù)前面加了一個new
操作符烛卧,僅此而已妓局。
然后,我啥也不說好爬,咱們先看一段代碼
let person1 = new Person('zhd','19','boy')
let person2 = Person('zhd','19','boy')
console.log(person1) // {name: 'zhd', age:19,sex:'boy',joins:function(){}}
console.log(person2) // {name: 'zhd', age:19,sex:'boy',joins:function(){}}
從上面可以看出,無論是從寄生構(gòu)造函數(shù)模式創(chuàng)建的對象存炮,還是從工廠模式常見的對象,從數(shù)據(jù)結(jié)構(gòu)上來說僵蛛,沒有什么不同。也就是說充尉,構(gòu)造函數(shù)內(nèi)部返回的對象與構(gòu)造函數(shù)本身是沒有關(guān)系的,無論是否用new
操作符驼侠,創(chuàng)建的對象都是一樣的谆吴,跟構(gòu)不構(gòu)造沒關(guān)系苛预。所以在此,用 instanceof
來確定一個對象從哪個對象來的是不正確的热某。
如:person1 instanceof Person: // false
穩(wěn)妥構(gòu)造函數(shù)模式
從這個模式的名稱來看腻菇,也是依托于構(gòu)造函數(shù)來創(chuàng)建對象的昔馋。具體的方式來看下面的代碼:
function Person(name, age, sex) {
let o = new Object()
o.sayName = function() {
console.log(name)
}
return o
}
let person1 = Person('zhd', '18', 'boy')
從官方定義來看,所謂的穩(wěn)妥秘遏,是指通過參數(shù)形式傳入到模式內(nèi)部的實參,沒有方法和屬性能訪問到邦危,除了模式內(nèi)部存在的方法和屬性,你不能人為的添加屬性和方法取訪問它倦蚪。他沒有公共的屬性,而且內(nèi)部也不會通過this
這個對象去訪問參數(shù)审丘。
從書寫方式上來看,同寄生構(gòu)造函數(shù)模式有兩點不同:一是不通過this
訪問數(shù)據(jù)成員,二是不使用new
操作符調(diào)用構(gòu)造函數(shù)來創(chuàng)建對象播急。