5种JS原型继承方式总结,你了解几种?

前言:

js有几种经典的继承方式。比如原型链继承、构造函数继承、组合继承、寄生组合继承、ES6继承。让我们一一分析并实现。同时了解每种方案的优缺点。

其实js的继承本质上是通过原型链机制实现的扩展。不管是哪种继承方式,都是通过操作父类原型链和子类原型链形成关联关系实现的。只是不同实现中需要考虑不同的问题。在实际项目开发中,建议尽可能使用ES6的class extends实现。


在这里插入图片描述

正文开始如果觉得文章对您有帮助,请帮我三连+订阅,谢谢💖💖💖


一、原型链继承

优点: 父类新增原型方法或属性,子类都能访问到,可共用方法
缺点:

  • 创建子类实例时,无法向父类构造函数传参
  • 引用类型(比如对象/数组)的属性会被所有实例共享 ( 实例使用的是同一个原型对象。它们的内存空间是共享的)

   function Parent() {
     this.name = "Parent";
     this.arr=[]
   }
   Parent.prototype.sayHello = function () {
     this.arr.push(1)
     console.log("Hello from " + this.name);
   };
   
   function Child() {
     this.name = "Child";
   }
   
   // 设置Child的原型为Parent的实例
   Child.prototype = new Parent();
   
   // 创建Child的实例
   let child = new Child();
   let child2 = new Child();
   child.sayHello(); // 输出: Hello from Child
   child.sayHello(); // 输出: Hello from Child
   console.log(child.sayHello  === child2.sayHello); // true
   console.log(child.arr,child2.arr); // [ 1, 1 ] [ 1, 1 ]  , child的 arr 修改 导致 child2的arr也被修改了

在这里插入图片描述

二、构造函数继承

优点: 修复引用类型被篡改解决原型链继承无法传参的问题(通过call传参)。
缺点: 每次创建子类实例时都会创建父类的方法,导致内存浪费无法实现函数复用


function Parent(name) {
  this.name = name;
  this.arr=[]
}
Parent.prototype.sayHello = function () {
  this.arr.push(1)
  console.log("Hello from " + this.name);
};

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

Child.prototype = new Parent(); // 继承Parent的原型方法
Child.prototype.constructor = Child; // 修正构造函数指向

var child = new Child('ziyu');
var child1 = new Child('ziyu');
console.log(child.sayHello()); 
console.log(child.sayHello()); 
console.log(child.sayHello === child1.sayHello);  //false
console.log(child.arr, child1.arr); // 输出: [ 1, 1 ] []

在这里插入图片描述

三、组合继承

优点: 可以传参,并且可以复用父类方法,可以避免引用类型属性共享的问题。
缺点: 父类构造函数被调用了两次,一次在设置原型时,一次在构造函数中,可能会导致不必要性能开销

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

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

// 通过原型链继承Parent的方法
Child.prototype = new Parent()   // 第二次调用
Child.prototype.constructor = Child;

Child.prototype.sayBye = function() {
  console.log('Bye from ' + this.name);
};

var child = new Child('ziyu_jia', 25);
child.sayHello(); // 输出: Hello from ziyu_jia
child.sayBye(); // 输出: Bye from ziyu_jia

在这里插入图片描述

四、寄生组合继承

优点:寄生组合继承其实就是在组合继承的基础上,解决了父类构造函数调用两次的问题
缺点: 无明显缺点


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

function Child(name, age) {
  // 创建一个没有实例的Parent对象
  Parent.call(this, name);
  this.age = age;
}

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

Child.prototype.sayBye = function() {
  console.log('Bye from ' + this.name);
};

var child = new Child('ziyu_jia', 25);
child.sayHello(); // 输出: Hello from ziyu_jia
child.sayBye(); // 输出: Bye from ziyu_jia

在这里插入图片描述

Object.create()

Object.create() 静态方法以一个现有对象作为原型,创建一个新对象。

const person = {
  isHuman: false,
  printIntroduction: function () {
    console.log(`My name is ${this.name}.`); 
  },
};

const me = Object.create(person);
me.name='ziyu'
console.log( me.printIntroduction());  // My name is ziyu

在这里插入图片描述

五、ES6继承

ES6提供了class语法糖,同时提供了extends用于实现类的继承。这也是项目开发中推荐使用的方式。
优点: 使用class继承很简单,也很直观

class Parent {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    console.log('Hello from ' + this.name);
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name); // 调用父类的构造函数
    this.age = age;
  }
  sayBye() {
    console.log('Bye from ' + this.name);
  }
}

