参考链接: js继承的6种方式
6种方法

1、原型链继承

核心:将父类的实例作为子类的原型

//父类
function superType(){
    this.name='father';
    this.colors= ['red','blue','green'];
}
//方法
superType.prototype.getName=function(){
    console.log(this.name);
    console.log(this.colors);
}
//子类
function subType(){
    // this.name='son'; 如果加上这句x.getName会输出son
}
//实质:把superType的实例赋值给subType的原型(实例包含指向原型对象的指针)
//于是superType中的所有属性和方法,subType都有了。
subType.prototype = new superType();
console.log('原型链');
var x = new subType();//创建实例
var y = new subType();
x.getName();//输出father
y.name='s';
y.getName();//输出s
y.colors=['1','2','3'];//这是深拷贝(maybe)所以y改变了x不会改变
x.getName();//father ['red','blue','green'];
y.getName();//s ['1','2','3']
//如果使用y.colors.push('a');则x和y的值会一样,即共享同一个引用类型属性

原型链的缺点:(所以其实实践过程中很少使用)
1.所有subType的实例共享一个superType的引用类型属性
2.创建subType时没办法向superType传参

实例可以继承的属性有:
实例的构造函数属性,父类原型的属性、父类构造函数属性

特点:

  1. 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
  2. 父类新增原型方法/原型属性,子类都能访问到
  3. 简单,易于实现

缺点:

  1. 可以在构造函数中,为实例增加实例属性。如果要新增原型属性和方法,则必须放在new superType()这样的语句之后执行。
  2. 无法实现多继承
  3. 致命缺陷:来自原型对象的所有属性被所有实例共享
  4. 致命缺陷:创建子类实例时,无法向父类构造函数传参

2、构造继承(经典继承)

核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类

function Person(name){
    this.name=name;
    this.colors= ['red','blue','green'];
}
Person.prototype.getName=function(){
    console.log(this.name);
}
//子类
function Child(){
    Person.call(this,'a');//调用父类的构造函数实现继承效果
}
//实例
var child1 = new Child();
var child2 = new Child();
console.log('构造函数');
child1.colors.push('white');
console.log(child1.name);
// child2.getName();不能通过,因为这是父类的原型的方法
console.log(child1.colors);//['red', 'blue', 'green', 'yellow']
console.log(child2.colors);//['red', 'blue', 'green']

特点:

  • 解决了1中,子类实例共享父类引用属性的问题
  • 创建子类实例时,可以向父类传递参数
  • 可以实现多继承(call多个父类对象)即继承多个构造函数属性

缺点:

  • 实例并不是父类的实例,只是子类的实例
  • 只能继承父类的实例属性和方法,不能继承父类原型属性/方法
  • 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

3、组合继承(组合原型链继承和借用构造函数继承)(常用)

核心:结合了两种模式的优点,传参和复用
使用原型链实现对原型方法的继承,而通过借用构造函数来实现对实例属性的继承

function Parent(name){
    this.name=name;
    this.arr=['red','blue'];
}
Parent.prototype.getName=function(){
    console.log(this.name);
}
//构造函数的方法
function child(name,age){
    Parent.call(this,name);//第二次调用Parent()
    this.age=age;
}
//原型链继承的方法
child.prototype = new Parent();//第一次调用Parent()
var c1=new child('hhh',18);
var c2=new child('yyy',19);
console.log('组合继承');
c1.getName();//hhh
c2.getName();//yyy
console.log(c1.age);//18
console.log(c2.age);//19
c1.arr.push('green');
console.log(c1.arr);//['red', 'blue', 'green']
console.log(c2.arr);//['red', 'blue']
console.log(c1 instanceof child);//true
console.log(c1 instanceof Parent);//true

特点:

  • 可以继承父类原型上的属性,可以传参,可以复用
  • 每个新实例引入的构造函数属性是私有的

缺点

  • 覅用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。

4、原型式继承

核心:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了可以随意增添属性的实例或对象。object.create()就是这个原理

function createObj(o){
    function f(){}//在内部创建一个临时构造函数,
    f.prototype = o;//将传入的对象作为这个构造函数的原型,
    return new f();//最后返回这个临时类型的新实例
}

var person={//父类
    name:'xiao',
    friend:['ddd','ccc']
};

var anotherPerson = createObj(person);
var anotherPerson2 = createObj(person);
console.log('原型式继承');
anotherPerson.name='hscod';//给anotherPerson添加了name值,但是并没有修改原型上的name值
anotherPerson.friend.push('aaa');
console.log(anotherPerson);//{name: 'hscod'}
anotherPerson2.friend.push('bbb');
console.log(anotherPerson2);//{}
console.log(person.friend);//[ 'ddd', 'ccc', 'aaa', 'bbb' ]
//缺点和原型链继承一样,所有的引用类型会被共享

特点:类似于复制一个对象,用函数来包装。
缺点:1、所有实例都会继承原型上的属性。
   2、无法实现复用。(新实例属性都是后面添加的)

5、寄生式继承

//创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。
//可以理解成在原型式继承的基础上新增一些函数或属性
console.log('寄生式继承');
var ob1=createObj(person);
var ob2=Object.create(person);//ES5的规范写法
console.log(ob1.name);//xiao
console.log(ob2.name);//xiao

function createSon(s){//子类
    var newob=createObj(s);//创建对象
    newob.sayName = function(){//增强对象
        console.log(this.name);
    }
    return newob;//指定对象
}

var p1=createSon(person);
p1.sayName();//xiao
//缺点:和构造函数一样,每次创建对象都会创建一遍方法

重点:就是给原型式继承外面套了个壳子。
    优点:没有创建自定义类型,因为只是套了个壳子返回对象(这个),这个函数顺理成章就成了创建的新对象。
    缺点:没用到原型,无法复用。

6、寄生组合式继承(常用)

寄生:在函数内返回对象然后调用
组合:1、函数的原型等于另一个实例。2、在函数中用apply或者call引入另一个构造函数,可传参

//子类构造函数复制父类的自身属性和方法,子类原型只接受父类的原型属性和方法
function pa(name){
    this.name=name;
    this.attr=['a','b'];
}
pa.prototype.speak = function(){
    console.log('my name is '+this.name);
}
function ch(name,age){
    pa.call(this,name);
    this.age=age;
}
function createOb(o){
    function F(){};
    F.prototype =o;
    return new F();
}
//Child.prototype = new Parent();
function inheritPrototype(child,parent){
    var prototype = createOb(parent.prototype);//创建对象
    prototype.constructor=child;//增强对象
    child.prototype=prototype;//指定对象
}

inheritPrototype(ch,pa);
console.log('寄生组合式');
var ch1=new ch('zyy','18');
console.log(ch1);//ch { name: 'zyy', attr: [ 'a', 'b' ], age: '18' }
console.log(ch1 instanceof ch);//true
console.log(ch1 instanceof pa);//true
//优点:只调用了一次pa构造函数,因此避免了在pa.prototype上面创建不必要的多余的属性。