文章目录
- 1. 引言
- 2. 完整示例代码(含原始注释)
- 3. 代码结构与变量初始化
- 3.1 外部变量 `number`
- 3.2 基础对象 `person`
- 4. `Object.defineProperty` 的完整配置解析
- 4.1 被注释掉的数据描述符(Data Descriptor)
- 4.2 被注释掉的旧式 getter 写法
- 4.3 实际启用的 getter
- 4.4 实际启用的 setter
- 5. 最终日志输出与行为验证
- 5.1 控制台初始输出
- 5.2 手动测试 getter/setter(在控制台执行)
- 测试 1:读取 `age`
- 测试 2:修改 `age`
- 测试 3:再次读取
- 6. 与 Vue 2 响应式系统的关联
- 7. 总结
1. 引言
在 Vue 2 的响应式系统实现中,Object.defineProperty是核心机制之一。虽然 Vue 3 已经转向使用更强大的Proxy,但理解Object.defineProperty对于掌握 Vue 响应式原理、调试旧项目以及深入 JavaScript 对象属性控制仍具有重要意义。本文将结合一段包含详细注释的示例代码,逐行解析其设计思路、配置选项含义以及运行行为,并展示控制台中的实际输出结果。
2. 完整示例代码(含原始注释)
<!DOCTYPEhtml><html><head><metacharset="UTF-8"/><title>回顾Object.defineProperty方法</title><!-- 引入Vue --><!-- <script type="text/javascript" src="../js/vue.js"></script> --></head><body><scripttype="text/javascript">letnumber=18letperson={name:'张三',sex:'男',}Object.defineProperty(person,'age',{// value:18,// enumerable:true, // 控制属性是否可以枚举,默认值为false// writable:true, // 控制属性是否可以被修改,默认值是false// configurable:true, // 控制属性是否可以被删除,默认值是false// 当有人读取age的属性时,get函数(getter)就会被调用,且返回值就是age的值// get:function(){// //return 'hello'// console.log('有人读取age属性了')// return number// }get(){//return 'hello'console.log('有人读取age属性了')returnnumber},// 当有人修改person的age的属性时,set函数(setter)就会被调用,且会受到修改的具体值set(value){console.log('有人修改了age属性,且值是:',value)number=value}})console.log(person)</script></body></html>这段代码不仅实现了age属性的响应式定义,还通过大量注释说明了Object.defineProperty的各种配置项及其作用。下面我们将逐部分讲解这些注释内容和实际代码逻辑。
3. 代码结构与变量初始化
3.1 外部变量number
letnumber=18- 这是一个闭包变量,用于存储
age属性的真实值。 - 使用外部变量而非直接在对象上存值,是为了配合 getter/setter 实现“拦截”逻辑。
- 在 Vue 2 中,类似地,每个响应式属性背后都有一个内部存储(如
_data.age),并通过 getter/setter 暴露给用户。
3.2 基础对象person
letperson={name:'张三',sex:'男',}- 初始对象仅包含两个普通属性:
name和sex。 - 后续将通过
Object.defineProperty动态添加age属性,而非直接赋值(如person.age = 18)。
4.Object.defineProperty的完整配置解析
调用:
Object.defineProperty(person,'age',{/* descriptor */})目标是为person对象定义一个名为'age'的新属性,其行为由描述符(descriptor)控制。
4.1 被注释掉的数据描述符(Data Descriptor)
// value:18,// enumerable:true, // 控制属性是否可以枚举,默认值为false// writable:true, // 控制属性是否可以被修改,默认值是false// configurable:true, // 控制属性是否可以被删除,默认值是false这些是数据描述符的典型配置项,适用于直接定义静态值的属性:
value:属性的值。若使用此选项,则不能同时使用get/set。enumerable:是否可枚举。- 若为
false(默认),则该属性不会出现在for...in循环、Object.keys()或JSON.stringify()中。 - 示例中未显式设置,因此
age默认不可枚举。
- 若为
writable:是否可写。- 若为
false,则person.age = 20会静默失败(严格模式下抛错)。
- 若为
configurable:是否可配置。- 若为
false,则无法删除该属性,也无法再次修改其描述符(包括转为 getter/setter)。
- 若为
✅关键点:一旦使用
get/set(存取描述符),就不能再使用value或writable,否则会报错。
4.2 被注释掉的旧式 getter 写法
// get:function(){// //return 'hello'// console.log('有人读取age属性了')// return number// }这是 ES5 风格的函数写法,功能与下方的简写形式完全一致。现代开发中通常使用 ES6 的方法简写:
get(){console.log('有人读取age属性了')returnnumber}两者等价,后者更简洁。
4.3 实际启用的 getter
get(){//return 'hello'console.log('有人读取age属性了')returnnumber}- 当代码执行
person.age时,自动调用此函数。 - 函数体中:
console.log(...)用于追踪访问行为(模拟 Vue 的依赖收集日志)。return number返回真实存储的值。- 注释掉的
//return 'hello'表明:你可以返回任意值,不一定要与原变量一致(可用于计算属性或格式化)。
4.4 实际启用的 setter
set(value){console.log('有人修改了age属性,且值是:',value)number=value}- 当执行
person.age = newValue时,自动调用此函数,value即为传入的新值。 - 函数体中:
- 打印日志,记录修改行为(模拟 Vue 的派发更新通知)。
- 将新值赋给闭包变量
number,确保后续get能返回最新值。
- 注意:setter 不返回任何值,其作用是副作用(side effect)。
5. 最终日志输出与行为验证
console.log(person)5.1 控制台初始输出
运行页面后,控制台首先显示:
{ name: "张三", sex: "男" }age未显示,因为其enumerable默认为false。- 此时未触发
get或set,因为只是打印对象引用,未实际访问age。
5.2 手动测试 getter/setter(在控制台执行)
测试 1:读取age
person.age输出:
有人读取age属性了 18→ 触发get,返回number的当前值。
测试 2:修改age
person.age=30输出:
有人修改了age属性,且值是: 30→ 触发set,将number更新为 30。
测试 3:再次读取
person.age输出:
有人读取age属性了 30→get返回更新后的值。
6. 与 Vue 2 响应式系统的关联
上述代码正是 Vue 2 实现响应式的核心简化版:
get→ 收集依赖(如 Watcher)set→ 通知依赖更新(触发 re-render)- 闭包变量
number→ 对应组件实例的_data.age - 不可枚举性→ 避免干扰普通属性遍历
这也解释了为什么 Vue 2 无法检测到:
- 新增属性(需
Vue.set) - 删除属性(需
Vue.delete) - 数组索引直接赋值(因无法拦截)
因为Object.defineProperty只能拦截已知属性的读写。
7. 总结
本文不仅展示了Object.defineProperty的基本用法,更完整解读了原始代码中的每一处注释和设计意图:
- 数据描述符(
value,writable,enumerable,configurable)的作用与限制; - 存取描述符(
get/set)如何实现属性拦截; - 闭包变量在状态管理中的关键角色;
- 控制台输出的行为逻辑及测试方法;
- 与 Vue 2 响应式原理的直接对应关系。
通过这种“带注释的代码 + 逐行讲解”的方式,我们不仅能学会如何使用Object.defineProperty,更能理解其在框架底层中的工程价值。这对于深入前端原理、排查响应式 bug、乃至面试进阶都具有重要意义。