javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JavaScript实现继承

详解JavaScript中8 种不同的继承实现方式

作者:北辰alk

在 JavaScript 中,继承是实现代码复用和构建复杂对象关系的重要机制,虽然 JavaScript 是一门基于原型的语言,不像传统面向对象语言那样有类的概念,但它提供了多种实现继承的方式,本文将详细介绍 JavaScript 中 8 种不同的继承实现方式,需要的朋友可以参考下

前言

在 JavaScript 中,继承是实现代码复用和构建复杂对象关系的重要机制。虽然 JavaScript 是一门基于原型的语言,不像传统面向对象语言那样有类的概念,但它提供了多种实现继承的方式。本文将详细介绍 JavaScript 中 8 种不同的继承实现方式,每种方式都会配有代码示例和详细解释,最后还会通过流程图比较各种继承方式的特点。

1. 原型链继承

原型链继承是 JavaScript 中最基本的继承方式,它利用原型让一个引用类型继承另一个引用类型的属性和方法。

function Parent() {
  this.name = 'Parent';
  this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function() {
  return this.name;
};

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

// 关键步骤:将Child的原型指向Parent的实例
Child.prototype = new Parent();

var child1 = new Child();
console.log(child1.getName()); // "Parent"
console.log(child1.childName); // "Child"

// 问题:引用类型的属性会被所有实例共享
child1.colors.push('black');
var child2 = new Child();
console.log(child2.colors); // ["red", "blue", "green", "black"]

特点:

2. 构造函数继承(经典继承)

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

function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function() {
  return this.name;
};

function Child(name, age) {
  // 关键步骤:在子类构造函数中调用父类构造函数
  Parent.call(this, name);
  this.age = age;
}

var child1 = new Child('Tom', 18);
child1.colors.push('black');
console.log(child1.name); // "Tom"
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]

var child2 = new Child('Jerry', 20);
console.log(child2.colors); // ["red", "blue", "green"]

// 问题:无法继承父类原型上的方法
console.log(child1.getName); // undefined

特点:

3. 组合继承(最常用)

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

function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function() {
  return this.name;
};

function Child(name, age) {
  // 构造函数继承 - 第二次调用Parent()
  Parent.call(this, name);
  this.age = age;
}

// 原型链继承 - 第一次调用Parent()
Child.prototype = new Parent();
// 修正constructor指向
Child.prototype.constructor = Child;

Child.prototype.getAge = function() {
  return this.age;
};

var child1 = new Child('Tom', 18);
child1.colors.push('black');
console.log(child1.name); // "Tom"
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]
console.log(child1.getName()); // "Tom"
console.log(child1.getAge()); // 18

var child2 = new Child('Jerry', 20);
console.log(child2.colors); // ["red", "blue", "green"]
console.log(child2.getName()); // "Jerry"
console.log(child2.getAge()); // 20

特点:

4. 原型式继承

借助原型可以基于已有对象创建新对象。

function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

var person = {
  name: 'Nicholas',
  friends: ['Shelby', 'Court', 'Van']
};

var anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');

