創(chuàng)建對象
工廠模式
function createPerson(name){
let o = new Object()
o.name = name
return o
}
let person1 = createPerson('lpj')
let person2 = createPerson('hjy')
缺點:不能共享玷禽、對象標(biāo)識紊亂
- 單純構(gòu)造函數(shù)模式
function Person(name){
this.name = name;
this.sayName = function(){console.log(this.name)}
}
- 確保實例被標(biāo)識為特定類型(通過constructor 但一般使用instanceof操作符)
-
person1 instanceof Object
==person2 instance of Person
==true
缺點:
this.sayName = function(){}
方法其實相當(dāng)于this.sayName = new function(){}
也就是說方法和屬性等價了悔据,每個實例都有一個自己的方法
console.log(person1.sayName == person2.sayName)//false
原型模式
function Person(name){
this.name = name;
}
Person.prototype.sayName = function(){console.log(this.name)}
Person.prototype.job = 'student'
- 每個實例實際上都有[[prototype]]內(nèi)部特性(一個指向構(gòu)造器原型的指針) __ proto__實際上就用于訪問本實例的[[prototype]]而不是直接訪問外部
- 任何自定義的構(gòu)造器函數(shù)的原型都只是單純的函數(shù)對象,由Object()構(gòu)造器創(chuàng)造翼闽,因此原型鏈到最后
Person.prototype.__proto__.__proto__ ===null
- 相關(guān)方法
-
console.log(Person.prototype.isPrototypeOf(person1)//true
直接確認(rèn)原型 -
console.log(Object.getPrototypeOf(person1)== Person.prototype)
console.log(Object.getPrototypeOf(person1).job)//student
用于獲取實例[[Prototype]]的值 -
setPrototypeOf
可以重寫實例proto所指向的原型枫匾,但具有性能問題 -
Object.create()
也可以重寫實例指向的原型哑芹,性能更好花盐,在繼承時可能會用到
-
- 遮蔽(shadow)與確認(rèn)
實例如果有和原型上同名的屬性,會遮蔽原型上的拓挥,使其搜索不到唠梨,可以通過delete該屬性刪除從而恢復(fù)原型屬性的暴露
hasOwnProperty
用于確認(rèn)屬性在原型上還是實例上
in
用于確認(rèn)可枚舉是否屬性在原型或在實例上
Object.keys()
返回所有可枚舉屬性
Object.getOwnPropertyNames()
返回全部屬性包括不可枚舉的
let person1 = new Person()
let person2 = new Person()
console.log(person1.hasOwnProperty("name"))//false 不在實例上
console.log("name" in person1)//true 在原型上
person1.name = 'sb'//遮蔽
console.log(person1.hasOwnProperty("name"))//true 在實例上
console.log("name" in person1)//true 在實例和原型上都有
delete person1.name//取消遮蔽
console.log(person1.hasOwnProperty("name"))//false
- 一些點
- construcor默認(rèn)是不可枚舉屬性 要用
Object.getOwnPropertyNames()
才能返回 -
Object.keys()
和in
在內(nèi)部的枚舉順序是不確定的,取決于引擎侥啤,其余的方法順序則是確定的
- construcor默認(rèn)是不可枚舉屬性 要用
- 原型修改與重寫
-
Person.prototype.any = 'something'
可以直接添加原型的屬性当叭,并且只要添加后,任何情況下都可以訪問(原型的動態(tài)性) - 記住盖灸,實例只有指向原型的指針蚁鳖,沒有指向構(gòu)造器函數(shù)的指針
-
function Person(){}
let friend = new Person
Person.prototype = {
constructor :Person,//注意 如果不寫的話,這里將會從Person變成Object
name:'sb',
say(){console.log('sb')}
}
friend.say()//錯誤
friend指向的原型仍是最初的原型
- 盡管可以修改原生對象原型(如Array糠雨、String的)才睹,但不建議這么做,應(yīng)使用es6的自定義類來繼承原生類型
缺點:
- 弱化了構(gòu)造函數(shù)傳遞初始化參數(shù)的能力例如
Person.prototype.job = 'student'
會被所有實例繼承甘邀,這對函數(shù)(方法)來說還可以接受琅攘,但屬性往往不需要。- 共享了屬性松邪,導(dǎo)致如果你
person1.job = 'teacher'
的話,person2.job
也會改變
↑這里是錯誤的,number和string都不會改變坞琴,這些屬性是獨立拷貝的,但如果是Array逗抑,顯然由于引用類型的原因剧辐,你push一個新元素進去,會導(dǎo)致共享的全都被修改
↑注意:“獨立拷貝是錯的”邮府,原型就是只有一份荧关,這里之所以看起來person2.job不變是因為添加了person1的實例屬性job,原型被遮蔽了褂傀,而person2未被遮蔽
總之忍啤,不能純粹使用原型模式,該寫構(gòu)造器里就寫構(gòu)造器里
↑對于num和str來說仙辟,寫在構(gòu)造器里可以解決這個問題同波,但對于Array等引用來說鳄梅,寫在構(gòu)造器里還是一樣的,必須采用盜用構(gòu)造器
↑草 寫在構(gòu)造器里還真不一樣 其實很顯然 因為寫在構(gòu)造器里就是new了新的
function Animal(){
this.foot = 4
this.colors = ['white','black']
}
let cow = new Animal
cow.colors.push("yellow")
cow.foot = 2
let cat = new Animal
console.log(cow.foot+cow.colors)//2white,black,yellow
console.log(cat.foot+cat.colors)//4white,balck
真正的缺點應(yīng)該稱作:原型包含引用值從而在所有實例間將會共享未檩,這個缺點將在繼承中顯著體現(xiàn)
繼承
為什么要繼承戴尸?
為了讓一個實例能得到多個引用類型(Person、Object等)的屬性和方法冤狡,提升面向?qū)ο蟮哪芰λ锩伞⑻岣呖勺x性、降低代碼復(fù)雜度
- 基本繼承
function Animal(){
this.foot = 4
}
Animal.prototype.sayMyFoot = function(){
console.log("my foot has "+this.foot)
}
function Cat(name){
this.name = name
}
Cat.prototype = new Animal()//開始繼承
Cat.prototype.sayMyName = function(){
console.log("my name is "+this.name)
}
let mimi = new Cat("mimi")
mimi.sayMyName()
mimi.sayMyFoot()
//實際上mimi還繼承了所有Object()原型的方法 console.log(mimi.__proto__.__proto__.__proto__.__proto__===null)//ture
注意:Cat.prototype
如果使用字面量重寫的話筒溃,會導(dǎo)致原型鏈斷開马篮,因為構(gòu)造器是Object了而不是Animal
Cat.prototype = {
constructor:Cat,
//constructor:Animal, constructor就是Cat啊 別亂來
sayMyName : function(){
console.log("my name is "+this.name)
}
}
Cat.prototype.__proto__ = Animal.prototype//有了這句就能修復(fù)原型鏈
注意:別糾結(jié)沾乘,知道原型鏈斷開就好怜奖。這里修復(fù)后只能繼承sayMyFoot,如果要繼承foot的話還要call才行
原型鏈的問題
- 正如前面結(jié)尾所說的翅阵,原型包含引用值從而在所有實例間將會共享歪玲,而這里(通過new繼承) 相當(dāng)于給Cat.prototype添加了一個foot屬性,這是num就沒問題掷匠,如果是Array等引用值的話滥崩,就會產(chǎn)生問題
mimi.colors===["white","black"]
mimi.colors.push("yellow")
miao.colors===["white","black","yellow"]//受影響
mimi.foot = 2
miao.foot===4//不受影響
- 子類型在實例化時不能給父類型的構(gòu)造器傳參
Cat.prototype = new Animal()
你傳個p參對吧
- 解決辦法:盜用構(gòu)造函數(shù)
利用call把Animal的this指向Cat(即call里面的第一個參數(shù)this) 這樣相當(dāng)于把Animal的初始化代碼都偷到Cat上執(zhí)行了,相當(dāng)于沒有兩層new了讹语,該放到構(gòu)造器里的屬性就放進了Cat構(gòu)造器里而非跑到prototype里
function Animal(foot){
this.foot = foot
this.colors = ['white','black']
}
Animal.prototype.sayMyFoot = function(){
console.log("my foot has "+this.foot)
}
function Cat(name,foot){
this.name = name
Animal.call(this,foot)
}
//Cat.prototype = new Animal()//開始繼承
Cat.prototype.sayMyName = function(){
console.log("my name is "+this.name)
} let mimi = new Cat("mimi",4)
console.log(mimi.colors)
mimi.colors.push("yellow")
let miao = new Cat("miao",4)
console.log(miao.colors)//不被影響
console.log(miao.foot)//傳參成功
miao.sayMyFoot()//報錯!
如cb所說:
Cat.prototype.__proto__ = Animal.prototype
等價于Objcet.setPrototypeOf(Cat.prototype, Animal.prototype)
但我覺得后者會有性能問題
還可以Cat.prototype= Object.create(Animal.prototype)//(注意不是__proto__)這個方法自己就可以重新使[[prototype]]指過去
但要補充一行Cat.prototype.constructor = Cat//因為是函數(shù)特有的默認(rèn)屬性
什么钙皮?這都有缺點?
是的
- 必須在構(gòu)造函數(shù)中定義方法顽决,
this.name = name Animal.call(this,foot)
因此函數(shù)不能重用- 更致命的是短条,子類實例也訪問不到父類(Animal、Object)原型上的方法
- 組合式繼承
既然訪問不到方法才菠,那就用原型鏈繼承方法茸时,用盜用來繼承屬性
這樣既可以把方法定義在初始原型上實現(xiàn) 重用,又可以讓每個實例擁有自己的屬性
完整代碼如下:
function Animal(foot){
this.foot = foot
this.colors = ['white','black']
}
Animal.prototype.sayMyFoot = function(){
console.log("my foot has "+this.foot)
}
//盜用構(gòu)造器繼承屬性
function Cat(name,foot){
this.name = name
Animal.call(this,foot)
}
//第三種繼承:組合繼承
//在盜用的基礎(chǔ)上赋访,使用__proto__來進行原型鏈繼承而不使用new
Cat.prototype= new Animal //不要用 盡管它是書里的方式
Cat.prototype.sayMyName = function(){
console.log("my name is "+this.name)
}
//你也可以選擇覆寫
/* Cat.prototype = {
//constructor:Animal, constructor就是Cat啊 別亂來
sayMyName : function(){
console.log("my name is "+this.name)
}
}
Cat.prototype.__proto__ = Animal.prototype*/
let mimi = new Cat("mimi",4)
console.log(mimi.colors)
mimi.colors.push("yellow")
let miao = new Cat("miao",4)
console.log(miao.colors)//不被影響
console.log(miao.foot)//傳參成功
miao.sayMyFoot()//成功繼承方法
注意:這里書里沙雕了可都,就硬要把Animal屬性全部繼承給Cat,從而使用Cat.prototype= new Animal蚓耽,但其實沒必要渠牲,反而會帶來很多副作用
- 存在效率問題,在
Cat.prototype= new Animal
調(diào)用了一次Animal
在Animal.call(this,foot)
又調(diào)了一次- 由于上述問題步悠,Cat.prototype的foot和colors屬性和實例上的foot和colors是兩個不同的重復(fù)
console.log(Cat.prototype.colors===mimi.colors)//false
解決方法Cat.prototype.__proto__ = Animal.prototype
- 區(qū)別是Cat.prototype搜索不到foot和colors 要能搜索到就必須new
- 缺點是失去Object.create添加屬性的特性以及寄生方式DIY方法的能力
Object.setPrototypeOf(Cat.prototype, Animal.prototype)
跟上面一樣签杈,但是還會有性能問題- 標(biāo)準(zhǔn)方法:
Object.create(Animal.prototype)
然后設(shè)置constructor
即寄生式組合繼承
- 原型式繼承:為寄生式打基礎(chǔ)
解決了重復(fù)調(diào)用父類構(gòu)造函數(shù)的問題(直接利用了原型而不用構(gòu)造器)
(直接用__ proto__重定位也是可以解決這個問題的)
試想一種函數(shù)
function object(o){
function F(){}
F.prototype = o
return new F()
}
let person = {
name : "lpj",
girlFriends: ["hjy","cst"]
}
let anotherPerson = object(person)
anotherPerson.name = "sb"http://這里會添加name到實例上而不是原型上
//anotherPerson.__proto__.name = "sb"
anotherPerson.girlFriends.push("someone")//這里會直接訪問原型的girlFriends
let yetAnotherPerson = object(person)
yetAnotherPerson.name = "KKP"
console.log(anotherPerson)//name is sb
console.log(yetAnotherPerson)//name is KKP
console.log(anotherPerson.__proto__)//Array has 3
console.log(yetAnotherPerson.__proto__)//Array has 3
借用 構(gòu)造器簡單地實現(xiàn)了對象之間的信息共享,不用專門去寫構(gòu)造器函數(shù)了
其實是對person進行淺拷貝
Objcet.create()
第二個參數(shù)還允許添加新的屬性到新的實例上贤徒,注意同名會產(chǎn)生遮蔽
- 更進一步:寄生式繼承
寄生式繼承的概念是比較廣的芹壕,不僅用于繼承
顧名思義汇四,用Object.create()新建一個對象,然后增強它 再返回
function createAnother(original){
let clone = Object.create(original)
clone.sayHi = function(){console.log('hi')}
return clone
}
let person = {
name : "lpj"
}
let anotherPerson = createAnother(person)
anotherPerson.sayHi()
注意:這會使得createAnother類型的函數(shù)的復(fù)用性大幅下降
-
來個大雜燴:寄生式組合繼承
利用create()把Animal的原型淺復(fù)制出來踢涌,增強一個constructor屬性通孽,賦給Cat的原型就完事了
function Animal(foot){
foot = 4
this.foot = foot
this.colors = ['white','black']
}
Animal.prototype.sayMyFoot = function(){
console.log("my foot has "+this.foot)
}
//繼承屬性
function Cat(name,foot){
this.name = name
Animal.call(this,foot)
}
//非標(biāo)準(zhǔn)最佳方式:Cat.prototype.__proto__ = Animal.prototype 缺點是失去Object.create添加屬性的特性以及寄生方式DIY方法的能力
//Cat.prototype= new Animal 原方式
function inheritPrototype(child,father){
let prototype = Object.create(father.prototype)
prototype.constructor = child
child.prototype = prototype
}
inheritPrototype(Cat, Animal)
Cat.prototype.sayMyName = function(){
console.log("my name is "+this.name)
}
到這里,實現(xiàn)了方法共享睁壁、屬性私有(偽)背苦、傳參、且只調(diào)用了一次構(gòu)造器潘明、不破壞任何原型鏈行剂、且在必要時可以添加新方法和屬性