JavaScript 原型链与继承机制深度解析
本文深入解析 JavaScript 的原型继承机制,区分可见的 prototype 与不可见的内部原型链。重点探讨构造函数重写后 constructor 指向偏移的问题,并提供三种修正方案。同时介绍 SpiderMonkey 引擎中的 __proto__ 属性及 ES5 的 Object.getPrototypeOf() 方法,帮助开发者准确掌握原型链访问与对象特性维护。
两个原型
一、两个原型
许多开发者了解 JavaScript 采用原型继承模式,每个构造函数都包含一个 prototype 属性,利用该属性可以实现丰富的继承效果。然而,仅依靠这一个可见属性并不能完全实现 JavaScript 的继承机制。文中不再赘述利用 prototype 进行常规继承的代码写法,读者可自行查阅相关资料。此外,还存在一个不可见的 prototype 成员。每个实例对象内部都有一条指向原型的属性(即内部原型),该属性无法被直接访问或修改,但它是维持 JavaScript 继承机制的基础。
代码如下:
// 构造器声明
function Guoyansi() { }
function GuoyansiEx() {}
// 原型继承
GuoyansiEx.prototype = new Guoyansi();
// 创建对象
var g1 = new GuoyansiEx();
var g2 = new GuoyansiEx();
上述代码中涉及的对象关系可通过图示说明。

原型的维护
二、原型的维护
由构造函数生成的实例,其 constructor 属性通常指向该构造函数。我们暂且接受这一说法。
代码如下:
function Guoyansi() { }
var obj1 = new Guoyansi();
console.log(obj1.constructor === Guoyansi); // true
实际上,构造函数本身并不直接拥有 constructor 属性,该属性来源于哪里?答案是:来自原型。因此可以得出以下结论。
代码如下:
obj1.constructor === Guoyansi.prototype.constructor === Guoyansi
既然可以通过 constructor 找到对应的构造函数,我们可以进一步完善前述的图示。

代码如下:
function GuoyansiEx() {}
GuoyansiEx.prototype = new Guoyansi();
console.log(GuoyansiEx.constructor === GuoyansiEx) // false
根据前述图示,上述结果似乎应为 true,但实际为何为 false?
现对此进行分析。由于 GuoyansiEx 的原型被 Guoyansi 的实例所覆盖,GuoyansiEx 原型中的 constructor 自然源自 Guoyansi 的实例。而 Guoyansi 实例中的 constructor 又源自 Guoyansi.prototype。因为 Guoyansi.prototype 未被重写,所以 Guoyansi.prototype 的 constructor 指向 Guoyansi(构造函数)。
根据以上分析,可得出以下结论。
代码如下:
GuoyansiEx.constructor === Guoyansi.constructor === Guoyansi;
若在开发中对 constructor 的指向有精确要求,可采用以下处理方式。
代码如下:
/** 方法一: */
function Guoyansi() {}
function GuoyansiEx() {}
GuoyansiEx.prototype = new Guoyansi();
GuoyansiEx.prototype.constructor = GuoyansiEx; // 重置 constructor 指向。
代码如下:
/**
方法二
**/
function Guoyansi() {}
function GuoyansiEx() {
this.constructor = arguments.callee;
}
GuoyansiEx.prototype = new Guoyansi();
代码如下:
/**
方法三
**/
function Guoyansi() {}
function GuoyansiEx() {
this.constructor = GuoyansiEx;
}
GuoyansiEx.prototype = new Guoyansi();
看不见的原型的作用
三、看不见的原型有什么用?
可见的原型链允许我们操作以实现继承,那么不可见的原型链既无法查看也无法操作,其作用何在?面向对象继承具有相似性特征,即子类与父类具有相似性。因此,在子类中无法删除从父类继承而来的成员,这意味着子类必须保留父类的特性。为了维护这一特性,JavaScript 在对象内部生成了一条不可见的原型属性,且不允许用户直接访问。这样,用户可以出于各种目的修改 constructor,而不会破坏子类拥有父类特性的事实。简而言之:内部原型是 JavaScript 原型继承机制所必需的,而外部原型是用户实现继承所需的。
SpiderMonkey 引擎中的 __proto__
四、火狐引擎 SpiderMonkey 中的 __proto__
还是这段代码。
代码如下:
function Guoyansi() {}
Guoyansi.prototype.age = 24;
function GuoyansiEx() {}
var obj1 = new Guoyansi();
GuoyansiEx.prototype = obj1;
GuoyansiEx.prototype.constructor = GuoyansiEx; // 重置 constructor 指向。
var obj2 = new GuoyansiEx();
现在希望从 obj2 开始向上访问父类 Guoyansi 的 prototype 属性中的 age。思路如下:第一步:obj2 ====> obj2.constructor.prototype;第二步:obj2.constructor.prototype ====> GuoyansiEx.prototype;第三步:GuoyansiEx.prototype ====> obj1;第四步:obj1.constructor ====> Guoyansi;第五步:Guoyansi.prototype.age。
写成代码如下:console.log(obj2.constructor.prototype.constructor.prototype.age) // 24。最终结果为 24,可以正常执行。但在许多书籍中提到,修改 constructor 后将无法找到父类中的原型,对此尚存疑问。
在火狐浏览器中提供了一种更简洁的属性 __proto__。SpiderMonkey 引擎默认在任何创建的对象上添加了一个名为 __proto__ 的属性,该属性指向构造函数所用的原型。这实际上就是前述不可见的原型链,只是在此处变相公开了。可以通过以下方式访问到 age:console.log(obj2.__proto__.__proto__.age); // 24。这样确实成功访问到了父类的原型属性,但该属性仅适用于火狐浏览器,在其他浏览器中会出错。在 ES5 中对 Object 进行了扩展,使用 Object.getPrototypeOf() 可以访问到所有父类的原型。
代码如下:
function Guoyansi() {}
Guoyansi.prototype.age = 24;
function GuoyansiEx() {}
var obj1 = new Guoyansi();
GuoyansiEx.prototype = obj1;
GuoyansiEx.prototype.constructor = GuoyansiEx; // 重置 constructor 指向。
var obj2 = new GuoyansiEx();
var proto = Object.getPrototypeOf(obj2);
while (proto) {
console.log(proto.constructor);
proto = Object.getPrototypeOf(proto);
}
console.log("object的原型" + proto);
结果是:GuoyansiEx\nGuoyansi\nObject\nobject的原型null
个人觉得这些内容算是 JavaScript 面向对象的精髓之一。读者可自行参考,并根据需求将其应用到项目中。
JavaScript闭包与类(Class)的区别及适用场景对比
2026-11-05 15:48:57
JavaScript入门要学ES6吗?let与箭头函数等新特性解析
2026-11-05 14:49:16
JavaScript 8种数据类型详解:基本类型与引用类型的区别
2026-11-05 14:25:15
怎么在JavaScript中使用async/await优化Ajax异步流程
2026-11-05 13:56:15
JavaScript replace回调函数实现动态正则替换逻辑
2026-11-05 13:52:13
