原型
ECMAScript 中描述了原型鏈的概念,并將原型鏈作為實(shí)現(xiàn)繼承的主要方法奇适。其基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。簡單回顧一下構(gòu)造函數(shù)进鸠、原型和實(shí)例的關(guān)系:每個構(gòu)造函數(shù)都有一個原型對象媳握,原型對象都包含一個指向構(gòu)造函數(shù)的指針碱屁,而實(shí)例都包含一個指向原型對象的內(nèi)部指針。那么蛾找,假如我們讓原型對象等于另一個類型的實(shí)例娩脾,結(jié)果會怎么樣呢?顯然打毛,此時的原型對象將包含一個指向另一個原型的指針柿赊,相應(yīng)地,另一個原型中也包含著一個指向另一個構(gòu)造函數(shù)的指針隘冲。假如另一個原型又是另一個類型的實(shí)例闹瞧,那么上述關(guān)系依然成立,如此層層遞進(jìn)展辞,就構(gòu)成了實(shí)例與原型的鏈條奥邮。這就是所謂原型鏈的基本概念
繼承
繼承是OO 語言中的一個最為人津津樂道的概念。許多OO 語言都支持兩種繼承方式:接口繼承和
實(shí)現(xiàn)繼承罗珍。接口繼承只繼承方法簽名洽腺,而實(shí)現(xiàn)繼承則繼承實(shí)際的方法。如前所述覆旱,由于函數(shù)沒有簽名蘸朋,
在ECMAScript 中無法實(shí)現(xiàn)接口繼承。ECMAScript 只支持實(shí)現(xiàn)繼承扣唱,而且其實(shí)現(xiàn)繼承主要是依靠原型鏈
來實(shí)現(xiàn)的
原型與原型鏈
- prototype :每個函數(shù)都會有這個屬性藕坯,這里強(qiáng)調(diào),是函數(shù)噪沙,普通對象是沒有這個屬性的(這里為什么說普通對象呢炼彪,因?yàn)镴S里面,一切皆為對象正歼,所以這里的普通對象不包括函數(shù)對象)辐马。它是構(gòu)造函數(shù)的原型對象;
- proto :每個對象都有這個屬性,局义,這里強(qiáng)調(diào)喜爷,是對象,同樣萄唇,因?yàn)楹瘮?shù)也是對象檩帐,所以函數(shù)也有這個屬性。它指向構(gòu)造函數(shù)的原型對象穷绵;
- constructor :這是原型對象上的一個指向構(gòu)造函數(shù)的屬性轿塔。
function Pig(name: string, age: number){
this.name = name
this.age = age
}
// 創(chuàng)建Pig實(shí)例
const Pepig = new Pig('Pepig', 18)
// 在實(shí)例化的時候,prototype上的屬性會作為原型對象賦值給實(shí)例, 也就是Pepig原型
Pepig.__proto__ === Pig.prototype // true
// Pig是一個函數(shù)對象, 它是Function對象的一個實(shí)例 Funtcion的原型對象又指向Object對象
Pig.__proto__ === Function.prototype // true
Function.prototype.__proto__ === Object.prototype // true
Object.prototype.__proto__ === null // true
// 原型對象上的constructor指向構(gòu)造函數(shù)本身
Pig.prototype.constructor = Pig
繼承的方式
1.原型鏈繼承
function P(name: string){
this.name = name
}
P.prototype.sayName = function(){
console.log('P', this.name)
}
function C(name: string){
this.name = name
}
C.prototype = new P('P')
C.prototype.constructor = C
C.prototype.sayName = function(){
console.log('C', this.name)
}
const child = new C('C')
child.sayName() // 'C' C
缺點(diǎn)
- 子類型無法給超類型傳遞參數(shù),在面向?qū)ο蟮睦^承中勾缭,我們總希望通過 var child = new Child('son', 'father'); 讓子類去調(diào)用父類的構(gòu)造器來完成繼承揍障。而不是通過像這樣 new Parent('father') 去調(diào)用父類。
- 引用類型值的原型屬性會被所有實(shí)例共享
2.借用構(gòu)造函數(shù)(經(jīng)典繼承)
function P(age: number){
this.names = ['alex', 'jet']
this.age = age
}
function C(age: number){
P.call(this, age)
}
const child = new C()
child.names.push('mark')
child.names // alex jet mark
const child1 = new C()
child1.names // alex jet
const child = new C('15')
child.age // 15
const child = new C('18')
child.age // 18
優(yōu)點(diǎn)
- 避免了引用類型的屬性被所有實(shí)例共享
- 可以在 Child 中向 Parent 傳參
缺點(diǎn)
- 方法都在構(gòu)造函數(shù)中定義俩由,因此函數(shù)復(fù)用就無從談起了毒嫡。而且,在超類型的原型中定義的方法幻梯,對子類型而言也是不可見的兜畸。
3.組合繼承
function P(name: string){
this.name = name
this.colors = ['yellow', 'blue']
}
P.prototype.getName = function(){
console.log(this.name)
}
function C(name: string, age: number){
P.call(this, name)
this.age = age
}
C.prototype = new P()
const child = new C('jet', 18)
child.colors.push('black') // yellow blue black
child.name // jet
child.age // 18
const child1 new C('jack', 20)
child1.colors // yellow blue
child1.name // jack
child1.age // 20
優(yōu)點(diǎn)
- 融合原型鏈繼承和構(gòu)造函數(shù)的優(yōu)點(diǎn),是 JavaScript 中最常用的繼承模式碘梢。
- 都會調(diào)用兩次超類型構(gòu)造函數(shù)
4.原型式繼承
function object(o: Object){
function F(){}
F.prototype = o
return new F()
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
}
var anotherPerson = object(person)
anotherPerson.name = "Greg"
anotherPerson.friends.push("Rob")
var yetAnotherPerson = object(person)
yetAnotherPerson.name = "Linda"
yetAnotherPerson.friends.push("Barbie")
alert(person.friends) //"Shelby,Court,Van,Rob,Barbie"
特點(diǎn)
- 包含引用類型值的屬性始終都會共享相應(yīng)的值咬摇,就像使用原型模式一樣
5.寄生式繼承
思想: 寄生式繼承的思路與寄生構(gòu)造函數(shù)和工廠模式類似,即創(chuàng)建一個僅用于封裝繼承過程的函數(shù)煞躬,該
函數(shù)在內(nèi)部以某種方式來增強(qiáng)對象肛鹏,最后再像真地是它做了所有工作一樣返回對象
function createAnother(original){
var clone = object(original) //通過調(diào)用函數(shù)創(chuàng)建一個新對象
clone.sayHi = function(){ //以某種方式來增強(qiáng)這個對象
alert("hi")
}
return clone //返回這個對象
}
缺點(diǎn)
- 使用寄生式繼承來為對象添加函數(shù),會由于不能做到函數(shù)復(fù)用而降低效率
6.寄生組合式繼承
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //創(chuàng)建對象
prototype.constructor = subType; //增強(qiáng)對象
subType.prototype = prototype; //指定對象
}
優(yōu)點(diǎn)
1.只調(diào)用了一次 SuperType 構(gòu)造函數(shù)恩沛,并且因此避免了在 SubType.
prototype 上面創(chuàng)建不必要的在扰、多余的屬性。與此同時雷客,原型鏈還能保持不變芒珠;因此,還能夠正常使用
instanceof 和 isPrototypeOf()