JavaScript 面向对象编程:深入理解原型继承与实现方式
本文探讨 JavaScript 中基于原型的继承机制。首先分析直接赋值 prototype 导致的引用共享问题,指出其无法支持方法覆盖的缺陷。随后介绍利用反射机制遍历属性实现独立继承的方法,并封装为静态方法,旨在实现更清晰的面向对象编程模式。
直接赋值原型实现的继承及其缺陷
继承的概念看似直观,即“将一个类的 prototype 拷贝到另一个类”。代码简洁,直接来看实现方式:
function class1() { }
function class2() { }
class2.prototype = class1.prototype;
class2.moreProperty1 = " class 2 additional string ";
class2.moreMethod1 = function () { alert( " class 2 additional method "); }
/*
此时,class2 拥有了与 class1 相同的 prototype。若忽略构造函数差异,这两个类在原型层面是等价的。
接着,通过修改 prototype 为 class2 添加了额外的属性和方法。因此,class2 是在 class1 的基础上
增加了属性和方法,从而实现了类的继承。
*/
function test() {
var obj = new class2();
// JavaScript 提供了 instanceof 操作符来判断一个对象是否是某个类的实例
alert(obj instanceof class2); // true
alert(obj instanceof class1); // ?
}
运行上述代码,结果或许符合预期。表面上看,这种实现完全可行,JavaScript 也能正确识别这种继承关系,obj 同时是 class1 和 class2 的实例。但实质上并非如此(学习的目的是知其然更知其所以然)。JavaScript 的这种理解基于一种简单策略,见下文代码:先使用 prototype 让 class2 继承 class1,再在 class2 中重复定义 method 方法:
// 定义class1
function class1() {
// 构造函数
}
// 定义class1 的成员
class1.prototype = {
m1: function () { // 方法1
alert( " class1 method1 ");
}
}
// 定义class2
function class2() {
// 构造函数
}
// 让class2 继承于class1
class2.prototype = class1.prototype;
// 给class2 重复定义方法method
class2.prototype.method = function () {
alert( " whose method2? class1 or class2 ");
}
// 创建两个类的实例
var obj1 = new class1();
var obj2 = new class2();
function test() {
// 分别调用两个对象的method 方法
obj1.method();
obj2.method();
}
从执行结果看,method 方法在 class1 和 class2 中的运行结果相同。
由此可见,当修改 class2 的 prototype 时,class1 的 prototype 也会随之改变。即使对 class2 的 prototype 增减成员,class1 的成员定义也会同步变化。因此,class1 和 class2 仅仅是构造函数不同的两个类,它们共享相同的成员定义。读者可能已发现其中的关键:class1 和 class2 的 prototype 完全相同,指向同一个对象。从这条赋值语句即可看出:
//让class2 继承于class1
class2.prototype=class1.prototype;
在 JavaScript 中,除基本数据类型(数字、字符串、布尔值等)外,所有赋值和函数参数均为引用传递,而非值传递。上述语句仅使 class2 的 prototype 对象引用 class1 的 prototype,导致类成员定义始终保持一致。这也解释了 instanceof 操作符的执行机制:它判断对象是否为某个 prototype 的实例。由于 obj1 和 obj2 对应同一个 prototype,它们的 instanceof 结果相同。因此,使用 prototype 引用拷贝实现继承并非正确做法。但在要求不严格的情况下,这是一种合理方法,唯一的约束是禁止类成员的覆盖定义(这也体现了 JavaScript 的灵活性)。实际上,我们可以利用反射机制和 prototype 来实现正确的类继承:
利用反射机制实现正确的类继承
function class1() {
// 构造函数
}
class1.prototype = {
method: function () {
alert( " method1 ");
},
method2: function () {
alert( " method2 ");
}
}
function class2() {
// 构造函数
}
// 让class2 继承于class1
for ( var p in class1.prototype) {
class2.prototype[p] = class1.prototype[p]; // 利用反射机制和prototype实现继承
}
// 覆盖定义class1中的method 方法
class2.prototype.method = function () {
alert( " class2 new method1 ");
}
// 创建两个类的实例
var obj1 = new class1();
var obj2 = new class2();
function test() {
// 分别调用两个对象的method 方法
obj1.method();
obj2.method();
// 分别调用两个对象的method2 方法
obj1.method2();
obj2.method2();
}
运行结果显示,obj2 中重复定义的 method 已覆盖继承的 method 方法,同时 method2 方法未受影响。此外,obj1 中的 method 方法仍保持原有定义。这样,便实现了正确意义上的类继承。为方便开发,可为每个类添加一个共有方法来实现继承:
封装继承方法与代码局限性
// 为类添加静态方法inherit表示继承于某类
Function.prototype.inherit = function (baseClass) {
for ( var p in baseClass.prototype) {
this .prototype[p] = baseClass.prototype[p];
}
}
function class1() {
// 构造函数
}
class1.prototype = {
method: function () {
alert( " method1 ");
},
method2: function () {
alert( " method2 ");
}
}
function class2() {
// 构造函数
}
// 让class2 继承于class1
// for (var p in class1.prototype) {
// class2.prototype[p] = class1.prototype[p]; // 利用反射机制和prototype实现继承
// }
class2.inherit(class1); // 等价于上面注释掉的那一个for循环
// 覆盖定义class1中的method 方法
class2.prototype.method = function () {
alert( " class2 new method1 ");
}
// 创建两个类的实例
var obj1 = new class1();
var obj2 = new class2();
function test() {
// 分别调用两个对象的method 方法
obj1.method();
obj2.method();
// 分别调用两个对象的method2 方法
obj1.method2();
obj2.method2();
}
上述代码使逻辑更加清晰易懂。通过这种方法实现继承有一个缺点:在 class2 中添加类成员定义时,不能直接对 prototype 赋值,而只能对其属性进行赋值。例如,不能采用:
class2.prototype={
//成员定义
}
而只能采用:
class2.prototype.propertyName=someValue;
class2.prototype.methodName=function(){
//语句
}
由此可见,这种继承方式仍以牺牲部分代码可读性为代价。是否存在“不仅基类可用对象直接赋值给 property,派生类中也可同样实现,使代码逻辑更清晰,更能体现面向对象语言特点”的 JavaScript 继承方式?引号中的描述极具吸引力,值得继续深入学习。
JS合并多个数组的三种方法:concat、spread与flat
2026-11-05 17:39:45
JavaScript怎么准确判断数据类型:typeof、instanceof与Object.prototype.toString
2026-11-05 17:09:21
JS中将数组和对象混合组装为JSON字符串
2026-11-05 16:41:42
JS运算符优先级与结合性的区别是什么?
2026-11-05 13:22:02
JS Map怎么只遍历Key或Value
2026-11-05 13:00:36
