什么是原型?
function Person(){
}
let p = new Person()
實例對象擁有__proto__
函數(shù)擁有Person.prototype,Person.prototype就是原型,它是一個對象,我們也稱它為原型對象。
什么是原型鏈
原型與原型層層相鏈接的過程即為原型鏈。
Object.prototype.__proto__ // null
Function.prototype.__proto__ === Object.prototype // true
Function.__proto__ === Function.prototype // true

function Person(params) {
}
Person.prototype.n = 1
Person.prototype.add = function () {
this.n++
}
let p1 = new Person()
let p2 = new Person()
p1.add()
p2.add()
console.log(Person.prototype.n) // 1
p1.add()執(zhí)行時,add中this的指向為p1這個對象實例,當(dāng)p1的屬性找不到n時,通過原型鏈向上查找,找到n=1,然后給當(dāng)前對象p1進(jìn)行賦值
原型繼承
原型模式
function Person() {
this.name = '小王'
this.color = ['red', 'black']
this.getName = function () {
console.log(this.name);
}
}
function Man() {
this.name = '小黃'
}
Man.prototype = new Person()
const man = new Man()
const man2 = new Man()
man.color.push('green')
console.log(man.color); // ['red', 'black', 'green']
console.log(man2.color); // ['red', 'black', 'green']
兩個問題:
- 原型中的引用值會在多個實例間共享
- 子類型在實例化時不能給夫類型的構(gòu)造函數(shù)傳參
盜用構(gòu)造函數(shù)(對象偽裝 或 經(jīng)典繼承)
function Person() {
this.name = '小王'
this.color = ['red', 'black']
this.getName = function () {
console.log(this.name);
}
}
function Man() {
Person.call(this)
this.name = '小黃'
}
const man = new Man()
const man2 = new Man()
man.color.push('green')
console.log(man.color); // ['red', 'black', 'green']
console.log(man2.color); // ['red', 'black']
通過使用call()(或apply())方法,Person的構(gòu)造函數(shù)在Man的實例創(chuàng)建的上下文中執(zhí)行了。相當(dāng)于新的Man對象上運行了Person函數(shù)中的所有初始化代碼,結(jié)果就是每個實例都會有自己的colors屬性。
優(yōu)點
- 能夠在子類構(gòu)造函數(shù)中像父類構(gòu)造函數(shù)傳參
問題
- 必須在構(gòu)造函數(shù)中定義方法,因此函數(shù)不能重用
- 子類也不能訪問父類原型上定義的方法,因此所有類型只能使用構(gòu)造函數(shù)模式
組合繼承(偽經(jīng)典繼承)
綜合原型鏈和盜用構(gòu)造函數(shù),結(jié)合兩者的優(yōu)點。
思路是使用原型鏈繼承原型上的屬性和方法,通過盜用構(gòu)造函數(shù)繼承實例屬性
function Person() {
this.name = '小王'
this.color = ['red', 'black']
this.getName = function () {
console.log(this.name);
}
}
function Man() {
// 繼承構(gòu)造函數(shù)實例的屬性
Person.call(this)
this.name = '小黃'
}
// 繼承原型的屬性和方法
Man.prototype = new Person()
const man = new Man()
const man2 = new Man()
man.color.push('green')
console.log(man.color); // ['red', 'black', 'green']
console.log(man2.color); // ['red', 'black']
組合繼承彌補(bǔ)了原型鏈和盜用構(gòu)造函數(shù)的不足,是JavaScript中使用最多的繼承模式。而且組合繼承也保留了instanceof操作符和isPrototypeOf()方法識別合成對象的能力。
原型式繼承
function object(o) {
function F() {}
F.prototype = o;
return new F()
}
object會創(chuàng)建一個臨時構(gòu)造函數(shù),將傳入的對象賦值給這個構(gòu)造函數(shù)的原型,然后返回這個臨時構(gòu)造函數(shù)的實例。
實質(zhì)上是對傳入對象的一次淺復(fù)制,所以還存在值引用的問題。
let obj = {
age: 23,
height: 180,
skill: ['java', 'js']
}
const man = object(obj)
const man1 = object(obj)
// es5新增了Object.ceate的方法可替代
const man = Object.create(obj)
const man1 = Object.create(obj)
man.skill.push('c++')
man.skill; // ["java", "js", "c++"]
man1.skill;// ["java", "js", "c++"]
原型式繼承非常適合不需要單獨創(chuàng)建構(gòu)造函數(shù),但仍然需要在對象間共享信息的場合。但要記住,屬性中包含的引用值始終會在相關(guān)對象間共享,跟使用原型模式是一樣的。
寄生式繼承
寄生式繼承背后的思路類似于寄生構(gòu)造函數(shù)和工廠模式:創(chuàng)建一個實現(xiàn)繼承的函數(shù),以某種方式增強(qiáng)對象,然后返回這個對象。
function createAnother(original) {
// 通過調(diào)用函數(shù)創(chuàng)建一個新的對象
let clone = object(original);
// 以某種方式增強(qiáng)這個對象
clone.sayHi = function () {
console.log('Hi');
}
// 返回這個對象
return clone
}
let person = {
name: 'tom',
friends: ['jerry', 'arthur']
}
let anoherPerson = createAnother(person);
anoherPerson.sayHi()
寄生式繼承同樣適合主要關(guān)注對象,而不在乎類型和構(gòu)造函數(shù)的場景。object()函數(shù)不是寄生式繼承所必需的,任何返回新對象的函數(shù)都可以在這里使用。
寄生式組合繼承
組合繼承其實也存在效率問題。最主要的效率問題就是父類構(gòu)造函數(shù)始終會被調(diào)用兩次:一次在是創(chuàng)建子類原型時調(diào)用,另一次是在子類構(gòu)造函數(shù)中調(diào)用。
寄生式組合繼承通過盜用構(gòu)造函數(shù)繼承屬性,但使用混合式原型鏈繼承方法?;舅悸肥遣煌ㄟ^調(diào)用父類構(gòu)造函數(shù)給子類原型賦值,而是取得父類原型的一個副本。說到底就是使用寄生式繼承來繼承父類原型,然后將返回的新對象賦值給子類原型。
function Person() {
this.name = '小王'
this.color = ['red', 'black']
this.getName = function () {
console.log(this.name);
}
}
Person.prototype.code = function () {
console.log('coding');
}
Person.prototype.skill = ['java', 'js']
function Man() {
Person.call(this)
}
function inheritPrototype(man, person) {
const prototype = person.prototype
prototype.constructor = person;
man.prototype = prototype
}
inheritPrototype(Man, Person)
const man = new Man()
寄生式組合繼承可以算是引用類型繼承的最佳模式。