理解 JavaScript 的对象继承方法

理解 JavaScript 的对象继承方法

tips:博主不定期分享前端知识与算法。
公众号:FE Corner
wx小程序:FE Corner

在 JavaScript 中,对象继承是一个核心概念,它允许对象从其他对象继承属性和方法,从而实现代码的重用和组织。在这篇文章中,我们将详细探讨几种常见的对象继承方式,帮助你在实际开发中选择最合适的继承方法。

1. 原型链继承

原型链继承是 JavaScript 中最基本的继承方式。通过将子对象的 prototype 赋值为父对象的实例,子对象可以继承父对象的属性和方法。

function Parent() {
    this.name = 'Parent';
}

Parent.prototype.greet = function() {
    console.log('Hello from ' + this.name);
};

function Child() {
    this.name = 'Child';
}

Child.prototype = new Parent();

const child = new Child();
child.greet(); // 输出 "Hello from Child"
  • 优点:
    实现简单,适合属性和方法较少的对象。
  • 缺点:
    不能传参给父构造函数。
    所有子实例共享父对象的引用类型属性,可能导致数据污染。

2. 构造函数继承

构造函数继承通过在子对象的构造函数中调用父对象的构造函数,实现属性的继承。这种方式允许我们在创建子对象时传递参数给父构造函数。

function Parent(name) {
    this.name = name;
}

function Child(name, age) {
    Parent.call(this, name); // 继承父类属性
    this.age = age;
}

const child = new Child('Child', 10);
console.log(child.name); // 输出 "Child"
console.log(child.age);  // 输出 "10"
  • 优点:
    支持向父对象传递参数。
    避免了共享引用类型属性的问题。
  • 缺点:
    不能继承父对象的原型方法。

3. 组合继承

组合继承结合了原型链继承和构造函数继承的优点。它通过原型链继承父对象的原型方法,并通过构造函数继承父对象的属性。

function Parent(name) {
    this.name = name;
}

Parent.prototype.greet = function() {
    console.log('Hello from ' + this.name);
};

function Child(name, age) {
    Parent.call(this, name); // 继承属性
    this.age = age;
}

Child.prototype = new Parent(); // 继承方法
Child.prototype.constructor = Child;

const child = new Child('Child', 10);
child.greet(); // 输出 "Hello from Child"
console.log(child.age); // 输出 "10"
  • 优点:
    结合了两种继承方式的优势。
  • 缺点:
    父构造函数会被调用两次,可能导致性能问题。

4. 原型式继承

原型式继承基于一个现有对象创建一个新对象,将其作为新对象的原型。这种方法适合轻量级的对象创建场景。

const parent = {
    name: 'Parent',
    greet: function() {
        console.log('Hello from ' + this.name);
    }
};

const child = Object.create(parent);
child.name = 'Child';
child.greet(); // 输出 "Hello from Child"

优点:
简单易用,适合轻量级对象。
缺点:
共享引用类型的属性可能引发问题。

5. 寄生式继承

寄生式继承在原型式继承的基础上,进一步增强了新对象。它允许你在继承的基础上添加额外的方法和属性。

function createChild(original) {
    const clone = Object.create(original);
    clone.greet = function() {
        console.log('Hello from ' + this.name);
    };
    return clone;
}

const parent = { name: 'Parent' };
const child = createChild(parent);
child.name = 'Child';
child.greet(); // 输出 "Hello from Child"
  • 优点:
    可以在继承的过程中增强新对象。
  • 缺点:
    增加了一层函数调用,可能影响性能。

6. 寄生组合式继承

寄生组合式继承被认为是最理想的继承方式。它结合了寄生式继承和组合继承的优点,并避免了组合继承中多次调用父构造函数的问题。

function Parent(name) {
    this.name = name;
}

Parent.prototype.greet = function() {
    console.log('Hello from ' + this.name);
};

function Child(name, age) {
    Parent.call(this, name); // 继承属性
    this.age = age;
}

Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

const child = new Child('Child', 10);
child.greet(); // 输出 "Hello from Child"
console.log(child.age); // 输出 "10"
  • 优点:
    避免了组合继承中的缺点,并且性能优越。
  • 缺点:
    实现稍微复杂,但值得使用。

