继承

时间:2019-09-14
本文章向大家介绍继承,主要包括继承使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

六、继承(※)

假设原型链的重点object.prototype为原型链的E(end)端,原型链的起点为S(start)端。

通过前面学习原型链的学习我们知道,处于S端的对象,可以通过S->E的单向查早,访问到原型链上所有的方法与属性。因此这给继承提供了理论基础。我们只需再S端添加新的对象,那么新对象就能够通过原型链访问到父级的方法与属性。

因为封装一个对象由构造函数与原型链共同组成,因此继承也会分别有构造函数的继承与原型的继承。

先声明一个Person对象,该对象将多为父级,而子级cPerson将要继承Person的所有属性和方法。

var Person = function(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.getName = function() {
    return this.name;
}

Person.prototype.getAge = function() {
    return this.age;
}

1.原型链继承

要让子类继承父类方法,需要将子类对象的原型加入到原型链中。只需要将子集的原型对象设置为父级的实例,加入到原型链中即可。然后通过__proto__就可以访问父类对象的原型 +

cPerson.prototype = new Person(name, age);

// 添加更多方法
cPerson.prototype.getLive = function() {}

 有几点注意事项:

(1)不要忘记,所有的引用类型都默认继承了Object。这个继承也是通过原型链实现的。

(2)可以使用instanceof或者isPrototypeof()来确定原型和实例之间的关系。

instanceof:测试实例与原型链中出现过的构造函数;

isPrototypeof():测试原型链中出现过的原型。

alert(instance instanceof Object); //true

alert(SuperType.prototype.isPrototypeof(instance)); //true

(3)子类型有时需要覆盖超类型中的某个方法,或者需要添加某个不存在的方法。给原型添加方法的代码一定要放在替换原型(替换原型就是实现继承的那句代码)的语句之后。

(4)不能使用字面量创建原型方法,否则会重写原型链。

原型链继承的问题:

(1)如果构造函数的某个属性的值是一个引用类型,那么该构造函数所有的实例都会共享这个属性。

(2)创建子类型的实例时,不能向超类型构造函数中传递参数。(其实可以,但会影响其它对象实例)

2.构造函数的继承

就是想让父级构造函数中的操作在子集构造函数中重现一遍即可。可以通过call方法来达到目的:

//构造函数的继承
var Student = function(name, age, grade) {
    // 通过call方法还原Person构造函数中的所有处理逻辑
    Student.call(Person, name, age);
    this.grade = grade;
}


// 等价于
var Student = function(name, age, grade) {
    this.name = name;
    this.age = age;
    this.grade = grade;
}

构造函数继承的优势:可以向超类型构造函数传递参数。(因为使用了call、apply)。

构造函数继承的缺点:

(1)仍然无法避免构造函数模式存在的问题,方法都再构造函数中定义,不能实现函数复用。

(2)在超类型的原型中定义的方法,对子类型是不可见的。

3.组合继承(常用)

将原型链继承和构造函数继承相结合。

 这样,既通过在原型上定义方法实现了函数复用,又能保证每个实例都有它自己的属性。

//父类:人
    function Person () {
      this.head = '脑袋瓜子';
      this.emotion = ['喜', '怒', '哀', '乐']; 
    }
    //将 Person 类中需共享的方法放到 prototype 中,实现复用
    Person.prototype.eat = function () {
      console.log('吃吃喝喝');
    }
    Person.prototype.sleep = function () {
      console.log('睡觉');
    }
    Person.prototype.run = function () {
      console.log('快跑');
    }
    //继承属性(子类:学生,继承了“人”这个类) 
    function Student(studentID) {
      this.studentID = studentID;
      Person.call(this);
    }
    //继承方法
    Student.prototype = new Person();  //此时 Student.prototype 中的 constructor 被重写了,会导致 stu1.constructor === Person
    Student.prototype.constructor = Student;  //将 Student 原型对象的 constructor 指针重新指向 Student 本身

    var stu1 = new Student(1001);
    console.log(stu1.emotion); //['喜', '怒', '哀', '乐']

    stu1.emotion.push('愁');
    console.log(stu1.emotion); //["喜", "怒", "哀", "乐", "愁"]
    
    var stu2 = new Student(1002);
    console.log(stu2.emotion); //["喜", "怒", "哀", "乐"]

    stu1.eat(); //吃吃喝喝
    stu2.run(); //快跑
    console.log(stu1.constructor);  //Student

注意要重写constructor的指针。

缺点:无论在什么情况下,都会调用两次超类型构造函数:

一次是在创建子类型原型的时候;

一次是在子类型构造函数内部。

4.原型式继承

用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。

Object.create就是这个原理。

var person={
   name:'Nico';
    friends:['shelly'.'court','van']  
}
// 第二个参数可省
var person1=Object.create(person,{
    name:{value:'gray'}
});

alert(person1.name); //'gray'    

Object.create第一个参数是用作新对象原型的对象,第二个参数可省,是新对象定义额外属性的对象。

原型式继承的缺点:

(1)所有实例都会继承原型上的属性。(和原型链继承类似)

(2)无法实现复用。(和构造函数继承类似)

5.寄生式继承

创建一个仅用于封装继承过程的函数,在内部以某种方式增强对象,再返回这个对象。

优点:寄生继承(原型式继承)就是不用实例化父类了,直接实例化一个临时副本实现了相同的原型链继承。(即子类的原型指向父类副本的实例从而实现原型共享)

缺点:无法实现复用。(和构造函数继承类似)

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

之前说了组合继承的缺点是会调用两次超类型构造函数。

  //父类:人
    function Person () {
      this.head = '脑袋瓜子';
      this.emotion = ['喜', '怒', '哀', '乐'];
    }
    //将 Person 类中需共享的方法放到 prototype 中,实现复用
    Person.prototype.eat = function () {
      console.log('吃吃喝喝');
    }
    Person.prototype.sleep = function () {
      console.log('睡觉');
    }
    Person.prototype.run = function () {
      console.log('快跑');
    }
    //子类:学生
    function Student(studentID) {
      this.studentID = studentID;
      Person.call(this); //第二次调用
    }
    
    Student.prototype = new Person();   //第一次调用
    Student.prototype.constructor = Student;  

    var stu1 = new Student(1001);
    console.log(stu1.emotion); //['喜', '怒', '哀', '乐']

    stu1.emotion.push('愁');
    console.log(stu1.emotion); //["喜", "怒", "哀", "乐", "愁"]
    
    var stu2 = new Student(1002);
    console.log(stu2.emotion); //["喜", "怒", "哀", "乐"]

    stu1.eat(); //吃吃喝喝
    stu2.run(); //快跑
    console.log(stu1.constructor);  //Student

调用两次后,就会有两组属性,一组在实例上,一组在Student的原型中。

寄生式组合继承利用寄生继承解决组合继承的问题。

寄生式组合继承通过构造函数来继承属性,通过原型链的混成形式来继承方法。

function SuperType(name,colors){
  this.name=name;
  this.colors=colors;
}
SuperType.prototype.getSuperProperty=function(){ return this.name; }
function SubType(job,name,colors){
  SuperType.call(this,name,colors);
  this.job=job;
}
SubType.prototype.getSubPrototype=function(){ return this.job; }
function inherit(subType,superType){
  var prototype=Object.create(superType.prototype);
  prototype.constructor=subType;
  subType.prototype=prototype;
}
inherit(SubType,SuperType);
var instance=new SubType("doctor","John",["red","green"]);
console.log(instance.getSubPrototype());  //输出"doctor"
console.log(instance.getSuperProperty());  //输出"John",成功调用在父类原型定义的方法

其中

function inherit(subType,superType){
  var prototype=Object.create(superType.prototype);
  prototype.constructor=subType;
  subType.prototype=prototype;
}

就是寄生组合式继承的基本模式。如果暂时理解不了就先记住吧。

原文地址:https://www.cnblogs.com/sherrycat/p/11503699.html