深入理解 JavaScript 继承:从原型链到 call/apply 的灵活运用

在 JavaScript 这门语言中,继承是一个绕不开的话题。不同于 Java、C++ 等传统面向对象语言的"类继承",JavaScript 采用的是基于**原型(Prototype)**的继承机制。这种机制既灵活又强大,但也常常让初学者感到困惑。

本文将带你从基础出发,深入剖析 JavaScript 中的继承方式,并重点讲解 call 和 apply 在构造函数继承中的妙用。文章结合原理、代码示例与思考,助你真正掌握 JS 继承的本质。


一、原型继承:JS 的根基

JavaScript 中每个函数都有一个 prototype 属性,每个对象都有一个内部属性 [[Prototype]](可通过 __proto__ 访问),指向其构造函数的 prototype 对象。

javascript 复制代码
function Animal(name) {
  this.name = name;
}
Animal.prototype.say = function() {
  console.log(`${this.name} is an animal.`);
};

const dog = new Animal('Dog');
dog.say(); // Dog is an animal.

这就是最经典的原型链继承 。子类实例通过 __proto__ 指向父类的 prototype,从而实现方法共享。

但原型继承有一个明显问题:无法在不创建父类实例的情况下,向父类构造函数传递参数 。这引出了我们接下来要讲的------构造函数继承。


二、构造函数继承:借助 call / apply

构造函数继承的核心思想是:在子类构造函数中,调用父类构造函数,并将 this 指向子类实例。

这就需要用到 Function.prototype.call 或 apply:

javascript 复制代码
function Animal(name) {
  this.name = name;
}

function Dog(name, breed) {
  // 关键:使用 call 将 Animal 的 this 指向当前 Dog 实例
  Animal.call(this, name);
  this.breed = breed;
}

const myDog = new Dog('Buddy', 'Golden Retriever');
console.log(myDog.name);   // Buddy
console.log(myDog.breed);  // Golden Retriever

✨ call vs apply:区别在哪?

  • call(thisArg, arg1, arg2, ...):逐个传参
  • apply(thisArg, [arg1, arg2, ...]):以数组形式传参

两者都能指定函数执行时的 this 指向,且立即执行函数 。在继承场景中,call 更常用,因为参数通常是已知的。

📌 关键点 :call/apply 并不会建立原型链!它们只是"借用"了父类构造函数来初始化子类实例的属性。


三、组合继承:原型 + 构造函数

既然原型继承能共享方法,构造函数继承能传参,那能不能两者结合?

当然可以!这就是经典的 组合继承(Combination Inheritance) :

javascript 复制代码
function Animal(name) {
  this.name = name;
}
Animal.prototype.say = function() {
  console.log(`${this.name} says hello!`);
};

function Dog(name, breed) {
  Animal.call(this, name); // 构造函数继承:传参 + 初始化属性
  this.breed = breed;
}
Dog.prototype = new Animal(); // 原型继承:共享方法
Dog.prototype.constructor = Dog; // 修正 constructor 指向

const dog = new Dog('Max', 'Husky');
dog.say(); // Max says hello!

这种方式几乎完美,但有一个小瑕疵:父类构造函数被调用了两次 (一次在 new Animal() 设置原型,一次在 Dog 内部)。


四、更优雅的方式:寄生组合继承

为了解决重复调用问题,我们可以使用 寄生组合继承(Parasitic Combination Inheritance) ------ 也是现代框架(如 React 早期)推荐的方式。

核心思想:不通过 new Parent() 设置子类原型,而是用一个空函数作为中介。

这正是你上传的 2.html 中提到的:"利用空对象作为中介"。

javascript 复制代码
function inheritPrototype(Child, Parent) {
  const F = function() {};      // 空构造函数
  F.prototype = Parent.prototype;
  Child.prototype = new F();    // 避免调用 Parent()
  Child.prototype.constructor = Child;
}

function Animal(name) {
  this.name = name;
}
Animal.prototype.say = function() {
  console.log(`${this.name} speaks.`);
};

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

inheritPrototype(Cat, Animal);

const kitty = new Cat('Luna', 'white');
kitty.say(); // Luna speaks.

这种方式只调用一次父类构造函数,效率更高,是目前最推荐的继承模式。


五、ES6 Class:语法糖下的本质

ES6 引入了 class 语法,让继承看起来更"传统":

scala 复制代码
class Animal {
  constructor(name) {
    this.name = name;
  }
  say() {
    console.log(`${this.name} talks.`);
  }
}

class Bird extends Animal {
  constructor(name, wingspan) {
    super(name); // 等价于 Animal.call(this, name)
    this.wingspan = wingspan;
  }
}

但请注意:class 本质上仍是基于原型的语法糖 。super() 底层依然是通过 call 调用父类构造函数。

理解底层机制,才能在遇到边界情况(如 this 绑定、混入 Mixin、动态继承)时游刃有余。


六、思考:为什么 JS 的继承如此特别?

JavaScript 的继承不是"复制",而是"链接"。它强调**行为委托(delegation)**而非"拥有"。

  • 原型链:对象 → 原型 → 原型的原型......直到 null
  • call/apply:临时改变上下文,实现"借用"
  • 组合继承:兼顾属性初始化与方法复用

这种设计赋予 JS 极大的灵活性,也带来了学习曲线。但一旦掌握,你就能写出更高效、更可维护的代码。


结语

继承不是目的,复用与扩展才是 。无论是古老的原型链,还是现代的 class,理解其背后的运行机制,才能真正驾驭 JavaScript。

💡 建议 :不要死记语法,多动手画原型链图,多调试 this 指向。真正的高手,看得见"看不见的链接"。


欢迎点赞、收藏、评论交流!

如果你觉得这篇文章对你有帮助,不妨分享给正在学习 JS 的朋友。前端路上,我们一起成长 🌱


相关推荐
Seraphina368 小时前
DVWA(SQL注入-low,medium,XSS反射-low)
前端·数据库·笔记·sql·网络安全·web·xss
Access开发易登软件8 小时前
Access 导入 Markdown 表格:把文档里的数据写进表
前端·html·vba·markdown·access开发
朝朝辞暮i9 小时前
C++ 第 25 课:getter / setter + this
java·javascript·c++
扶风ff9 小时前
求职笔试资料太散?用练题簿在线刷题,按岗位整理练习清单
前端·学习·小程序
蜗牛互联网9 小时前
Python接入Gemini 3.8 Flash实现票据视觉抽取与规则校验
java·javascript·网络·人工智能·python
浪浪山_大橙子9 小时前
公司里的 AI,终于不只会聊天:我用 GPT‑6 把企业工作伙伴开源了
前端·后端·面试
琹箐9 小时前
命令行调用接口
前端
无限码力9 小时前
华为OD面试手撕真题 【合并相邻且相等的元素】多语言题解
华为od·面试·华为od面试真题·华为od面试手撕真题·华为od面试算法真题
可乐鸡翅yeah_9 小时前
AES‑128 加密 M3U8,IV 初始化向量新手容易踩坑
前端·网络·数据库·ffmpeg·音视频·m3u8在线
MetWeave10 小时前
机场天气的"广播系统":三种报文一次讲明白
前端