【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
相关推荐
LuckySusu1 小时前
【js篇】如何准确获取对象自身的属性?hasOwnProperty深度解析
前端·javascript
LuckySusu1 小时前
【js篇】深入理解 JavaScript 作用域与作用域链
前端·javascript
LuckySusu1 小时前
【js篇】call() 与 apply()深度对比
前端·javascript
LuckySusu1 小时前
【js篇】addEventListener()方法的参数和使用
前端·javascript
该用户已不存在1 小时前
6个值得收藏的.NET ORM 框架
前端·后端·.net
LuckySusu2 小时前
【js篇】深入理解 JavaScript 原型与原型链
前端·javascript
文心快码BaiduComate2 小时前
文心快码入选2025服贸会“数智影响力”先锋案例
前端·后端·程序员
云枫晖2 小时前
手写Promise-构造函数
前端·javascript
文心快码BaiduComate2 小时前
用Comate Zulu开发一款微信小程序
前端·后端·微信小程序