继承:子类继承父类中的属性和方法(目的是让子类的实例能够调用父类的属性和方法)
1.原型链继承
原型链继承:让父类中的属性和方法在子类实例的原型链上(把父类的原型放到子类实例的原型链上,实例使用父类的属性和方法,是基于__proto__原型链查找机制完成的)
…
子类.prototype = new 父类();
子类.prototype.constructor = 子类; 子类的原型指向父类的实例,需要添加constructor属性,保证原型重定向后的完整性。
优点:
- 父类方法和属性可以复用。
缺点:
- 父类所有的引用数据类型会被多个子类共享(更改一个子类的数据。其他数据也会受影响)。
- 子类实例不能给父类构造函数传参。
function Person(){
this.name = '小明';
this.eats = ['banner'];
this.getName = function(){
console.log(this.name);
}
}
Person.prototype.getA = function () {
console.log(this.name);
}
function Child(){};
Child.prototype = new Person();
Child.prototype.constructor = Child; //原型重定向,需要添加constructor属性,保证原型的完整性
const b1 = new Child();
const b2 = new Child();
b1.name = '小花';
b1.eats.push('apple');
console.log(b1.name); //小花
console.log(b1.eats); //['banner', 'apple']
b1.getName();//小花
b1.getA();//小花
console.log(b2.name);//小明
console.log(b2.eats);//['banner', 'apple']
b2.getName();//小明
b2.getA();//小明
2.构造函数继承
构造函数继承:使用call或者apply方法,将父类的构造函数绑定在子类上,即在子类构造函数中添加:
父类.apply(this, arguments); 或 父类.call(this, arguments);
优点:
- 引用类型数据不会被子类共享,更改一个也不会影响别的实例。
缺点:
- 子类不能访问父类原型属性(Person.prototype)上的方法和属性。
function Person() {
this.name = '小明';
this.eats = ['banner'];
this.getDo = function () {
console.log(this.name + '吃了' + this.eats);
}
}
Person.prototype.getName = function () {
console.log(this.name);
}
function Child() {
Person.call(this, arguments);
}
const c1 = new Child();
c1.name = '小三';
c1.eats.push('apple');
console.log(c1.name);//小三
console.log(c1.eats);//['banner', 'apple']
c1.getDo();//小三吃了banner,apple
c1.getName();//Uncaught TypeError: c1.getName is not a function 无法获取父类原型上的方法
const c2 = new Child();
console.log(c2.name);//小明
console.log(c2.eats);//['banner']
c2.getDo();//小明吃了banner
c2.getName();//Uncaught TypeError: c2.getName is not a function 无法获取父类原型上的方法
3.组合继承
组合继承:就是原型链继承+构造函数继承
优点:
- 父类的属性和方法都可以实现公用
- 父类的引用属性不会被被共享
缺点:
- 会调用两次父类的构造函数,会有两份一样的属性和方法,影响性能。
function Person() {
this.name = '小明';
this.eats = ['banner'];
this.getDo = function () {
console.log(this.name + '吃了' + this.eats);
}
}
Person.prototype.getName = function () {
console.log(this.name);
}
function Child() {
Person.call(this, arguments);
}
Child.prototype = new Person();
Child.prototype.constructor = Child;
const c1 = new Child();
c1.name = '小三';
c1.eats.push('apple');
console.log(c1.name);//小三
console.log(c1.eats);//['banner', 'apple']
c1.getDo();//小三吃了banner,apple
c1.getName();//小三
const c2 = new Child();
console.log(c2.name);//小明
console.log(c2.eats);//['banner']
c2.getDo();//小明吃了banner
c2.getName();//小明
4.寄生组合继承
寄生组合继承:利用一个干净的实例对象,来作为子类的原型,并且这个干净的实例对象还得继承父类的属性,然后结合组合继承。
优点:完美解决其他继承方式存在的弊端问题。
function Person() {
this.name = '小明';
this.eats = ['banner'];
this.getDo = function () {
console.log(this.name + '吃了' + this.eats);
}
}
Person.prototype.getName = function () {
console.log(this.name);
}
function Child() {
Person.call(this, arguments);
}
Child.prototype = Object.create(Person.prototype);
Child.prototype.constructor = Child;
const c1 = new Child();
c1.name = '小三';
c1.eats.push('apple');
console.log(c1.name);//小三
console.log(c1.eats);//['banner', 'apple']
c1.getDo();//小三吃了banner,apple
c1.getName();//小三
const c2 = new Child();
console.log(c2.name);//小明
console.log(c2.eats);//['banner']
c2.getDo();//小明吃了banner
c2.getName();//小明
5.ES6 class继承
Class 可以通过extends关键字实现继承,让子类继承父类除私有属性外的属性和方法。
class Person{
constructor(){
this.name = '小明';
this.eats = ["苹果"];
this.getName = function (){
console.log(this.name);
}
}
get = () =>{
console.log('Person.prototype上的方法');
}
}
class Child extends Person{}
const c1 = new Child();
c1.name = '小三';
c1.eats.push('香蕉');
console.log(c1.name);//小三
console.log(c1.eats);//['苹果', '香蕉']
c1.getName();//小三
c1.get();//Person.prototype上的方法
const c2 = new Child();
console.log(c2.name);//小明
console.log(c2.eats);//['苹果']
c2.getName();//小明
c2.get();//Person.prototype上的方法