前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库Proxy set 陷阱不变量 不可写属性
JavaScript对象与原型

目标属性不可写又不可配置时,Proxy 的 set 陷阱能返回什么?

新值和原值不同时,set 陷阱只能返回 false;返回 true 会被引擎识破,直接抛 TypeError。只有写入的值和原值完全相同,返回 true 才被允许。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#对象与原型
先看核心答案读代码示例
理解线索

引擎在 `set` 陷阱返回后做的检查

  1. 陷阱返回 true引擎回头查目标自身的这个属性,看它是不是不可写又不可配置
  2. 值比较用 Object.is 的规则比较新值和目标上的当前值
  3. TypeError属性锁死了但值不同,说明陷阱在撒谎,直接抛错

这条检查只看目标对象自身的属性。属性只要可写或者可配置,陷阱返回什么都行。

核心回答

先记住这个答案

set 陷阱返回 true 表示「写入成功」。如果目标对象自身有一个不可写又不可配置的数据属性,这个属性的值是永远改不了的,陷阱再说写成功就是在撒谎。所以规范规定:这种情况下陷阱返回 true 时,引擎会检查新值和目标上的当前值是否相同,不同就抛 TypeError。换句话说,值不同时陷阱只能返回 false;值相同时返回 true 是允许的,因为这相当于什么都没改。

  • 值不同还返回 true,赋值处抛 TypeError
  • 值相同时返回 true 是允许的
  • set 返回 false 可表示写入失败
  • 直接转发 Reflect.set 就不会违反

陷阱撒谎会被当场抓住

写一个无论如何都返回 true 的 set 陷阱,看看引擎怎么反应。

值相同放行,值不同抛错JavaScript
const target = {}
Object.defineProperty(target, 'id', {
  value: 1,
  writable: false,
  configurable: false
})

const liar = new Proxy(target, {
  set() {
    return true // 不管写什么都报告成功
  }
})

console.log(Reflect.set(liar, 'id', 1)) // 新值和原值相同,不违反
try {
  Reflect.set(liar, 'id', 2) // 新值不同,还返回 true
} catch (e) {
  console.log(e.name)
}
console.log(target.id)
查看输出与解释
true
TypeError
1

检查发生在陷阱返回之后:陷阱先跑完,拿到 true,引擎再去读 target 上 id 的描述符,发现它锁死了、值是 1,而这次要写的是 2,对不上就抛错。连 Reflect.set 这种本来不抛错的调用方式也照抛,因为这是代理违反了规则,不是普通的写入失败。 为什么要有这条规则?不可写又不可配置,是 JavaScript 给出的「这个值永远不变」的保证,Object.freeze 就是靠它实现的。代理如果能随便声称写成功,外部代码就会以为冻结对象的值变了,这个保证就不成立了。不变量的作用就是让代理可以拦截,但不能伪造这种保证。

正确的写法:直接转发 Reflect.set

大多数时候 set 陷阱只是想在写入前后做点事,比如打日志或校验。最后一步交给 Reflect.set,它会按原生规则去写,写不进去就自己返回 false,永远不会违反不变量。

转发到冻结对象JavaScript
const target = Object.freeze({ id: 1 }) // freeze 后每个属性都不可写、不可配置

const proxy = new Proxy(target, {
  set(t, key, value, receiver) {
    console.log('set', key, value)
    return Reflect.set(t, key, value, receiver) // 写不进去时它自己返回 false
  }
})

console.log(Reflect.set(proxy, 'id', 2))

function strictAssign() {
  'use strict'
  try {
    proxy.id = 3
  } catch (e) {
    console.log(e.name)
  }
}
strictAssign()
console.log(target.id)
查看输出与解释
set id 2
false
set id 3
TypeError
1

陷阱返回 false 之后,结果和给普通只读属性赋值一样:Reflect.set 拿到 false;严格模式下的 proxy.id = 3 抛 TypeError;非严格模式下静默失败。这里的 TypeError 是「写入失败」的正常报错,和上一段「陷阱撒谎」的报错是两回事。

访问器属性的对应规则
如果目标上是不可配置的访问器属性,而且没有 setter,那它同样写不进去。这时陷阱返回 true 一律抛 TypeError,没有「值相同」的例外,因为根本没有值可比。
回答前,多想一步

容易答错的地方

说「必须返回 false」
不完全对。新值和原值相同时返回 true 不会报错。准确说法是:值不同时不能返回 true。
以为属性只是 writable: false 就会触发
必须同时是不可写和不可配置。只是不可写、但还能配置的属性,陷阱返回 true 不会报错,因为理论上可以用 defineProperty 改它的值,引擎就不追究了。
以为检查的是原型链上的属性
检查只看 target 自身的属性描述符。原型上的只读属性不在这条不变量的范围里。
试着用自己的话回答

面试官还会怎么问?

其它陷阱还有哪些类似的不变量?

get:目标上不可写又不可配置的数据属性,必须原样返回它的值。has:不能把不可配置的属性报告成不存在。deleteProperty:不能对不可配置的属性返回删除成功。ownKeys:返回的列表必须包含所有不可配置的自有键;目标不可扩展时,还必须和目标的键完全一致。思路都一样,代理不能推翻目标对象已经做出的「锁定」承诺。

陷阱里不返回任何值会怎样?

返回 undefined 会被转成 false,按写入失败处理,严格模式下的赋值会抛 TypeError。这是写 set 陷阱最常见的 bug:做完了校验却忘了 return true。

想让冻结对象「看起来」能改,有办法吗?

不能在同一个 target 上做到。常见做法是代理一个没冻结的副本,或者用一个空对象做 target,把真实数据放在闭包里,所有陷阱都读写闭包里的数据。这样 target 上没有锁死的属性,也就不受这条不变量限制。

从一道题,走向一组知识

把知识连起来

对象与原型

Object.freeze 冻住的对象,为什么里面的属性还能改?深度冻结怎么写?

Object.freeze 后所有属性都不可写、不可配置,代理冻结对象时最容易撞上这条不变量。

对象与原型

Reflect.ownKeys 返回的键是什么顺序?和 Object.keys 有什么区别?

ownKeys 陷阱也有不变量,返回的键列表必须包含目标上所有不可配置的键,Reflect.ownKeys 正好用来转发。

参考资料

  • JavaScript:对象与原型参考资料

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 陷阱撒谎会被当场抓住
  3. 正确的写法:直接转发 Reflect.set
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