JavaScript 面向对象编程:深入理解原型继承与实现方式

作者:木棉时间:2026-10-23 22:31:09浏览:128

本文探讨 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 继承方式?引号中的描述极具吸引力,值得继续深入学习。

作者最新文章