JavaScript中对象继承的方式有哪些?

1. 原型链继承

通过将一个对象的 prototype 属性指向另一个对象,从而实现继承。

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

function Child() {}

// 设置原型链
Child.prototype = new Parent();

const child = new Child();
console.log(child.name); // "Parent"

子类通过原型链继承父类的属性和方法。父类的属性会成为子类实例的共享属性。

function Parent() {
    this.hobbies = ["reading", "coding"]; // 引用类型属性
}

function Child() {}

// 设置原型链
Child.prototype = new Parent();

const child1 = new Child();
const child2 = new Child();

child1.hobbies.push("traveling"); // 修改 child1 的 hobbies

console.log(child1.hobbies); // ["reading", "coding", "traveling"]
console.log(child2.hobbies); // ["reading", "coding", "traveling"] -> 被意外修改

当使用原型链继承时,父类的引用类型属性会被所有子类实例共享,因此修改一个实例的属性会影响所有其他实例

如何解决这个问题?通过在子类的构造函数中调用父类构造函数,避免共享引用类型属性,也就是下面所提到的借用构造函数的方式。

2. 借用构造函数

通过在子类构造函数中调用父类的构造函数实现。

function Parent(name) {
    this.name = name;
    this.hobbies = ["reading", "coding"];
}

function Child(name, age) {
    Parent.call(this, name); // 借用 Parent 的构造函数
    this.age = age;
}

const child1 = new Child("Alice", 25);
const child2 = new Child("Bob", 30);

child1.hobbies.push("traveling");
console.log(child1.hobbies); // ["reading", "coding", "traveling"]
console.log(child2.hobbies); // ["reading", "coding"]

父类构造函数中定义的方法会在每个子类实例中重新创建一份,导致方法无法复用,占用更多内存。

3. 组合继承(原型链 + 构造函数)

结合原型链和构造函数继承的优点。

function Parent(name) {
    this.name = name;
    this.hobbies = ["reading", "coding"];
}

Parent.prototype.greet = function () {
    console.log(`Hello, ${this.name}`);
};

function Child(name, age) {
    Parent.call(this, name); // 借用构造函数
    this.age = age;
}

Child.prototype = new Parent(); // 设置原型链
Child.prototype.constructor = Child; // 修复构造函数引用

const child1 = new Child("Alice", 25);
child1.greet(); // "Hello, Alice"

继承了父类的实例属性和原型方法,不会共享引用类型属性,每个实例都可以单独使用父类的方法。

问题:父类构造函数被调用两次:一次在原型链设置时,一次在子类构造函数中。

4. 寄生式组合继承

优化组合继承,避免调用父类构造函数两次。

function Parent(name) {
    this.name = name;
    this.hobbies = ["reading", "coding"];
}

Parent.prototype.greet = function () {
    console.log(`Hello, ${this.name}`);
};

function Child(name, age) {
    Parent.call(this, name);
    this.age = age;
}

// 创建子类的原型对象,避免调用父类构造函数
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

const child1 = new Child("Alice", 25);
child1.greet(); // "Hello, Alice"

Object.create()以一个现有对象作为原型,创建一个新对象。
采用这种方式父类构造函数只调用一次,继承了父类的实例属性和原型方法,高效且是目前最常用的继承方式。

5. Object.create 原型继承

通过 Object.create 方法创建新对象,直接设置其原型。

const parent = {
    greet: function () {
        console.log(`Hello, ${this.name}`);
    },
};

const child = Object.create(parent);
child.name = "Alice";
child.greet(); // "Hello, Alice"

简单、轻量,适合只需要继承原型对象的方法和属性的场景。
问题:无法向父类传递参数

6. extends 继承

在ES6之后,可以使用extends 关键字进行继承。

class Parent {
    constructor(name) {
        this.name = name;
    }

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

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

const child = new Child("Alice", 25);
child.greet(); // "Hello, Alice"

简化了继承的语法,支持调用父类构造函数(super),更符合面向对象编程的习惯。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值