javascript 继承的实现与优缺点

20 阅读3分钟

在JavaScript中,继承可以通过多种方式实现,每种方式都有其优点和缺点。以下是几种常见的继承实现方式的具体介绍,包括它们的实现方法、优缺点。

1. 原型链继承

实现方式

通过将子类的原型对象指向父类的实例来建立继承关系。

javascript
function Parent() {  
    this.parentProperty = 'I am a parent property';  
}  

Parent.prototype.parentMethod = function() {  
    console.log('This is a method from the parent');  
};  

function Child() {  
    this.childProperty = 'I am a child property';  
}  

Child.prototype = new Parent();  

const childInstance = new Child();  
console.log(childInstance.parentProperty); // 输出 'I am a parent property'  
childInstance.parentMethod(); // 输出 'This is a method from the parent'  

优缺点

  • 优点:

    • 简单易用,语法清晰。
    • 可以实现方法共享,节省内存空间。
  • 缺点:

    • 共享父类构造函数的属性,所有子实例共享这些属性,可能导致数据污染。
    • 无法使用父构造函数的初始化功能,限制了设计。
    • 原型修改会影响所有子类实例。

2. 构造函数继承

实现方式

在子构造函数内部调用父构造函数,使用callapply方法实现属性的继承。

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

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

const childInstance = new Child('Alice', 10);  
console.log(childInstance.name); // 输出 'Alice'  
console.log(childInstance.age); // 输出 10  

优缺点

  • 优点:

    • 每个实例都拥有自己的属性副本,避免数据共享和污染。
    • 可以使用父构造函数为子类实例初始化属性。
  • 缺点:

    • 方法不共享,导致每个实例生成自己的方法副本,增加内存开销。
    • 无法访问父类的原型方法,需要显式实现。

3. 组合继承

实现方式

结合原型链继承和构造函数继承,既可以初始化子类的独立属性,又可以共享父类的方法。

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

Parent.prototype.sayName = function() {  
    console.log(this.name);  
};  

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

Child.prototype = Object.create(Parent.prototype); // 继承原型方法  
Child.prototype.constructor = Child;  

const childInstance = new Child('Bob', 12);  
childInstance.sayName(); // 输出 'Bob'  
console.log(childInstance.age); // 输出 12  

优缺点

  • 优点:

    • 同时继承了父类的构造函数属性和原型方法。
    • 提高了内存使用效率,通过原型共享方法。
  • 缺点:

    • 调用父构造函数两次,可能造成性能开销。
    • 实现相对较复杂,容易对设计不熟悉的人造成困惑。

4. ES6类继承

实现方式

使用ES6引入的class语法,可以通过extends关键字实现简洁的继承。

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

    sayName() {  
        console.log(this.name);  
    }  
}  

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

const childInstance = new Child('Charlie', 8);  
childInstance.sayName(); // 输出 'Charlie'  
console.log(childInstance.age); // 输出 8  

优缺点

  • 优点:

    • 语法简洁,代码清晰易读。
    • 继承关系直观,使用super关键字调用父类的方法和构造函数。
  • 缺点:

    • 需要 ES6 支持,对于老旧浏览器兼容性问题需要注意。
    • 在类和原型之间的关系可能会增加理解的复杂性。

5. 寄生式继承

实现方式

通过一个工厂函数来创建对象,继承时使用Object.create

javascript
function createChild(proto, properties) {  
    const child = Object.create(proto); // 创建新对象  
    Object.assign(child, properties); // 赋值给新对象  
    return child;  
}  

const parentInstance = {  
    name: 'Parent',  
    sayName: function() {  
        console.log(this.name);  
    }  
};  

const childInstance = createChild(parentInstance, { name: 'Child' });  
childInstance.sayName(); // 输出 'Child'  

优缺点

  • 优点:

    • 灵活性高,允许在对象创建时动态配置属性。
    • 不需要构造函数,提高了灵活性。
  • 缺点:

    • 可能导致代码可读性降低,尤其是对于不熟悉这个模式的开发者。
    • 不能实例化构造函数,限制了某些特定功能。

总结

JavaScript提供了多种实现继承的方式,每种方式各有其适用的场景及优缺点。选择合适的继承策略应根据具体项目需求、性能考虑和代码可读性进行权衡。对于复杂的继承关系,组合继承或ES6类继承常常是更好的选择。