继承
在ECMAScript中实现继承的方式主要是通过原型链实现的。
1.原型链继承
通过原型链继承的基本思想是:通过原型,继承引用类型的属性和方法。
注意:所有的引用类型都继承自Object。
两个函数:Animal、Dog
一个Dog实例:dog
通过原型链继承,实现让dog调用sayName()方法
function Animal(name) {
this.name = name;
}
Animal.prototype.sayName = function () {
console.log('my name is ', this.name);
}
function Dog(name, color, age) {
this.name = name;
this.color = color;
this.age = age
}
// 原型链继承
Dog.prototype = new Animal();
//修改Dog原型中的constructor指针指向
Dog.prototype.constructor = Dog;
Dog.prototype.sayColor = function () {
console.log('my color is ', this.color);
}
let dog = new Dog('小白', '白色', 2)
dog.sayColor(); //白色
dog.sayName(); //小白 成功调用该方法
console.log(Dog.prototype.constructor);
//[Function: Dog]
当Dog的原型成为Animal的实例时,就实现了原型链的继承。但是Dog里面的constructor指针就会销毁,那么当我们查找dog实例的构造函数时就会向上找到Animal原型的constructor指针,这时,dog的构造函数就为Animal。
但是,dog的构造函数明明是Dog,通过原型链继承,改变这个指向,所以,我们要手动的修改回来,让他指向回Dog。
此时,我们就能调用Animal中的sayName()方法。
1.1 注意
- 若我们需要在子类的原型中添加方法,需要在改变子类的constructor指向之后再添加,否则调用不了该方法。
例如:我们在Dog的原型中添加了一个sayColor()方法,需要在Dog原型的constructor指向改变之后再添加。
- 若我们用字面量形式为子类的原型中添加多个方法,那么这就会破坏掉原来的原型链,这样子类的constructor就会指向Object。
Dog.prototype = {
getDogName() {
console.log(this.name);
},
someOtherMethod() {
return false;
}
};
console.log(Dog.prototype.constructor);
//[Function: Object]
1.2 判断类型
我们都知道,通过instanceof操作符可以判断一个实例是否属于某个构造函数,即该实例的原型链中是否出现某个构造函数。
isPrototypeOf()
通过isPrototype()这个方法,我们可以判断某个构造函数的原型链中是否包含某个实例,是则返回true,不是则返回false。
//true
console.log(Object.prototype.isPrototypeOf(dog));
//true
console.log(Animal.prototype.isPrototypeOf(dog));
//true
console.log(Dog.prototype.isPrototypeOf(dog));
1.3 原型链的问题
- 当原型中包含引用值时,原型中的引用值会在所有实例*享,而当引用值被修改时,修改后的值就会被所有实例共享。
- 子构造函数在实例化时不能给父构造函数传参。
2.经典继承
经典继承又称盗用构造函数,用于解决原型包含引用值导致的继承问题。
用法:在子构造函数中使用call()或apply()方法,改变this指向,指向当前子构造函数并调用父构造函数。这样之后每个实例都会拥有自己的引用值类型数据,就不会共享而导致出现问题。
// 父'类'
function Animal() {
this.name = 'aaa'
this.categorys = ['cat', 'dog']
}
// 子'类'
function Dog() {
// 改变this指向 继承属性
Animal.call(this);
}
2.1 经典继承优点
- 可在子构造函数中向父构造函数传参
- 可给子构造函数的实例添加新的属性
2.2 经典继承的问题
- 必须在构造函数中定义方法,导致函数不能重用
- 子构造函数不能访问父构造函数原型上定义的方法
- 每个子构造函数都拥有父构造函数的副本,浪费内存,影响性能
注意:不能使用instanceof操作符和isPrototypeOf()方法判断继承的父构造函数,因为只继承了父构造函数的实例方法、属性,没有继承父构造函数原型对象中的属性、方法。
3.组合继承
又称伪经典继承,结合了原型链继承和经典继承的优点,是JS使用最多得继承模式。
用法:通过原型链继承原型上的属性和方法;通过经典继承继承实例属性。
优点:将方法定义在原型上,实现重用;还可以让每个实例都拥有自己的实例
function Animal() {
this.name = 'aaa'
this.categorys = ['cat', 'dog']
}
Animal.prototype.sayName = function () {
console.log(this.name);
}
// 子'类'
function Dog() {
// 改变this指向 继承属性
Animal.call(this);
}
// 继承方法
Dog.prototype = new Animal()
var d1 = new Dog();
d1.categorys.push('rabbit')
var d2 = new Dog();
console.log(d1.categorys); //[ 'cat', 'dog', 'rabbit' ]
console.log(d2.categorys); //[ 'cat', 'dog']
d1.sayName() //aaa