var child = new Child('ziyu', 25);
child.sayHello(); // 输出: Hello from ziyu
child.sayBye(); // 输出: Bye from ziyu

在这里插入图片描述

参考文档

### JavaScript继承几种方式及其适用场景 #### 1. 原型继承 原型继承通过将父类的实例设置为子类的原型,使子类能够访问父类的属性和方法。这种方式适用于简单的继承场景,但需要注意的是,所有子类实例共享父类实例中的引用类型属性,可能会导致数据污染。 ```javascript function Parent() { this.name = 'parent'; } Parent.prototype.sayHello = function() { console.log('Hello'); }; function Child() {} Child.prototype = new Parent(); // 使用父类实例作为子类原型 var child = new Child(); console.log(child.name); // 'parent' child.sayHello(); // 'Hello' ``` [^2] #### 2. 构造函数继承 构造函数继承通过在子类构造函数中调用父类构造函数实现继承。这种方式解决了引用类型共享的问题,但无法继承父类原型上的方法。 ```javascript function Parent(name) { this.name = name; } Parent.prototype.sayHello = function() { console.log('Hello'); }; function Child(name, age) { Parent.call(this, name); // 调用父类构造函数 this.age = age; } var child = new Child('child', 10); console.log(child.name); // 'child' // child.sayHello(); // 报错,无法继承父类原型上的方法 ``` [^3] #### 3. 组合继承 组合继承结合了原型继承和构造函数继承的优点,既可以通过构造函数继承父类实例属性,又可以通过原型继承父类原型上的方法。这是最常用的继承方式。 ```javascript function Parent(name) { this.name = name; } Parent.prototype.sayHello = function() { console.log('Hello'); }; function Child(name, age) { Parent.call(this, name); // 第一次调用Parent() this.age = age; } Child.prototype = new Parent(); // 第二次调用Parent() Child.prototype.constructor = Child; var child = new Child('child', 10); console.log(child.name); // 'child' child.sayHello(); // 'Hello' ``` [^1] #### 4. 寄生组合继承 寄生组合继承改进了组合继承中多次调用父类构造函数的问题,是目前最理想的继承方式。它通过借用父类原型创建子类原型,避免了不必要的属性复制。 ```javascript function inheritPrototype(Child, Parent) { var prototype = Object.create(Parent.prototype); // 创建父类原型副本 prototype.constructor = Child; // 恢复子类构造函数 Child.prototype = prototype; } function Parent(name) { this.name = name; } Parent.prototype.sayHello = function() { console.log('Hello'); }; function Child(name, age) { Parent.call(this, name); // 调用父类构造函数 this.age = age; } inheritPrototype(Child, Parent); // 使用寄生方式设置子类原型 var child = new Child('child', 10); console.log(child.name); // 'child' child.sayHello(); // 'Hello' ``` [^1] #### 5. ES6 Class继承 ES6引入了`class`语法糖,简化了继承的实现方式。`class`本质上仍然是基于原型链的继承,但提供了更简洁、直观的语法。 ```javascript class Parent { constructor(name) { this.name = name; } sayHello() { console.log('Hello'); } } class Child extends Parent { constructor(name, age) { super(name); // 调用父类构造函数 this.age = age; } } let child = new Child('child', 10); console.log(child.name); // 'child' child.sayHello(); // 'Hello' ``` #### 6. 对象冒充(Object.create) 对象冒充是一种基于`Object.create`的继承方式,可以直接指定新对象的原型,适用于简单的对象继承场景。 ```javascript function Parent(name) { this.name = name; } Parent.prototype.sayHello = function() { console.log('Hello'); }; function Child(name, age) { Parent.call(this, name); // 调用父类构造函数 this.age = age; } Child.prototype = Object.create(Parent.prototype); // 设置子类原型 Child.prototype.constructor = Child; // 恢复构造函数 var child = new Child('child', 10); console.log(child.name); // 'child' child.sayHello(); // 'Hello' ``` [^3] ### 总结 不同的继承方式适用于不同的场景。如果需要简单地继承父类的原型方法,可以选择原型继承;如果需要解决引用类型共享问题,可以使用构造函数继承;如果需要同时继承父类实例属性和原型方法,推荐使用组合继承或寄生组合继承;对于现代JavaScript开发,建议优先使用ES6 `class`语法糖实现继承
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

子羽bro

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

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

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

打赏作者

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

抵扣说明:

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

余额充值