7.extends 关键字

在 ES6 中,extends 关键字引入了一种更加简洁和直观的方式来实现类的继承。extends 允许一个类继承另一个类的所有属性和方法,从而大大简化了 JavaScript 中的继承操作。(底层其实就是基于原型的寄生组合式继承)

  • ES6 类的基本概念
    在 ES6 中,class 关键字用于定义类,extends 关键字用于继承其他类。类语法是 JavaScript 构造函数和原型的语法糖,使得面向对象编程更加直观。
class Parent {
    constructor(name) {
        this.name = name;
    }

    greet() {
        console.log(`Hello from ${this.name}`);
    }
}

在这个例子中,我们定义了一个 Parent 类,constructor 是类的构造函数,greet 是一个实例方法。

使用 extends 实现继承
通过 extends 关键字,我们可以轻松地让一个类继承另一个类。

class Child extends Parent {
    constructor(name, age) {
        super(name); // 调用父类的构造函数
        this.age = age;
    }

    sayAge() {
        console.log(`I am ${this.age} years old`);
    }
}

const child = new Child('Child', 10);
child.greet(); // 输出 "Hello from Child"
child.sayAge(); // 输出 "I am 10 years old"

在这个例子中:
Child 类通过 extends 继承了 Parent 类。
super(name) 调用了父类 Parent 的构造函数,并将参数 name 传递给父类。这一步非常关键,因为它确保了父类的属性在子类中被正确初始化。
Child 类还定义了一个新的方法 sayAge,这是 Parent 类中没有的。
super 关键字的作用

  • 在子类中,super 关键字可以用来:
    调用父类的构造函数。
    访问父类的实例方法或静态方法。
class Animal {
    constructor(name) {
        this.name = name;
    }

    speak() {
        console.log(`${this.name} makes a noise.`);
    }
}

class Dog extends Animal {
    constructor(name) {
        super(name); // 调用父类的构造函数
    }

    speak() {
        super.speak(); // 调用父类的方法
        console.log(`${this.name} barks.`);
    }
}

const dog = new Dog('Rex');
dog.speak();
// 输出:
// Rex makes a noise.
// Rex barks.
  • 在上面的例子中:
    super(name) 调用了 Animal 类的构造函数,确保 Dog 实例具有 name 属性。
    super.speak() 调用了父类的 speak 方法,并在其基础上添加了额外的功能。
    静态方法的继承
    extends 也可以继承父类的静态方法。静态方法是在类本身上定义的方法,而不是在实例上。
class Parent {
    static greet() {
        console.log('Hello from Parent');
    }
}

class Child extends Parent {}

Child.greet(); // 输出 "Hello from Parent"

在这里,Child 类继承了 Parent 类的静态方法 greet。

  • 为什么选择 extends?
    语法简洁:extends 提供了一种更易读、更易写的继承方式,与传统的构造函数和原型继承相比,代码更为简洁直观。
    功能强大:通过 super 关键字,可以轻松地在子类中调用父类的构造函数和方法,这让继承关系更为明确和灵活。
    原生支持:extends 是 ES6 标准的一部分,原生支持类继承,减少了手动设置原型链的复杂性。

小结

  • 原型链继承:简单直接,但共享引用类型会有问题。
  • 构造函数继承:能传参,但无法继承父类原型方法。
  • 组合继承:兼顾两者优点,但父构造函数被调用两次。
  • 原型式继承:适合简单对象继承,但也有共享引用类型问题。
  • 寄生式继承:增强版的原型式继承,灵活但性能稍差。
  • 寄生组合式继承:最优继承方式,避免了组合继承的缺点。
  • ES6中 extends关键字:简化了类的继承过程,还通过 super 关键字提供了对父类构造函数和方法的方便访问。

JavaScript 提供了多种对象继承方法,每种方法都有其特定的适用场景和优缺点。选择适合需求的继承方式,可以让我们的代码更加简洁、可维护,并且性能良好。在实际开发中,寄生组合式继承被广泛认为是最理想的继承方式,但根据具体场景,其他方法也可以为我们提供更简单或更直接的解决方案。
在这里插入图片描述

  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值