学无先后,达者为师

网站首页 前端文档 正文

JavaScript中的常用的继承方法(ES5和ES6)

作者:1900's 88 keys 更新时间: 2022-01-16 前端文档

原型链继承

步骤及关键点

基础步骤:

  1. 定义父类型构造函数
  2. 给父类型的原型添加方法
  3. 定义子类型的构造函数
  4. 创建父类型的对象赋值给子类型的原型
  5. 将子类型原型的构造函数属性设给为子类型
  6. 给子类型原型添加方法
  7. 创建子类型的对象:可以调用父类型的方法
  8. 修正constructor属性

关键点:
子类型的原型为父类型的一个实例对象


定义父类和子类构造函数

// 定义父类型构造函数
function Father()
{
    this.money = "一个亿!"
}
//给父类型的原型添加方法
Father.prototype.showMoney = function(){
    console.log("我爸实际有多少钱:"+this.money)
}

/*---------------上面是父类型,下面是子类型-----------*/

// 定义子类型构造函数
function Son()
{
    this.name =  "江流"
}

这里两个构造函数实际上是没有父子关系,在ES5中是无法直接通过两个函数类型来实现基础的,只是在这里先理解为父类和子类。大概的逻辑图为(地址是瞎写的,实际地址不是这样):
在这里插入图片描述


让子类型的原型为父类型的一个实例对象

这里分别给父类型的原型添加方法showMoney()和子类型的原型添加方法showName(),继承就是通过子类型的实例去访问父类型的方法,例如让子类实例son1去访问方法showMoney(),即:son1.showMoney();//报错,这样直接访问一定是会报错的。
但是如果使用son1去访问toString(),却不会报错,其原因就是son1会通过隐形原型属性__proto__找到Object原型对象,然后再其中找到toString()方法。故此让子类型的原型成为父类型的一个实例对象,就可以让子类型的实例通过隐形原型属性__proto__访问到父类型的原型中的方法。

故此代码为:

//使子类型的原型成为父类型的一个实例对象
Son.prototype = new Father();

但是这样会有一个问题,如果使用Son.prototyoe.constructor,会指向一个父类型构造函数,这是一个错误的答案,因为父类型生成的新实例对象中本身不会添加constructor属性,会通过隐形__proto__Father的原型函数中找到这个constructor属性,并且给出这个答案,故此我们还需要修正这个实例对象的constructor属性为子类型的构造函数。

//让新的子类型原型的constructor属性指向子类型
Son.prototype.constructor = Son;

测试代码:

// 定义父类型构造函数
function Father()
{
    this.money = "一个亿!"
}
//给父类型的原型添加方法
Father.prototype.showMoney = function(){
    console.log("我爸实际有多少钱:"+this.money)
}

// 定义子类型构造函数
function Son()
{
    this.name =  "江流"
}
/*---------------下面这里是继承的实际操作-----------*/

//使子类型的原型成为父类型的一个实例对象
Son.prototype = new Father();
//让新的子类型原型的constructor属性指向子类型
Son.prototype.constructor = Son;


//给子类型的原型添加方法
Son.prototype.showName= function(){
    console.log("名字:"+this.name);
}

/*---------------上面是完整继承代码,下面测试代码-----------*/

var son1 = new Son();// 通过Son构造函数创建的实例对象son1
son1.showMoney();//调用父类型的方法
son1.showName();//调用自身的方法

在这里插入图片描述

成功通过子类型的实例调用到父类型的方法,并且打印出了,而且不会影响自身的方法。
大概逻辑图是(地址是瞎写的,实际地址不是这样):
在这里插入图片描述


特点

优点:

  • 子类的新实例不会继承父类实例的属性

缺点:

  • 子类的新实例无法向父类构造函数传参
  • 所有新实例都会共享父类实例的属性(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)

借用构造函数继承(不是真正的继承)

步骤及关键点

步骤:

  1. 定义父类型构造函数
  2. 定义子类型构造函数
  3. 在子类型构造函数中调用父类型构造

关键点:
在子类型构造函数中用过call()、apply(),bind()调用父类型构造函数

call()方法将父类的this指向子类的this,这样就可以实现子类继承父类的属性

定义父类和子类构造函数

从刚才的例子稍作修改

// 定义父类型构造函数
function Father(name,age)
{
	this.name = name,
	this.age = age
};
/*---------------上面是父类型,下面是子类型-----------*/

// 定义子类型构造函数
function Son(name, age, money)
{
    Father.call(this, name, age);//通过call()来调用执行父级的属性
    this.money =  money
};

