本文最后更新于:2026年2月11日 晚上
Object.defineProperty 到底怎么用?
Object.defineProperty 是 JavaScript 里一个挺强大的 API,能精确控制对象属性的行为。Vue 2.x 的响应式系统就是靠它实现的。
常规属性 vs 定义属性
普通方式加属性
1 2 3 4 5
| const user = {}; user.name = '张三'; user.age = 25;
console.log(user);
|
简单直接,但没法精细控制属性。
用 Object.defineProperty
1 2 3 4 5 6 7 8 9 10
| const user = {};
Object.defineProperty(user, 'name', { value: '张三', writable: true, enumerable: true, configurable: true });
console.log(user.name);
|
通过配置对象,能精确控制属性的各种行为。
属性描述符详解
1. value - 属性值
最基础的,设置属性的具体值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const obj = {};
Object.defineProperty(obj, 'message', { value: 'Hello World' });
console.log(obj.message);
Object.defineProperty(obj, 'count', { value: 42 }); Object.defineProperty(obj, 'isActive', { value: true }); Object.defineProperty(obj, 'items', { value: ['a', 'b', 'c'] }); Object.defineProperty(obj, 'nested', { value: { a: 1, b: 2 } }); Object.defineProperty(obj, 'fn', { value: function() { console.log('hi'); } });
|
2. writable - 能不能改
控制属性值能不能被修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const config = {};
Object.defineProperty(config, 'apiUrl', { value: 'https://api.example.com', writable: false });
console.log(config.apiUrl);
config.apiUrl = 'https://hacked.com'; console.log(config.apiUrl);
const constants = {}; Object.defineProperty(constants, 'PI', { value: 3.14159, writable: false, configurable: false });
|

3. enumerable - 能不能被遍历
控制属性会不会出现在遍历里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const user = { name: '张三', age: 25 };
Object.defineProperty(user, 'password', { value: 'secret123', enumerable: false });
for (let key in user) { console.log(key); }
console.log(Object.keys(user));
console.log(user.password);
console.log(Object.getOwnPropertyNames(user));
|
用在哪:
- 隐藏内部属性
- 实现私有属性(ES6 之前的方式)
- 排除不需要序列化的属性
4. configurable - 能不能改配置
控制属性描述符能不能改、属性能不能删:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const obj = {};
Object.defineProperty(obj, 'locked', { value: '不能修改我', writable: false, configurable: false });
delete obj.locked;
Object.defineProperty(obj, 'locked', { value: '尝试修改', writable: true });
|

5. get 和 set - 存取器
最强大也最复杂的,定义属性的读取和设置行为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const user = { firstName: '三', lastName: '张' };
Object.defineProperty(user, 'fullName', { get: function() { return this.lastName + this.firstName; }, set: function(value) { const parts = value.split(' '); this.lastName = parts[0]; this.firstName = parts[1]; } });
console.log(user.fullName);
user.fullName = '李 四'; console.log(user.lastName); console.log(user.firstName);
|
重要限制:
- 用了 getter/setter,就不能同时用 value 和 writable
- 这俩是互斥的

实际应用案例
案例 1:数据校验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function createValidatedUser() { const user = {}; let _age = 0; Object.defineProperty(user, 'age', { get: function() { return _age; }, set: function(value) { if (typeof value !== 'number' || value < 0 || value > 150) { throw new Error('年龄必须是 0-150 之间的数字'); } _age = value; } }); return user; }
const user = createValidatedUser(); user.age = 25; user.age = 200; user.age = 'abc';
|
案例 2:计算属性缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| function createRectangle(width, height) { const rect = { width, height }; let _areaCache = null; let _cacheValid = false; Object.defineProperty(rect, 'area', { get: function() { if (!_cacheValid) { _areaCache = this.width * this.height; _cacheValid = true; console.log('计算面积'); } return _areaCache; } }); rect.invalidateCache = function() { _cacheValid = false; }; return rect; }
const rect = createRectangle(10, 20); console.log(rect.area); console.log(rect.area); rect.invalidateCache(); console.log(rect.area);
|
案例 3:Vue 响应式原理简化版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| function reactive(obj) { const deps = new Map(); Object.keys(obj).forEach(key => { let value = obj[key]; const subscribers = new Set(); Object.defineProperty(obj, key, { get: function() { const currentEffect = globalThis.activeEffect; if (currentEffect) { subscribers.add(currentEffect); } return value; }, set: function(newValue) { if (value !== newValue) { value = newValue; subscribers.forEach(effect => effect()); } } }); }); return obj; }
const state = reactive({ count: 0 });
globalThis.activeEffect = () => { console.log('count 变化了:', state.count); }; state.count; globalThis.activeEffect = null;
state.count = 1; state.count = 2;
|
获取属性描述符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const obj = { name: 'test' };
const descriptor = Object.getOwnPropertyDescriptor(obj, 'name'); console.log(descriptor);
const descriptors = Object.getOwnPropertyDescriptors(obj); console.log(descriptors);
|
批量定义属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const user = {};
Object.defineProperties(user, { name: { value: '张三', writable: true, enumerable: true }, age: { value: 25, writable: true, enumerable: true }, password: { value: 'secret', writable: true, enumerable: false } });
|
跟 Proxy 比咋样?
ES6 的 Proxy 也能实现类似功能,但有区别:
| 特性 |
Object.defineProperty |
Proxy |
| 拦截范围 |
已存在的属性 |
整个对象的所有操作 |
| 新增属性 |
需要手动处理 |
自动拦截 |
| 数组变化 |
难以监听 |
可以监听 |
| 性能 |
略快 |
略慢 |
| 兼容性 |
支持 IE9+ |
不支持 IE |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const reactive = (obj) => { return new Proxy(obj, { get(target, key) { console.log('读取:', key); return target[key]; }, set(target, key, value) { console.log('设置:', key, '=', value); target[key] = value; return true; } }); };
const state = reactive({ count: 0 }); state.count; state.count = 1; state.newProp = 2;
|
总结
| 属性描述符 |
作用 |
默认值 |
| value |
属性值 |
undefined |
| writable |
可修改 |
false |
| enumerable |
可枚举 |
false |
| configurable |
可删除/重定义 |
false |
| get |
读取时调用 |
undefined |
| set |
设置时调用 |
undefined |