五种继承的方式

1. 原型链继承

核心:让子类的原型指向父类的实例。

js 复制代码
function Parent(name) {
  this.name = name;
  this.colors = ["red", "blue"];
}
Parent.prototype.sayHi = function() {
  console.log("Hi from Parent");
};

function Child() {}
Child.prototype = new Parent("Tom"); // 关键

const c1 = new Child();
c1.colors.push("green");

const c2 = new Child();
console.log(c2.colors); // ["red","blue","green"] ❌ 被共享了

console.log(c1.name); // "Tom"
console.log(c2.name); // "Tom"

问题出在哪?

关键是 调用 new Child() ,并没有再执行 Parent 构造函数。

  • Parent("Tom") 只在 设置原型链的时候被调用了一次。
  • 之后无论你创建多少个 Child 实例,都会复用这个"已经带着 name=Tom 的对象"。
  • 所以 Child 实例的 name 永远都是 "Tom",你没法在 new Child("Jerry") 的时候传递参数给 Parent

举例说明

vbscript 复制代码
const c1 = new Child("Jerry");
console.log(c1.name); // ❌ 还是 "Tom",根本没用 "Jerry"

为什么?

因为 Child 构造函数里面压根没调用 Parent,也就没法把 "Jerry" 传过去。

缺点

  • 父类实例属性(引用类型)会被所有子类实例共享。
  • 不能向父类构造函数传参。

2. 借用构造函数继承(经典继承) ----解决原型继承 实例属性共享问题

核心:在子类构造函数里调用父类构造函数。

js 复制代码
function Parent(name) {
  this.name = name;
  this.colors = ["red", "blue"];
}
Parent.prototype.sayName = function(){
    console.log(this.name)
}
function Child(name) {
  Parent.call(this, name); // 关键
}

const c1 = new Child("Tom");
c1.colors.push("green");

const c2 = new Child("Jerry");
console.log(c2.colors); // ["red","blue"] ✅ 独立
c2.sayName() // ❌ c2.sayName is not a function

优点

  • 解决了原型链继承共享引用属性的问题。
  • 可以给父类构造函数传参。

缺点

  • 方法都在构造函数里定义,没法复用,浪费内存。// colors在每个子类实例上都有一份
  • 不能继承父类原型上的方法

本质区别总结

  • 原型链继承 :共享的是 父类实例对象 上的属性 → 所有子类实例共用同一个引用,new Parent() 只执行了一次(在设置 Child.prototype 时),所以只分配了一份存储空间。
  • 构造函数继承 :每次实例化时重新执行父类构造函数 → 每个子类实例独立拥有自己的属性,Parent.call(this) 在每个子类实例里都会执行一次,所以每次 new Child() 都会分配一份新的存储空间。

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

核心:结合两者优点。

js 复制代码
function Parent(name) {
  this.name = name;
  this.colors = ["red", "blue"];
}
Parent.prototype.sayHi = function() {
  console.log("Hi, I am " + this.name);
};

function Child(name, age) {
  Parent.call(this, name); // 借用构造函数
  this.age = age;
}
Child.prototype = new Parent(); // 原型链
Child.prototype.constructor = Child;

const c1 = new Child("Tom", 18);
c1.sayHi(); // Hi, I am Tom

优点

  • 既能复用方法,又能独立拥有属性。

缺点

  • 父类构造函数会被调用两次(一次在 Parent.call,一次在 new Parent())。

4. 寄生组合继承(最推荐的 ES5 写法)

js 复制代码
function Animal(name) {
  this.name = name;
}
Animal.prototype.sayHi = function() {
  console.log(`I am ${this.name}`);
};

function Dog(name) {
  Animal.call(this, name);
}
// 用 Object.create 避免多次调用 Animal
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

const d = new Dog("Lucky");
d.sayHi(); // I am Lucky

🔹 特点:

  • 完美解决了组合继承的缺点。
  • ES5 最佳实践

5. ES6 class 继承(语法糖)

js 复制代码
class Animal {
  constructor(name) {
    this.name = name;
  }
  sayHi() {
    console.log("I am " + this.name);
  }
}

class Dog extends Animal {
  constructor(name, color) {
    super(name); // 调用父类构造函数,相当于 Animal.call(this, name)
    this.color = color;
  }
  bark() {
    console.log("Woof!");
  }
}

const d = new Dog("Lucky", "white");
d.sayHi(); // I am Lucky
d.bark();  // Woof!

super(name) 的作用:

  1. 调用父类构造函数 Animal(name)
  2. 把返回的结果绑定到当前子类实例的 this。 🔹 特点:
  • 语法更简洁直观。
  • 内部还是基于原型链。
相关推荐
Chengbei112 小时前
云安全漏洞挖掘SKILL、一站式云漏洞挖掘工具,支持S3爆破、IMDS探测、K8s检测与AK/SK权限利用
前端·人工智能·网络安全·云原生·容器·kubernetes·系统安全
不简说2 小时前
JS 代码技巧 vol.9 — 20 个设计模式在真实项目里的应用
前端·javascript·github
CoderLiu2 小时前
Agent 工程的下一层:为什么「把流程写成图」还不够,还需要 Graph Engineering
前端·人工智能·后端
勇往直前plus2 小时前
Vue3(篇三) Element Plus
前端·javascript·typescript·vue
爱勇宝2 小时前
《道德经》第 8 章:真正高级的能力,像水一样成事
前端·后端·程序员
Revolution612 小时前
数组本身没有 map,为什么还能直接调用:原型链怎样查找属性
前端·javascript·面试
bonechips2 小时前
RAG实战(一):EPUB 加载、文本切割与向量入库
前端·数据库
swipe2 小时前
10|(前端转全栈)库存扣减为什么最容易出事故?SKU、并发与原子更新
前端·后端·全栈
cdcdhj3 小时前
vue3中的watchEffect()监听,什么时候监听,什么时候清理,什么时候停止
前端·javascript·vue.js
huabuyu3 小时前
几百 MB 的文件为什么等几分钟才能打开?文件分片下载与渐进预览原理
前端·javascript