var yetAnotherPerson = object(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie');

console.log(person.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"]

ES5 规范化了原型式继承,新增了 Object.create() 方法:

var person = {
  name: 'Nicholas',
  friends: ['Shelby', 'Court', 'Van']
};

var anotherPerson = Object.create(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');

var yetAnotherPerson = Object.create(person, {
  name: {
    value: 'Linda'
  }
});
yetAnotherPerson.friends.push('Barbie');

console.log(person.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"]

特点:

5. 寄生式继承

创建一个仅用于封装继承过程的函数,在函数内部增强对象。

function createAnother(original) {
  var clone = Object.create(original); // 通过调用函数创建一个新对象
  clone.sayHi = function() { // 以某种方式增强这个对象
    console.log('Hi');
  };
  return clone; // 返回这个对象
}

var person = {
  name: 'Nicholas',
  friends: ['Shelby', 'Court', 'Van']
};

var anotherPerson = createAnother(person);
anotherPerson.sayHi(); // "Hi"

特点:

6. 寄生组合式继承(最理想)

通过借用构造函数继承属性,通过原型链混成形式继承方法。

function inheritPrototype(child, parent) {
  var prototype = Object.create(parent.prototype); // 创建父类原型的副本
  prototype.constructor = child; // 修正constructor指向
  child.prototype = prototype; // 将副本赋值给子类原型
}

function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function() {
  return this.name;
};

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

// 关键步骤:避免调用Parent构造函数,直接使用父类原型
inheritPrototype(Child, Parent);

Child.prototype.getAge = function() {
  return this.age;
};

var child1 = new Child('Tom', 18);
var child2 = new Child('Jerry', 20);

console.log(child1.getName()); // "Tom"
console.log(child1.getAge()); // 18
console.log(child2.getName()); // "Jerry"
console.log(child2.getAge()); // 20

特点:

7. ES6 Class 继承

ES6 引入了 class 语法糖,使得继承更加清晰易读。

class Parent {
  constructor(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
  }
  
  getName() {
    return this.name;
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name); // 调用父类的constructor
    this.age = age;
  }
  
  getAge() {
    return this.age;
  }
}

const child1 = new Child('Tom', 18);
child1.colors.push('black');
console.log(child1.getName()); // "Tom"
console.log(child1.getAge()); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]

const child2 = new Child('Jerry', 20);
console.log(child2.colors); // ["red", "blue", "green"]

特点:

8. 混入方式继承(多继承)

JavaScript 本身不支持多继承,但可以通过混入(Mixin)的方式实现类似功能。

function extend(target, ...sources) {
  sources.forEach(source => {
    for (let key in source) {
      if (source.hasOwnProperty(key)) {
        target[key] = source[key];
      }
    }
    
    // 支持Symbol属性
    const symbols = Object.getOwnPropertySymbols(source);
    symbols.forEach(symbol => {
      target[symbol] = source[symbol];
    });
  });
  return target;
}

const canEat = {
  eat() {
    console.log(`${this.name} is eating.`);
  }
};

const canWalk = {
  walk() {
    console.log(`${this.name} is walking.`);
  }
};

const canSwim = {
  swim() {
    console.log(`${this.name} is swimming.`);
  }
};

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

// 将多个mixin混入Person的原型
extend(Person.prototype, canEat, canWalk);

const person = new Person('John');
person.eat(); // "John is eating."
person.walk(); // "John is walking."
// person.swim(); // 报错,没有swim方法

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

extend(Fish.prototype, canEat, canSwim);

const fish = new Fish('Nemo');
fish.eat(); // "Nemo is eating."
fish.swim(); // "Nemo is swimming."
// fish.walk(); // 报错,没有walk方法

ES6 中可以使用 Object.assign 简化混入:

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

Object.assign(Person.prototype, canEat, canWalk);

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

Object.assign(Fish.prototype, canEat, canSwim);

特点:

继承方式比较流程图

总结

JavaScript 提供了多种实现继承的方式,每种方式都有其适用场景和优缺点:

  1. 原型链继承:简单但引用类型属性会被共享
  2. 构造函数继承:可解决引用共享问题但无法继承原型方法
  3. 组合继承:最常用的继承方式,但会调用两次父类构造函数
  4. 原型式继承:适用于基于已有对象创建新对象
  5. 寄生式继承:增强对象但无法函数复用
  6. 寄生组合继承:最理想的继承方式,高效且完整
  7. ES6 Class继承:语法糖,底层仍是原型继承
  8. 混入方式:实现类似多继承的功能

在实际开发中,ES6 的 class 语法是最推荐的方式,它语法简洁,易于理解,且底层实现高效。对于需要兼容旧浏览器的项目,可以使用寄生组合式继承作为替代方案。

理解这些继承方式的原理和区别,有助于我们在不同场景下选择最合适的实现方式,写出更优雅、高效的 JavaScript 代码。

以上就是详解JavaScript中8 种不同的继承实现方式的详细内容,更多关于JavaScript实现继承的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文