“JavaScript继承方法大全:选择最适合你的继承方式!“

引言

  JavaScript 是一种面向对象的编程语言,在实现面向对象编程时,继承是一个非常重要的概念。JavaScript 中的继承方式有多种,每种方式都有其优缺点和适用场景。在本文中,我们将介绍 JavaScript 中常用的几种继承方式,帮助您更好地理解和应用面向对象编程的相关概念。

原型链继承

  通过让一个对象的原型指向另一个对象来实现继承。子类的原型对象是父类的实例,因此可以继承父类的属性和方法。但是,父类的引用类型属性会被子类实例共享,容易造成修改的混淆。

  • 代码举例:
function Animal() {
  this.species = 'animal';
}

function Cat(name, color) {
  this.name = name;
  this.color = color;
}

Cat.prototype = new Animal();

let cat1 = new Cat('Kitty', 'white');
console.log(cat1.species); // 输出:'animal'

  在原型链继承中,通过创建一个父类的实例,并将其赋值给子类的原型对象,实现了子类对父类原型属性和方法的继承。
  在上述代码中,我们创建了一个 Animal 构造函数,并给它的原型对象添加了一个 species 属性。然后,我们创建了一个 Cat 构造函数,并将其原型对象设置为一个 Animal 的实例。这样,Cat 的实例cat1就可以通过原型链访问到 Animal 原型对象上的 species 属性,输出了 ‘animal’

优缺点
  1. 优点:
  • 实现简单,容易理解
  • 可以通过原型链访问父类的方法和属性。
  1. 缺点:
  • 无法向父类传递参数。
  • 子类实例共享父类引用类型属性,容易造成修改污染。

构造函数继承

  通过在子类构造函数中调用父类构造函数来实现继承。子类的实例可以拥有自己的属性和方法,且不会共享父类的引用类型属性。但是,无法继承父类原型对象中的方法

  • 代码举例:
function Animal(name) {
  this.name = name;
}

function Cat(name, color) {
  Animal.call(this, name);
  this.color = color;
}

let cat1 = new Cat('Kitty', 'white');
console.log(cat1.name); // 输出:'Kitty'

  在构造函数继承中,通过在子类的构造函数中调用父类的构造函数,并使用 call 或 apply 方法改变 this 的指向,实现了子类对父类属性的继承。
  在上述代码中,我们创建了一个 Animal 构造函数,其中包含一个 name 属性,然后,我们创建了一个 Cat 构造函数,通过调用父类构造函数,将父类的 name 属性传递给子类,并添加了一个 color 属性。这样,Cat 的实例cat1就可以访问到 name 和 color 属性,输出了 ‘Kitty’。

优缺点
  1. 优点:
  • 可以继承父类的属性。
  • 不会共享父类引用类型属性。
  1. 缺点:
  • 无法继承父类的方法。
  • 子类实例无法访问父类原型上的属性和方法。

组合继承

  结合原型链继承和构造函数继承的优点,既可以继承父类的属性和方法,又可以拥有自己的属性和方法,且不会共享父类的引用类型属性。

  • 代码举例:
function Animal(name) {
  this.name = name;
}

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

function Cat(name, color) {
  Animal.call(this, name);
  this.color = color;
}

Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;

let cat1 = new Cat('Kitty', 'white');
console.log(cat1.name); // 输出:'Kitty'
cat1.sayName(); // 输出:'Kitty'

  在组合继承中,通过在子类构造函数中调用父类构造函数,实现了子类对父类属性的继承,并将子类的原型对象设置为一个父类的实例,实现了子类对父类方法的继承。
  在上述代码中,我们创建了一个 Animal 构造函数,其中包含一个 name 属性和一个 sayName 方法,然后,我们创建了一个 Cat 构造函数,通过调用父类构造函数,将父类的 name 属性传递给子类,并添加了一个 color 属性。接着,我们将 Cat 的原型对象设置为一个 Animal 的实例,并将其 constructor 属性设置为 Cat。这样,Cat 的实例cat1就可以访问到 name 和 color 属性,也可以访问到父类原型对象上的 sayName 方法,输出了 ‘Kitty’。

优缺点
  1. 优点:
  • 可以继承父类的属性和方法。
  • 不会共享父类引用类型属性。
  • 可以向父类传递参数。
  1. 缺点:
  • 会调用两次父类构造函数,一次在子类构造函数中,一次在子类原型上,造成性能浪费。

原型式继承

  利用已有的对象创建一个新的对象,新对象的原型指向已有对象,新对象可以拥有已有对象的属性和方法。与原型链继承类似,会共享引用类型属性。

  • 代码举例:
let animal = {
  species: 'animal',
};

let cat1 = Object.create(animal, {
  name: {
    value: 'Kitty',
  },
  color: {
    value: 'white',
  },
});

