JavaScript 继承详解 第一篇:原型与模拟实现

作者:函数茶馆

时间:2026-10-23

浏览:559

本文深入解析 JavaScript 作为基于对象语言的独特性,阐述如何通过构造函数与 prototype 原型链模拟传统面向对象中的类与继承。文章详细拆解 new 关键字的执行流程,分析基于原型链继承的常见缺陷,并列举了 Douglas Crockford、John Resig 等知名开发者提供的经典继承实现方案,为后续深入探讨奠定基础。

文章目录

面向对象与基于对象 绝大多数开发人员都具备使用 C++、C# 或 Java 等传统面向对象语言的开发经验。在这些语言体系中,类(Class)与实例(Instance)是两个核心概念:类用于定义一类事物的公共行为和方法,而实例则是类的具体实现。此外,封装、继承和多态被视为面向对象编程的三大支柱。 然而,在 JavaScript 的语境下,上述特性似乎并不直接存在。JavaScript 并非严格意义上的面向对象语言,而是一种基于对象的语言。其显著特征在于万物皆对象,包括字符串、数组、日期、数字乃至函数。例如:


// 定义一个函数 - add 
function add(a, b) { 
add.invokeTimes++; 
return a + b; 
} 
// 因为函数本身也是对象,这里为函数add定义一个属性,用来记录此函数被调用的次数 
add.invokeTimes = 0; 
add(1 + 1); 
add(2 + 3); 
console.log(add.invokeTimes); // 2 

模拟JavaScript中类和继承 在传统面向对象语言中,开发者通过类来创建自定义对象。鉴于 JavaScript 中所有事物均为对象,我们需要寻找一种替代机制来构建自定义对象。为此,引入了原型(prototype)这一概念。可以将 prototype 视为一个模板,新创建的自定义对象均被视为该模板的一个拷贝(实际上,内部并非真正的拷贝,而是建立了一个不可见的链接,从而在视觉和逻辑上呈现出拷贝的效果)。 通过 prototype 创建自定义对象的示例如下:


// 构造函数 
function Person(name, sex) { 
this.name = name; 
this.sex = sex; 
} 
// 定义Person的原型,原型中的属性可以被自定义对象引用 
Person.prototype = { 
getName: function() { 
return this.name; 
}, 
getSex: function() { 
return this.sex; 
} 
} 

在此示例中,我们将函数 Person 称为构造函数,即用于创建自定义对象的函数。可以看出,JavaScript 通过结合构造函数与原型的方式,成功模拟实现了传统面向对象语言中类的功能。 创建自定义对象(即实例化类)的代码如下:


var zhang = new Person("ZhangSan", "man"); 
console.log(zhang.getName()); // "ZhangSan" 
var chun = new Person("ChunHua", "woman"); 
console.log(chun.getName()); // "ChunHua" 

当执行代码 var zhang = new Person("ZhangSan", "man") 时,JavaScript 引擎内部依次执行以下操作: 1. 创建一个空白对象(等效于 new Object())。 2. 将 Person.prototype 中的属性(键值对)复制到该空对象中(如前所述,内部实现实为隐藏链接而非物理拷贝)。 3. 将该对象通过 this 关键字传递给构造函数,并执行构造函数逻辑。 4. 将最终的对象赋值给变量 zhang。 为了验证 prototype 模板并非被物理拷贝到实例化对象中,而是通过链接方式工作,请参考以下代码:


function Person(name, sex) { 
this.name = name; 
this.sex = sex; 
} 
Person.prototype.age = 20; 
var zhang = new Person("ZhangSan", "man"); 
console.log(zhang.age); // 20 
// 覆盖prototype中的age属性 
zhang.age = 19; 
console.log(zhang.age); // 19 
delete zhang.age; 
// 在删除实例属性age后,此属性值又从prototype中获取 
console.log(zhang.age); // 20

这种在 JavaScript 内部实现的隐藏 prototype 链接,构成了 JavaScript 运行的基础环境,也是模拟实现继承机制的核心基石。 那么,如何在 JavaScript 中实现简单的继承? 以下示例展示了创建一个雇员类 Employee 的过程,该类从 Person 类继承了原型(prototype)中的所有属性:


function Employee(name, sex, employeeID) { 
this.name = name; 
this.sex = sex; 
this.employeeID = employeeID; 
} 
// 将Employee的原型指向Person的一个实例 
// 因为Person的实例可以调用Person原型中的方法, 所以Employee的实例也可以调用Person原型中的所有属性。 
Employee.prototype = new Person(); 
Employee.prototype.getEmployeeID = function() { 
return this.employeeID; 
}; 
var zhang = new Employee("ZhangSan", "man", "1234"); 
console.log(zhang.getName()); // "ZhangSan 

上述关于继承的实现方式较为粗糙,且存在若干问题: 1. 在定义 Employee 构造函数和原型(以下简称类)时,直接对 Person 进行了实例化,这种做法并不恰当。 2. Employee 的构造函数无法调用父类 Person 的构造函数,导致在 Employee 构造函数中对 name 和 sex 属性进行重复赋值。 3. Employee 中的方法会覆盖 Person 中的同名方法,缺乏重载机制(此问题与上一条相关)。 4. 创建 JavaScript 类的语法显得零散,不如 C#/Java 等语言的语法优雅。 5. 实现中存在 constructor 属性指向错误的问题,这将在第二篇文章中详细讨论。 我们将在后续章节完善此示例。

JavaScript继承的实现

由于 JavaScript 本身缺乏完整的类和继承机制,且前文所述的手工实现方式存在诸多弊端,因此针对这一具有挑战性的任务,社区中已涌现出多种实现方案:

  • Douglas Crockford - Prototypal Inheritance in JavaScript
  • Douglas Crockford - Classical Inheritance in JavaScript
  • John Resig - Simple JavaScript Inheritance
  • Dean Edwards - A Base Class for JavaScript Inheritance
  • Prototype
  • Mootools
  • Extjs

本系列文章将逐一深入分析上述各种实现方案,旨在最终全面理解 JavaScript 中类和继承的实现原理。 下一章将介绍类实现中的相关知识,包括 this、constructor、prototype 等核心概念。

实用工具推荐

电子包浆

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

使用 ›
图片压缩

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

使用 ›
相关文章