必须用object.getprototypeof()替代__proto__,但需精准识别上下文:仅替换读取原型和手动比对场景;人工处理defineproperty、eval等动态场景;禁用字符串、注释、正则中的替换;用ast工具(如jscodeshift)定位memberexpression节点;赋值场景须分层重构,辅以eslint、测试和ci验证。

直接替换 __proto__ 为 Object.getPrototypeOf() 看似简单,但真正在大型项目中“平滑、无损”落地,关键不在替换动作本身,而在识别上下文、规避副作用、保留语义一致性。盲目全局查找替换极易引入运行时错误(比如误改字符串字面量、JSON 模板、正则表达式或注释里的 __proto__),更危险的是:把本该用 instanceof 或 isPrototypeOf() 判断的地方也硬改成 getPrototypeOf(),反而破坏逻辑。
以下是一套经过验证的配置思路,聚焦可落地、可验证、可回滚:
明确哪些 __proto__ 必须改,哪些不该动
- ✅ 必须升级:
-
obj.__proto__作为表达式读取原型(如const p = obj.__proto__;) -
obj.__proto__ === SomeCtor.prototype这类手动比对(应转为obj instanceof SomeCtor或SomeCtor.prototype.isPrototypeOf(obj))
-
- ⚠️ 需人工介入:
- 出现在
Object.defineProperty(obj, '__proto__', ...)中的——这是在模拟 setter 行为,不能简单替换成setPrototypeOf,得结合初始化逻辑重写 - 在
eval、模板字符串、new Function内部的——静态分析无法安全覆盖,必须标记并人工审查
- 出现在
- ❌ 禁止自动处理:
- 字符串字面量中出现的(如
'use __proto__ mode') - 注释里(如
// fallback for __proto__) - 正则表达式字面量(如
/__proto__/g)
- 字符串字面量中出现的(如
用 AST 工具做精准定位,而非文本搜索
推荐使用 jscodeshift(JS)或 ts-morph(TS),它们能解析语法树,只匹配真实 JS/TS 语句中的 MemberExpression 节点,且可精确判断是否为 __proto__ 属性访问。
一个最小可用的 jscodeshift 转换脚本(proto-to-getprototypeof.js)示例:
module.exports = function(fileInfo, api) {
const j = api.jscodeshift;
const root = j(fileInfo.source);
// 只匹配 obj.__proto__ 形式(非赋值、非 delete、非 in)
root.find(j.MemberExpression)
.filter(path => {
const { object, property } = path.node;
return (
j.Identifier.check(property) &&
property.name === '__proto__' &&
!j.AssignmentExpression.check(path.parent.node) && // 排除 obj.__proto__ = x
!j.UpdateExpression.check(path.parent.node) && // 排除 ++obj.__proto__
!j.UnaryExpression.check(path.parent.node) && // 排除 delete obj.__proto__
!j.BinaryExpression.check(path.parent.node) || // 排除 a.__proto__ === b
(j.BinaryExpression.check(path.parent.node) &&
!['===', '==', '!==', '!='].includes(path.parent.node.operator))
);
})
.replaceWith(path => j.callExpression(j.identifier('Object.getPrototypeOf'), [path.node.object]));
return root.toSource();
};
运行命令:
npx jscodeshift -t proto-to-getprototypeof.js src/ --dry --verbose
加 --dry 先看 diff,确认无误后再加 -w 写入。
对赋值场景做分层处理,绝不滥用 setPrototypeOf
obj.__proto__ = newProto 是高危操作,不能一概替换为 Object.setPrototypeOf(obj, newProto)。应按场景分流:
- 若该赋值发生在对象创建后立即执行,且目的是让对象继承某原型 → 改用
Object.create(newProto)初始化 - 若是动态切换行为(极少见),确认无性能敏感路径后,才用
Object.setPrototypeOf,并在代码旁加// eslint-disable-next-line no-proto注释说明理由 - 若出现在循环或高频调用中 → 必须重构,改用组合、代理(Proxy)或策略模式替代原型篡改
补充配套检查与防护
- 在 ESLint 中启用
no-proto规则,并设为error,防止新代码回归 - 添加 Jest 测试断言,验证关键对象的原型链是否仍符合预期(例如
expect(Object.getPrototypeOf(instance)).toBe(Constructor.prototype)) - CI 流程中加入
tsc --noEmit && eslint --ext .js,.ts src/,确保重构后类型和规范双重通过
整个过程不是“一键”,而是“一配三验”:配好 AST 规则 → 验 diff → 验运行 → 验测试。真正耗时的不是脚本执行,而是理解每一处 __proto__ 在业务中的真实意图。只要守住这个原则,千处修改也能稳稳落地。











