ES5的6种继承方式

141 阅读3分钟

总结一下在ES6通过class类之前实现继承的6种方式。

1、原型链实现继承

  1. 示例:子类Student继承于父类People 001.jpg
  2. 关键语句:Student.prototype = new People(); Student的原型prototype指向People的实例,要写在Student.prototype构造方法之前。
function People(name, age) {
    this.name = name;
    this.age = age;
}
People.prototype.sleep = function () {
    console.log(`${this.name} Zzz`);
}
function Student(name, age, gender){
    this.name = name;
    this.age = age;
    this.gender = gender;
}
// 关键语句实现继承
Student.prototype = new People();
Student.prototype.study = function(){
    console.log(`${this.name} study`);
}
Student.prototype.exam = function(){
    console.log(`${this.name} exam`);
}
//重写父类的sleep方法
Student.prototype.sleep = function () {
    console.log(`Hello ${this.name}, I am student`);
}
var xiaoming = new Student('xiaoming',10,'boy');
// 能够通过原型链访问父类的方法
xiaoming.sleep();
// 访问自身的方法
xiaoming.study();
  1. 原型链继承的问题:

问题一:如果父类的属性中有引用类型值,则这个属性会被所有子类的实例共享。

function People() {
  // 父类属性中有引用类型值
  this.arr = [1, 2, 3];
}
function Student() {
}
Student.prototype = new People();
let xiaoming = new Student();
let xiaohong = new Student();
xiaoming.arr.push(999);
console.log('小明' + xiaoming.arr); // [1, 2, 3, 999]
// 因为属性被所有子类的实例共享,小明添加数组值,实例对象小红也得到新增的结果
console.log('小红' + xiaohong.arr); // [1, 2, 3, 999]

问题二:子类的构造函数中,往往需要重复定义很多父类定义过的属性。

例如父类People定义的属性name、age,子类Student中还需重复定义一次。

2、借用构造函数

为解决原型中包含引用类型值被所有子类的实例共享和子类构造函数不优雅的问题,使用“借助构造函数”的技术解决,思路:在子类构造函数的内部调用父类的构造函数,注意使用 call() 绑定上下文。

function People(name, age) {
  this.name = name;
  this.age = age;
}
function Student(name, age, gender) {
  // 子类构造函数的内部调用父类的构造函数
  People.call(this, name, age);
  this.gender = gender;
}
const xiaoming = new Student('小明', 12, 'boy');

3、组合继承

1、将原型链继承和借用构造函数的技术组合到一起,叫做组合继承

    // 父类
    function People(name, age) {
        this.name = name;
        this.age = age;
    }
    People.prototype.sleep = function () {
        console.log(`${this.name} is sleeping`);
    }
    // 子类
    function Student(name, age, gender) {
        // 借用构造函数
        People.call(this, name, age);
        this.gender = gender;
    }
    // 通过原型链实现继承
    Student.prototype = new People();
    // 重写父类方法
    Student.prototype.sleep = function(){
        console.log(`I am student ${this.name}. I am sleeping`);
    }
    Student.prototype.study = function(){
        console.log(`${this.name} is study`);
    }
    Student.prototype.exam = function(){
        console.log(`${this.name} is exam`);
    }

    var xiaoming = new Student('xiaoming', 12, 'boy');
    xiaoming.sleep(); // I am student xiaoming. I am sleeping
    xiaoming.study(); // xiaoming is study

2、组合继承的问题:组合继承会调用两次父类的构造函数,一次是在创建子类原型时( new People() ),另一次是在子类构造函数的内部( People.call(this, name, age) )

4、原型式继承

1、IE9开始支持Object.create()方法,可以根据指定的对象为原型创建出新对象,Object.create()方法有两个参数,第一个参数表示指定的对象,第二个参数表示为新创建的对象补充添加其它属性。

2、在没有必要创建构造函数(没有类),没有类和类继承关系,而只是想让新对象与现有对象“类似”的情况下,使用Object.create()即可,这就称为原型式继承。

3、关键语句:var obj2 = Object.create(obj1);

image.png

// 没有类,是对象与对象的继承,即原型式继承
var obj1 = {
    a: 10,
    b: 20,
    test: function(){
        // this是obj2
        console.log(this.a + this.b);
    }
}
var obj2 = Object.create(obj1, {
    c: {
        value: 100
    },
    a: {
        value: 200
    }
});
// obj2可以打点调用obj1的test方法,因为obj1是obj2的原型
obj2.test();    // 220

5、寄生式继承

1、介绍:寄生式继承就是编写一个函数,它可以“增强对象”,只要把对象传入这个函数,这个函数将以此对象为“基础”创建出新对象,并为新对象赋予新的预置方法

2、示例:对象作为参数o传入函数 --> 创建出新对象p --> 新对象中新增方法study

image.png

// user1和user2作为实参传入工厂函数
var user1 = {
    name: 'xiaoming',
    age: 12,
}
var user2 = {
    name: 'xiaohong',
    age: 11,
}
// 寄生式继承
// 接收参数o ,o是一个对象
function fn(o){
    // 以o为原型创建出新的对象p
    var p = Object.create(o);
    // 在实例上补充方法
    p.study = function(){
        console.log(`${this.name} study`);
    }
    // 返回新对象p
    return p;
}
// 调用工厂函数,传递参数user1,会以user1为原型创建出新对象p1
var p1 = fn(user1);
p1.study();  // xiaoming study
var p2 = fn(user2);
p2.study();  // xiaohong study

3、寄生式继承缺点:不能做到函数复用而降低效率,即“方法没有写到prototype上”,即p1的sayHello和p2的sayHello方法是内存中不同的函数。

6、寄生组合式继承 - 难点

1、前瞻:组合继承问题是会调用两次父类的构造函数:一次在创建子类原型的时候,另一次在子类构造函数的内部,其实在创建子类原型的时候不必调用父类的构造函数

image.png

2、关键语句:inheritPrototype函数,这个函数接收两个参数,subType是子类的构造函数,superType是父类的构造函数;以父类的原型创建出新对象prototype,新对象prototype再绑定给子类的prototype。

image.png

 // 这个函数接收两个参数,subType是子类的构造函数,superType是父类的构造函数
 // inheritPrototype函数功能,以父类的原型创建出新对象prototype,新对象prototype再绑定给子类的prototype
 function inheritPrototype(subType, superType) {
     var prototype = Object.create(superType.prototype);
     subType.prototype = prototype;
 }

 function People(name, age) {
     this.name = name;
     this.age = age;
 }
 People.prototype.sleep = function () {
     console.log(`${this.name} Zzz`);
 }

 function Student(name, age, gender) {
     // 借助构造函数
     People.call(this, name, age);
     this.gender = gender;
 }
 // 调用编写的inheritPrototype函数,这个函数可以让Student类的prototype指向“以People.prototype为原型的一个新对象”
 inheritPrototype(Student, People);

 Student.prototype.study = function () {
     console.log(`${this.name} study`);
 }
 Student.prototype.exam = function () {
     console.log(`${this.name} exam`);
 }

 var xiaoming = new Student('xiaoming', 10, 'boy');
 xiaoming.sleep();  // xiaoming Zzz
 xiaoming.study();  // xiaoming study
 xiaoming.exam();  // xiaoming exam

写在最后:这是我的第一篇文章分享,前端新人,如有错误之处欢迎更正。