1.继承的相关概念
继承分为接口继承和实现继承,但是由于ES中函数没有签名,无法实现接口继承,因此ES中只支持实现继承,
由原型链方式实现。
2.原型链
ES中实现继承的主要方式,因为每个实例对象中都有一个内部指针指向其构造函数,当这个指针指向另一个类型的
实例,这时另一个实例的指针也会指向另一个构造函数,这时就会在实例和原型中产生一条原型链,而继承就是通
过原型链继承多个引用类型的属性和方法的一种使用。
补充:原型链就是把对象实例的指向其构造函数的指针地址修改成为了一个实例对象的引用地址
原型链的实现:
// 创建Animal
function Animal() {
this.name = 'animal';
}
Animal.prototype.getAnimalName = function () {
console.log(this.name + 'getAnimalName');
}
// 创建Dog
function Dog() {
this.name = 'dog';
}
// Dog继承自Animal 将Animal的实例赋值给Dog的原型对象,相当于将Animal的实例中的__proto__赋值给了Dog的原型对象
// 如此 Dog原型对象 就能通过 Animal 对象的实例中的[[prototype]](__proto__) 来访问到 Animal原型对象 中的属性和方法了。
Dog.prototype = new Animal();
// 不建议使用Dog.prototype.__proto__=== Animal.prototype,因为双下划线的属性是js中的内部属性,各个浏览器兼容性不一,不建议直接操作属性,ES6中提供了操作属性的方法可以实现。
console.log(Dog.prototype.__proto__ === Animal.prototype );
// 在使用原型链继承的时候,要在继承之后再去原型对象上定义自己所需的属性和方法
Dog.prototype.getDogName = function () {
console.log(this.name + 'getDogName');
}
var d1 = new Dog();
d1.getAnimalName()
d1.getDogName()
2.1 默认原型
在Javascript中,有一句话叫做万物皆为对象,其意思就是所有的引用类型都是一个对象,都是由一个Object对象延申出来的,因此,在原型链的概念上,所有的引用类型在原型链的最上端都是一个Object,故把Object称为默认原型。
2.2 原型和继承的关系
通过instanceof操作符判断一个实例是否出现在原型链上:
console.log(d1 instanceof Object) // true
console.log(d1 instanceof Animal) // true
通过判断原型链中是否存在isPrototype()方法来确定是否在原型链上:
console.log(Object.prototype.isPrototypeOf(d1)); // true
console.log(Animal.prototype.isPrototypeOf(d1)); // true
2.3 方法遮蔽
例子:
function Animal() {
this.name = 'animal';
}
Animal.prototype.getAnimalName = function () {
console.log(this.name + 'getAnimalName');
}
// 创建Animal的实例
var a1 = new Animal()
a1.getAnimalName(); //animalgetAnimalName
function Dog() {
this.name = 'dog';
}
Dog.prototype = new Animal();
// 新方法
Dog.prototype.getDogName = function () {
console.log(this.name + 'getDogName');
}
// 覆盖父类已有的方法
Dog.prototype.getAnimalName = function () {
console.log('我覆盖了父类的方法');
}
var d1 = new Dog();
d1.getAnimalName(); // 我覆盖了父类的方法
d1.getDogName();
与遮蔽属性相类似,通过实例覆盖父类的同名方法与遮蔽同名属性一样,父类的同名方法
不会被销毁,而是被子类实例所重写的方法遮蔽掉。
2.4 原型链破坏
当以字面量的形式创建原型方法时,相当于把原本实例中的指向另一个实例的指针指向了通过字面量形式创建的方法对象,这就是相当于把原型链重写了,因此原型链就会断掉。
例子:
function Animal() {
this.name = 'animal';
}
Animal.prototype.getAnimalName = function () {
console.log(this.name);
};
function Dog() {
this.name = 'dog';
}
// 继承
Dog.prototype = new Animal()
Dog.prototype = {
getDogName() {
console.log(this.name);
},
someOtherMethod() {
return false;
}
};
var d1 = new Dog();
d1.getAnimalName(); // 出错!
2.5 原型链的问题
当原型中包含了引用类型的值时,通过原型链继承了这个引用类型后,这个继承过来的实例属性就变成了当前实例对象的原型属性
3.经典继承
概念:在子类构造函数调用父类构造函数
例子:
function Animal() {
this.categorys = ["cat", "rabbit"];
}
function Dog() {
// 继承 Animal
Animal.call(this);
}
在var d1 = new Dog()时,是d1调用Dog构造函数,所以其内部this的值指向的是d1,所以Animal.call(this)就相当于Animal.call(d1),就相当于d1.Animal()。最后,d1去调用Animal方法时,Animal内部的this指向就指向了d1。那么Animal内部this上的所有属性和方法,都被拷贝到了d1上。所以,每个实例都具有自己的categorys属性副本。他们互不影响。
var d1 = new Dog();
d1.categorys.push("dog");
console.log(d1.categorys); // [ 'cat', 'rabbit', 'dog' ]
var d2 = new Dog();
console.log(d2.categorys); // [ 'cat', 'rabbit' ]
通过在子类构造函数中调用父类构造函数并修改this的指向为调用子类构造函数的实例对
象完成继承
3.1 参数传递
例子:
function Animal(name) {
this.name = name;
}
function Dog() {
// 继承 Animal 并传参
Animal.call(this, "zhangsan");
// 实例属性
this.age = 29;
}
var d = new Dog();
console.log(d.name); // zhangsan
console.log(d.age); // 29
3.1 经典继承的问题
1.创建的实例并不是父类的实例,只是子类的实例。
2.没有拼接原型链,不能使用instanceof。因为子类的实例只继承了父类的实例属性/方法,没有继承父类的构造函数的原型对象中的属性/方法。
3.每个子类的实例都持有父类的实例方法的副本,浪费内存,影响性能,而且无法实现父类的实例方法的复用。
4.组合继承
结合了原型继承和经典继承,基本思路是通过原型链继承继承属性和方法,通过经典继承继承实例属性
例子:
function Animal(name) {
this.name = name;
this.categorys = ["cat", "rabbit"];
}
Animal.prototype.sayName = function () {
console.log(this.name);
};
function Dog(name, age) {
// 继承属性
Animal.call(this, name);
this.age = age;
}
// 继承方法
Dog.prototype = new Animal();
Dog.prototype.sayAge = function () {
console.log(this.age);
};
var d1 = new Dog("zhangsan", 29);
d1.categorys.push("dog");
console.log(d1.categorys); // [ 'cat', 'rabbit', 'dog' ]
d1.sayName(); // zhangsan
d1.sayAge(); // 29
var d2 = new Dog("lisi", 27);
console.log(d2.categorys); // [ 'cat', 'rabbit' ]
d2.sayName(); // lisi
d2.sayAge(); // 27