JavaScript中几种主流继承模式的原理与对比

作者:闭包旅人

时间:2026-10-29

浏览:430

本文深入解析JavaScript中四种主流继承方式:原型链、构造函数、组合及寄生组合继承。通过对比分析各模式的优缺点,重点阐述寄生组合继承为何成为最经典且推荐的实现方案,帮助开发者理解原型机制并选择最佳实践。

文章目录

引言:JavaScript的面向对象特性

引言 从严格的技术定义来看,JavaScript并非一门纯粹的面向对象语言。这种观点通常源于其弱类型特性与Java或C#等强类型语言在继承机制上的显著差异,导致许多人默认其为非主流面向对象语言,甚至有书籍将其定义为“非完全面向对象”。然而,继承方式的形式并非核心,关键在于是否具备面向对象的思想。许多认为JavaScript不具备面向对象特性的观点,往往是因为未深入探究其继承机制。本文旨在通过对比分析,澄清这一误解。

JavaScript实现继承的背景与必要性

JavaScript实现继承的必要性 早期PC性能有限,服务器承担所有压力,客户端浏览器仅具展示功能。加之Table布局与电话线上网导致的加载缓慢,网页体验较差。如今,互联网技术飞速发展,个人电脑硬件大幅提升,浏览器性能显著优化,Web开发模式随之转变:服务端负载减轻,浏览器承担更多任务。这种压力分摊不仅降低了企业成本,也提升了前端开发的趣味性,催生了大量前端框架乃至MVC框架。在此背景下,JavaScript的角色已超越简单的表单验证、请求发送或DOM操作,更多地承担前端路由和业务逻辑层职责。由于涉及大量逻辑处理及前台数据抽离(即Model层),必须运用面向对象思维才能有效处理数据,因此继承机制在此显得至关重要。

继承场景:从Person到Man的模型构建

基于简单需求的继承场景 假设从前台抽离出一个名为Person的Model,其包含基本属性name和age。由于每个人都会说话,我们将say方法定义在原型对象上,供所有实例共享。现在,我们需要构建一个Man类,它需要继承Person的基本属性,并在此基础上扩展自身特有属性。


function Person (name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.say = function(){
  console.log('hello, my name is ' + this.name);
};
function Man() {
  //my own properties
}

下面介绍几种主流的继承方式。

原型链继承:原理与局限

1. 原型链继承


function Person (name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.say = function(){
  console.log('hello, my name is ' + this.name);
};
function Man() {
}
Man.prototype = new Person('pursue');
var man1 = new Man();
man1.say(); //hello, my name is pursue
var man2 = new Man();
console.log(man1.say === man2.say);//true
console.log(man1.name === man2.name);//true

这种继承方式直观直接:为了获取Person的所有属性和方法(包括实例属性和原型属性),直接将父类的实例new Person('pursue')赋值给子类的原型。此时,子类的实例man1和man2本身是空对象,所有属性和方法均需通过原型链查找,因此找到的属性和方法均为同一份引用。 鉴于此缺陷,直接利用原型链继承在实际开发中并不可行。

构造函数继承:属性复制与原型缺失

2. 利用构造函数继承


function Person (name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.say = function(){
  console.log('hello, my name is ' + this.name);
};
function Man(name, age) {
  Person.apply(this, arguments);
}
//Man.prototype = new Person('pursue');
var man1 = new Man('joe');
var man2 = new Man('david');
console.log(man1.name === man2.name);//false
man1.say(); //say is not a function

在此方式中,子类构造函数内部通过apply调用父类构造函数,从而继承父类的属性。相比原型链继承,这种方式更为优越,因为每个实例都拥有独立的资源副本。然而,该方法仅能继承父类的实例属性,无法访问原型上的say方法。为了同时继承父类的所有属性和方法,必须修改原型链,由此引出了组合继承方式。

组合继承:实例与原型的双重继承

3. 组合继承


function Person (name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.say = function(){
  console.log('hello, my name is ' + this.name);
};
function Man(name, age) {
  Person.apply(this, arguments);
}
Man.prototype = new Person();
var man1 = new Man('joe');
var man2 = new Man('david');
console.log(man1.name === man2.name);//false
console.log(man1.say === man2.say);//true
man1.say(); //hello, my name is joe

需要注意的是,man1和man2的实例属性实际上覆盖了原型属性,但并未覆盖原型上的say方法(因为该属性未被实例属性覆盖)。因此,man1.say === man2.say依然返回true,这意味着原型属性是所有实例共享的。开发者需格外小心未被覆盖的原型属性,避免意外共享带来的副作用。

寄生组合继承:最优实践解析

4. 寄生组合继承

尽管我不确定这种形式是否正式被称为“寄生组合继承”,但它确实是目前最流行、最经典的JavaScript继承方式。理解其核心在于明白原型对象的结构:


function Person (name, age) {
      this.name = name;
      this.age = age;
    }
Person.prototype.say = function(){
  console.log('hello, my name is ' + this.name);
};
function Man(name, age) {
  Person.apply(this, arguments);
}
Man.prototype = Object.create(Person.prototype);//a.
Man.prototype.constructor = Man;//b.
var man1 = new Man('pursue');
var man2 = new Man('joe');
console.log(man1.say == man2.say);
console.log(man1.name == man2.name);

寄生组合继承与组合继承的主要区别在于构造子类原型对象的方式(a.和b.)。这里使用了Object.create(obj)方法,该方法对传入的obj对象进行浅拷贝,其效果类似于:


function create(obj){
  function T(){};
  T.prototype = obj;
  return new T();
}

因此,a.步骤将子类的原型对象与父类的原型对象进行了良好连接,而非像普通组合继承那样直接对子类原型进行暴力复制(如Man.prototype = new Person();),后者会覆盖属性。寄生组合继承分别处理了实例属性和原型属性的继承,在实现逻辑上更为合理。

注意:代码b.不会改变instanceof的结果,但对于需要使用constructor的场景,这种处理方式更加严谨。

以上关于JavaScript中几种继承方式的对比分析即为全部内容,希望能为大家提供参考,也希望大家多多支持本站。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章