前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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 热点
旧版
首页程序员面试题库Object.defineProperty 定义 getter setter 与字面量区别
JavaScript对象与原型

用 defineProperty 写 getter/setter,和直接在字面量里写 get/set 有什么不一样?

读写效果完全一样,差别在默认特性。字面量里的 get 和 set 默认可枚举、可配置;Object.defineProperty 没写的特性一律是 false,属性遍历不到、删不掉、也不能再改定义。

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

同样是访问器,特性默认值不同

  1. 字面量 get 和 set创建对象时一起写,可枚举、可配置,Object.keys 能看到
  2. Object.defineProperty对象创建后再加,没写的 enumerable 和 configurable 都是 false
  3. 类里的 get 和 set定义在原型上,不可枚举但可配置,实例自身没有这个属性

一个描述符里不能既有 get 又有 value,访问器和数据属性的写法混在一起会直接抛 TypeError。

核心回答

先记住这个答案

从读写上看两种写法没有区别,读属性都会调 getter,赋值都会调 setter。真正的差别在属性特性的默认值:对象字面量里写的 get 和 set,它们的 enumerable 和 configurable 默认都是 true;用 Object.defineProperty 定义时,没写的特性一律按 false 处理。所以后者定义的访问器 Object.keys 看不到,JSON.stringify 不输出,delete 也删不掉。另外 defineProperty 能给一个已经存在的对象补访问器,字面量只能在创建对象时写。

  • 两种写法调用 getter、setter 的方式完全一样
  • 字面量访问器默认可枚举、可配置
  • 描述符省略特性默认 false
  • defineProperty 能给已经存在的对象补访问器

同一个访问器,两种写法对比

先看一段对比,a 用字面量写,b 用 defineProperty 写,getter 和 setter 的逻辑一模一样。

读写一样,遍历和删除不一样JavaScript
const a = {
  _n: 1,
  get n() { return this._n },
  set n(v) { this._n = v }
}
const b = { _n: 1 }
Object.defineProperty(b, 'n', {
  get() { return this._n },
  set(v) { this._n = v }
  // 没写 enumerable 和 configurable,默认都是 false
})

console.log(a.n, b.n)
console.log(Object.keys(a).join(','))
console.log(Object.keys(b).join(','))
console.log(JSON.stringify(b))
console.log(delete b.n, b.n)
查看输出与解释
1 1
_n,n
_n
{"_n":1}
false 1

a.n 和 b.n 都读到 1,说明访问器本身没差别。差别出在后面三行:b 的 n 不可枚举,Object.keys 和 JSON.stringify 都跳过了它;它又不可配置,delete 返回 false,属性还在。展开运算符 { ...b } 也只会复制 _n。 这就是面试里最想听到的点:不是 defineProperty 更高级,而是它的默认值更保守。想让它和字面量表现一致,要显式写上 enumerable: true, configurable: true。

不可配置意味着什么
configurable: false 之后,这个属性不能删除,也不能再用 defineProperty 改成别的样子,比如把访问器换成普通值,或者重新改成可枚举,都会抛 TypeError。所以在第三方库里调用 defineProperty 时,不写 configurable 等于把这个属性锁死了,后面谁想覆盖都覆盖不了。

什么时候非用 defineProperty 不可

字面量写法只能在创建对象的那一刻用。对象已经存在、而且要拦截它原有的属性时,只能用 defineProperty。

Vue 2 响应式就是这么做的JavaScript
function defineReactive(obj, key) {
  let val = obj[key] // 先把原来的值存进闭包
  Object.defineProperty(obj, key, {
    enumerable: true, // 显式打开,否则 v-for、JSON 序列化都看不到
    configurable: true,
    get() {
      // 这里收集依赖:谁读了这个属性
      return val
    },
    set(newVal) {
      if (newVal === val) return
      val = newVal
      // 这里通知依赖更新视图
    }
  })
}

Vue 2 拿到用户传进来的 data 对象后,逐个属性调用类似的函数,把普通属性原地替换成访问器。这件事字面量做不到,因为对象不是它创建的。注意这里 enumerable 被显式写成 true,不然遍历数据时这些属性就消失了。 这种做法的代价也很明显:只能拦截定义时已经存在的属性,后加的属性拦不到,所以 Vue 2 才需要 Vue.set。数组下标其实也能用 defineProperty 拦,但数组可能很长,Vue 2 出于性能考虑没这么做,改成重写 push 和 splice 这些会改动数组的方法。Vue 3 改用 Proxy,拦截的是整个对象,就没有这个限制。

字面量写法更适合日常业务
业务代码里给对象加一个计算属性,比如 get fullName(),直接写在字面量或 class 里更直观。class 里的 get 和 set 定义在原型上,不可枚举,但可配置,所有实例共享一份。
回答前,多想一步

容易答错的地方

说两种写法「完全一样」
读写行为一样,但特性默认值不同,结果就是遍历、序列化、删除时表现不一样。只答「语法不同、行为一致」会被追问卡住。
在描述符里同时写 value 和 get
描述符只能是数据描述符(value 和 writable)或访问器描述符(get 和 set)其中一种,混着写会抛 TypeError。想让访问器有初始值,就像上面那样把值存在闭包或另一个属性里。
只写 getter 就以为属性是只读的「常量」
只有 get 没有 set 时,赋值不会生效:非严格模式静默失败,严格模式抛 TypeError。但如果 configurable 是 true,别人照样能用 defineProperty 把它整个换掉,并不是真正锁死。
试着用自己的话回答

面试官还会怎么问?

怎么查看一个属性是用哪种方式定义的?

用 Object.getOwnPropertyDescriptor(obj, key) 看描述符。字面量访问器得到的是 { get, set, enumerable: true, configurable: true },而 defineProperty 没写特性时这两个都是 false。

Object.defineProperties 和它有什么区别?

只是批量版本,一次传一个对象,给多个属性分别写描述符,默认值规则完全一样。

为什么 Vue 3 不再用 defineProperty?

defineProperty 是按属性拦截的,要在初始化时递归遍历所有属性,后加的属性监听不到,数组也只能靠重写方法兜底。Proxy 拦截整个对象的所有操作,新增、删除属性都能感知,还可以在访问到深层对象时才去代理,初始化成本更低。

从一道题,走向一组知识

把知识连起来

对象与原型

Proxy 的 get 陷阱能做什么?怎么用它实现链式调用?

Vue 2 用 defineProperty 拦截读写,Vue 3 换成了 Proxy 的 get 陷阱,两题对照能看出为什么要换。

对象与原型

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

defineProperty 默认生成不可配置属性,这类属性套上 Proxy 后会触发不变量检查。

参考资料

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

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

本题目录
  1. 先记住这个答案
  2. 同一个访问器,两种写法对比
  3. 什么时候非用 defineProperty 不可
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

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

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