原型链继承
一、原型链
1、什么是原型链:每个构造函数都有一个原型对象,原型对象有一个属性constructor指回构造函数,而实例对象有一个内部指针__proto__指向原型对象。如果原型对象是另一个对象的实例呢?那就意味着这个原型对象本身有一个内部指针指向另一个构造函数的原型对象,相应的另一个原型对象也有一个内部指针指向另一个构造函数,这样就在实例对象和原型对象之间构造了一条原型链
2、原型链的作用:原型链是ECMAScript主要的继承方式,其基本思想就是通过原型链继承多个共享属性和方法
3、例子
// 创建一个对象Animal
function Animal(){
this.name = 'animal'
}
// 给Animal的原型对象上声明一个方法
Animal.prototype.getAnimalName = function (){
console.log(this.name + 'getAnimalName');
}
// 创建对象Cat
function Cat(){
this.name = 'cat'
}
// Cat继承Animal,将Animal的实例对象赋值给Cat的原型对象,
Cat.prototype = new Animal()
// 如此Cat的原型对象就能通过Animal的实例对象的__proto__指针访问到Animal原型对象中的属性和方法
console.log(Cat.prototype.__proto__ === Animal.prototype);//true
// 给Cat的原型对象上声明一个方法
Cat.prototype.getCatName = function (){
console.log(this.name + 'getCatName');
}
// 在使用原型链继承的时候,要在继承之后再去原型对象上定义自己所需要的属性和方法,因为在继承之前在原型对象上定义方法和属性将会被覆盖
// 创建实例对象cat1
var cat1 = new Cat()
console.log(cat1);//{name:'cat'}
// 调用自己原型对象上定义的方法
cat1.getCatName()//catgetCatName
// 调用继承Animal原型对象上的方法
cat1.getAnimalName()//catgetAnimalName
4、总结:以上代码定义了两个类型Animal、Cat,将Animal的实例对象赋值给了Cat的原型对象,所以Cat.prototype实现了对Animal的继承,这个赋值重写了Cat最初的原型对象,将其替换为Animal的实例。这意味着Animal实例可以访问的属性和方法也会存在于Cat的原型对象,这样继承之后,代码紧接着又给Cat.prototype,也就是Animal的实例添加了一个新的方法。最后创建了Cat的实例,并调用了它继承的getAnimalName()方法。
要注意的是:(1)Animal对象的getAnimalName()方法在Animal对象的原型对象上,name属性在Cat对象的原型对象上,这是因为getAnimalName()方法是定义在Animal原型对象上的方法,Cat.prototype是Animal的一个实例,所以name才会出现在它的上面。(2)由于Cat.prototype被重写为Animal的实例对象,所以Cat.prototype上的constructor不再指向Cat,而是指向Animal。(3)实例对象访问一个属性或方法,先会在实例上搜索,如果实例上没有找到就去上一级原型对象上搜索,如果没有找到再去上上一级原型对象上搜索,直到原型链的末端。
5、默认原型
实际上,原型链上还有一环,默认情况下,任何函数的默认原型都是一个Object实例,这意味这这个实例有一个内部指针指向Object.prototype。这也是为什么自定义类型能够继承包括toString()、valueOf()在内的所有默认方法的原因。因此,前面的例子还有一层额外的继承关系,Animal继承Object。cat1.tostring(),实际上调用的是保存在Object.prototype上的toString()方法
6、原型链的破环
以对象字面量方式创建原型对象的方法和属性会破坏之前的原型链,因为这相当于重写了原型链。
//Cat.prototype.getCatName = function (){
//console.log(this.name + 'getCatName');
//}
//以对象字面量的方式创建Cat.prototype上的方法getCatName()
Cat.prototype = {
getCatName(){
console.log(this.name + 'getCatName');
}
}
cat1.getAnimalName()//getAnimalName is not a function
这是因为Cat的原型对象被赋值为Object的实例对象,Cat.prototype.__proto__指向了Object.prototype而不再指向Animal.prototype,所以Cat的实例对象访问不到getAnimalName()方法。
7、原型链的问题
(1)原型对象中包含的引用数据类型的值会在所有实例之间共享,也就是操作一个实例对象上继承的原型对象的属性,另一个实例对象的这个属性也会发生同样的改变,如果我们需要一个属性再所有实例之间共享这当然是非常好的,但是想要操作一个实例继承的属性而不影响其他属性就比较难办了
// 创建一个对象Animal
function Animal(){
this.name = 'animal'
this.category = ['big','pig']
}
cat1.category.push('cat')
var cat2 = new Cat()
console.log(cat2.category)//['big','pig','cat']
这是因为在使用原型链继承时,原型实际上变成了另一个对象的实例,这就意味着,原先的实例属性摇身一变成为了原型属性
(2)子类型在实例化时不能给父类型的构造函数传递参数