【js篇】JavaScript 原型修改 vs 重写:深入理解 constructor的指向问题

在 JavaScript 中,原型(prototype)的修改与重写 是两个看似相似但行为差异极大的操作。尤其是在重写原型对象时,容易引发 constructor 指向丢失的问题,导致逻辑错误或继承链混乱。

本文将通过代码实例,深入剖析 "原型修改"与"原型重写"的区别 ,并教你如何正确处理 constructor 的指向,避免常见陷阱。


一、原型的"修改":动态添加属性/方法

当我们通过 点语法或中括号语法 向 prototype 添加方法时,称为"原型修改"。此时,原型对象的引用地址不变,所有已创建和后续创建的实例都能正确访问新方法。

javascript 复制代码
function Person(name) {
  this.name = name;
}

// ✅ 修改原型:添加方法
Person.prototype.getName = function () {
  return this.name;
};

const p = new Person('Alice');
console.log(p.__proto__ === Person.prototype);           // true
console.log(p.__proto__ === p.constructor.prototype);    // true

✅ 特点:

  • 不改变 Person.prototype 的引用;
  • 所有实例共享更新;
  • constructor 指向依然正确:p.constructor === Person。

二、原型的"重写":用新对象替换原型

当我们 直接给 Person.prototype 赋值一个新对象 时,就发生了"原型重写"。这会创建一个全新的对象,导致原有引用关系断裂。

javascript 复制代码
// ❌ 重写原型:用对象字面量替换
Person.prototype = {
  getName: function () {
    return this.name;
  }
};

const p2 = new Person('Bob');
console.log(p2.__proto__ === Person.prototype);          // true
console.log(p2.__proto__ === p2.constructor.prototype);  // false ❌
console.log(p2.constructor === Person);                  // false
console.log(p2.constructor === Object);                  // true

🔍 问题分析:

  1. 原始的 Person.prototype 是一个自动创建的对象,其内部有:

    js 复制代码
    Person.prototype.constructor === Person
  2. 但当我们重写为:

    js 复制代码
    Person.prototype = {
      getName: function() {}
    }

    这个新对象的 constructor 默认指向 Object,因为它是通过对象字面量创建的,等价于:

    js 复制代码
    const obj = new Object();
    obj.getName = function() {};
  3. 因此,p2.constructor === Object,构造函数指向丢失!


三、修复方案:手动恢复 constructor 指向

为了保持原型链的完整性,我们需要在重写原型后,手动将 constructor 指回原构造函数。

javascript 复制代码
Person.prototype = {
  getName: function () {
    return this.name;
  }
};

// ✅ 修复 constructor 指向
Person.prototype.constructor = Person;

const p3 = new Person('Charlie');
console.log(p3.__proto__ === Person.prototype);           // true
console.log(p3.__proto__ === p3.constructor.prototype);   // true ✅
console.log(p3.constructor === Person);                   // true ✅

四、更优雅的写法:定义时一并设置 constructor

为了避免遗漏,推荐在重写原型时直接在对象字面量中定义 constructor:

javascript 复制代码
Person.prototype = {
  constructor: Person, // ✅ 显式指定
  getName: function () {
    return this.name;
  },
  sayHello: function () {
    console.log(`Hello, I'm ${this.name}`);
  }
};

这样从一开始就保证了 constructor 的正确性。


五、使用 Object.defineProperty 防止被枚举

如果你希望 constructor 不被 for...in 遍历到,可以使用 Object.defineProperty 定义它为不可枚举属性:

javascript 复制代码
Person.prototype = {
  getName: function () {
    return this.name;
  }
};

Object.defineProperty(Person.prototype, 'constructor', {
  value: Person,
  enumerable: false,     // 不可枚举
  writable: true,        // 可修改
  configurable: true     // 可配置
});

六、最佳实践建议

场景 推荐做法
小量扩展原型 使用 Person.prototype.method = function() {}
大量方法定义 重写原型对象,但必须包含 constructor
构建类库/框架 使用 Object.defineProperty 控制属性特性
避免 直接重写原型而不修复 constructor

七、总结:关键对比表

操作 是否改变 prototype 引用 constructor 是否丢失 是否推荐
修改原型(添加方法) ❌ 否 ❌ 否 ✅ 推荐
重写原型(无 constructor) ✅ 是 ✅ 是 ⚠️ 不推荐
重写原型(含 constructor) ✅ 是 ❌ 否 ✅ 推荐

💡 结语

"重写原型而不修复 constructor,就像换了身份证却不改名字。"

理解原型的修改与重写,尤其是 constructor 的指向问题,是掌握 JavaScript 面向对象编程的关键一步。无论你是手写类库,还是阅读框架源码(如 Vue、React 的某些底层实现),这些知识都至关重要。

📌 记住:

  • 修改原型 → 安全,无需额外操作;
  • 重写原型 → 必须手动设置 constructor!
相关推荐
Dream-Y.ocean5 小时前
鸿蒙PC平台宅匠 3D 室内设计适配实战:基于 Electron 壳方案的 Three.js 双渲染引擎与参数化户型编辑器开发
javascript·3d·harmonyos
Csvn6 小时前
性能监控
前端
Highcharts.js6 小时前
Highcharts Gantt使用符号的自定义数据标签开发甘特图示例
javascript·甘特图·highcharts·图表开发·实战代码·响应式图表
Csvn6 小时前
构建工具
前端
FRing037 小时前
vue2干货总结3(computed计算属性、watch监听器)
开发语言·前端·vue.js·vue2
kyriewen9 小时前
Skills 榜前 10 我全装了:只有 3 个能直接用
前端·javascript·程序员
IT_陈寒9 小时前
当Java的hashCode遇上Lombok,我的缓存被全清了
前端·人工智能·后端
工控丫丫10 小时前
帆软看板中经常使用的公式函数及经常搭的组合
前端·javascript·数据库
jerrywus10 小时前
我用 GPT-6.1-sol 做了台会讲解的 3D 售货机:60 罐饮料,一口没喝上
前端·agent·claude
孟陬11 小时前
Foobar 技术周刊#159:当看不懂代码时,如何高效排障调试。脏页陷阱:一条只读查询,写入数 TB 数据。推测解码:大模型加速利器
前端·人工智能·后端