console.log(cat1.species); // 输出:'animal'
console.log(cat1.name); // 输出:'Kitty'
console.log(cat1.color); // 输出:'white'

  在原型式继承中,通过 Object.create 方法创建一个新对象,并将其原型对象设置为一个已有的对象,实现了继承。
  在上述代码中,我们创建了一个 animal 对象,其中包含一个 species 属性。然后,我们通过 Object.create 方法创建了一个 cat1 对象,并将其原型对象设置为 animal 对象。接着,我们使用 Object.defineProperties 方法向 cat1 对象添加了 name 和 color 属性。这样,cat1 对象就可以访问到 species、name 和 color 属性,输出了 ‘animal’、‘Kitty’ 和 ‘white’。

优缺点
  1. 优点:
  • 可以快速创建一个对象,该对象可以继承已有对象的属性和方法。
  1. 缺点:
  • 与原型链继承一样,子类实例共享父类引用类型属性,容易造成修改污染。

寄生式继承

  在原型式继承的基础上,通过在新对象上添加方法来实现继承。可以在不影响原有对象的基础上对其进行扩展。但是,同样会共享引用类型属性。

  • 代码举例:
let animal = {
  species: 'animal'
};

function createCat(obj) {
  let cat = Object.create(obj);
  cat.catchMouse = function() {
    console.log(`${this.name} is catching a mouse`);
  };
  return cat;
}

let cat1 = createCat(animal);
cat1.name = 'Kitty';
cat1.catchMouse(); // 输出:'Kitty is catching a mouse'
优缺点
  1. 优点:
  • 可以在已有对象的基础上进行扩展,同时又不希望影响到原有对象。
  1. 缺点:
  • 与原型链继承和原型式继承一样,子类实例共享父类引用类型属性,容易造成修改污染。

寄生组合式继承

  在组合继承的基础上,优化了原型链继承和构造函数继承的缺点,通过借用构造函数继承属性,同时通过原型链继承方法。可以继承父类的属性和方法,同时不会共享父类的引用类型属性。

  • 代码举例:
function Animal(name) {
  this.name = name;
  this.colors = ['white', 'black'];
}

Animal.prototype.sayHello = function() {
  console.log(`Hello, I'm ${this.name}`);
}

function Cat(name, color) {
  Animal.call(this, name);
  this.color = color;
}

Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;

let cat1 = new Cat('Kitty', 'white');
let cat2 = new Cat('Tom', 'black');

cat1.colors.push('brown');
console.log(cat1.colors); // 输出:['white', 'black', 'brown']
console.log(cat2.colors); // 输出:['white', 'black']

  在寄生组合式继承中,通过 Object.create 方法创建一个新对象,并将其原型对象设置为父类的原型对象,实现了子类对父类方法的继承。
  在上述代码中,我们创建了一个 Animal 构造函数,其中包含一个 name 属性和一个 sayName 方法,然后,我们创建了一个 Cat 构造函数,通过调用父类构造函数,将父类的 name 属性传递给子类,并添加了一个 color 属性。接着,我们将 Cat 的原型对象设置为一个 Animal.prototype 对象的副本,并将其 constructor 属性设置为 Cat。这样,Cat 的实例cat1就可以访问到 name 和 color 属性,也可以访问到父类原型对象上的 sayName 方法,输出了 ‘Kitty’。

优缺点
  1. 优点:
  • 可以继承父类的属性和方法,也不会共享父类引用类型属性。
  • 不会调用两次父类构造函数,性能优于组合继承。
  1. 缺点:
  • 实现复杂,需要手动设置子类原型的 constructor 属性为子类构造函数。

Class继承

   在 ES6 中,我们可以使用 class 关键字来定义一个类,使用 extends 关键字来实现继承。ES6 中的继承方式称为类继承,具有以下特点:

  1. 使用 class 关键字定义一个类:
class Animal {
  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(this.name);
  }
}
  1. 使用 extends 关键字实现继承:
class Cat extends Animal {
  constructor(name, color) {
    super(name);
    this.color = color;
  }
}

  在上述代码中,我们创建了一个 Animal 类,并定义了一个构造函数和一个方法。然后,我们创建了一个 Cat 类,并通过 extends 关键字将其继承自 Animal 类。在 Cat 的构造函数中,我们通过 super 关键字调用了父类的构造函数,并将父类的 name 属性传递给子类。接着,我们添加了一个 color 属性。这样,Cat 类就继承了 Animal 类的属性和方法,同时也添加了自己的属性。

  1. 使用 super 关键字调用父类的方法:
class Cat extends Animal {
  constructor(name, color) {
    super(name);
    this.color = color;
  }

  sayName() {
    super.sayName();
    console.log(`My color is ${this.color}`);
  }
}

  在上述代码中,我们重写了 Cat 类中的 sayName 方法,并使用 super 关键字调用了父类的 sayName 方法。这样,在调用 Cat 类的 sayName 方法时,我们既可以输出名字,也可以输出颜色。

最后

  以上就是js继承的一些方法,每种继承方式都有其优缺点和适用场景,我们需要根据具体的情况选择合适的继承方式。在实际开发中,我们通常会使用 ES6 中的类继承方式,它更加直观、易于理解和维护。通过理解和应用不同的继承方式,我们可以更好地实现面向对象编程,并编写出更加高效、简洁和可维护的代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小新-alive

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值