/*------测试-----*/
var son1 = new Son("心猿", 5000, 150000);
console.log(son1.name, son1.age, son1.money);

在这里插入图片描述


特点

优点:

  • 解决了原型链继承的部分缺点
  • 只继承了父类构造函数的属性,没有继承父类原型的属性
  • 可以继承多个构造函数属性(call多个)
  • 可以在子实例中向父类实例传参

缺点:

  • 只能继承父类构造函数的属性
  • 每次用每次都要重新调用
  • 每个新实例都会有父类构造函数的副本

组合继承(常用,结合上面两种方式使用)

步骤及关键点

  • 原型链 + 借用构造函数的组合继承
  • 利用原型链实现对父类型对象的方法继承
  • 利用call()借用父类型构建函数初始化相同属性
// 定义父类型构造函数
function Father(name, age,) {
    this.name = name;
    this.age = age;
}
//给父类型的原型添加方法
Father.prototype.showMoney = function () {
    console.log("我爸实际有多少钱:" + this.money)
}
/*---------------上面是父类型,下面是子类型-----------*/
// 定义子类型构造函数
function Son(name, age, money) {
    Father.call(this, name, age)
    this.money = money;
}

//使子类型的原型成为父类型的一个实例对象
Son.prototype = new Father();
//让新的子类型原型的constructor属性指向子类型
Son.prototype.constructor = Son;

//给子类型的原型添加方法
Son.prototype.showName = function () {
    console.log("我的名字是" + this.name + ",今年我" + this.age + "岁");
}

/*---------------上面是完整继承代码,下面测试代码-----------*/

var son1 = new Son("马少爷",21,"我爸对钱不感兴趣!");// 通过Son构造函数创建的实例对象son1
son1.showName();//调用自身的方法
son1.showMoney();//调用父类型的方法

在这里插入图片描述


特点

优点:

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

缺点:

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

寄生式继承(常用)

步骤及关键点

关键点:

  • 使用 Son.prototype = Object.create(Father.prototype)来替代Son.prototype = new Father();

实现原理

在上面的原型链继承中,我们通过Son.prototype = new Father();使子类型的原型成为父类型的一个实例对象,但是如果我们能直接让子类型拿到父类型原型上的方法,就也能得到继承的效果。
Object.create()这个方法就能实现这个效果。
所以将Son.prototype = Object.create(Father.prototype)来替代上面的代码就行了,但是这个Object.create()方法ES3是不支持的,所以还需要写个备用方案。

//这个也是Object.create()原理等价的
if(!Object.create){
    Object.create = function(obj){
        function F(){};
        F.prototype = obj;
        return new F();
    }
}

故此修改上面代码为:

// 定义父类型构造函数
function Father(name, age,) {
    this.name = name;
    this.age = age;
}

//给父类型的原型添加方法
Father.prototype.showMoney = function () {
    console.log("我爸实际有多少钱:" + this.money)
}

/*---------------上面是父类型,下面是子类型-----------*/

// 定义子类型构造函数
function Son(name, age, money) {
    Father.call(this, name, age)
    this.money = money;
}

if(!Object.create){
    Object.create = function(obj){
        function F(){};
        F.prototype = obj;
        return new F();
    }
}
Son.prototype = Object.create(Father.prototype);

//让新的子类型原型的constructor属性指向子类型
Son.prototype.constructor = Son;

//给子类型的原型添加方法
Son.prototype.showName = function () {
    console.log("我的名字是" + this.name + ",今年我" + this.age + "岁");
}


/*---------------上面是完整继承代码,下面测试代码-----------*/

var son1 = new Son("马少爷",21,"我爸对钱不感兴趣!");// 通过Son构造函数创建的实例对象son1
son1.showName();
son1.showMoney();

在这里插入图片描述


特点

优点:修复了组合继承的问题


ES6中extends继承(常用)

语法:

class Father {

}
class Son extends Father {

}

通过ES6语法重新写上面的例子

class Father {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    showMoney = function () {
        console.log("我爸实际有多少钱:" + this.money)
    }
}
class Son extends Father {
    constructor(name, age, money) {
        super(name, age, money)
        this.money = money
    }
    showName = function () {
        console.log("我的名字是" + this.name + ",今年我" + this.age + "岁");
    }
}

var son1  = new Son("马少爷", 21, "我爸对钱不感兴趣!");
son1.showName();
son1.showMoney();

在这里插入图片描述

原文链接:https://blog.csdn.net/weixin_45660621/article/details/121423001

栏目分类
最近更新