Object.defineProperty 到底怎么用?

本文最后更新于: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); // { name: '张三', age: 25 }

简单直接,但没法精细控制属性。

用 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); // Hello World

// 啥类型都能放
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); // https://api.example.com

config.apiUrl = 'https://hacked.com'; // 静默失败(严格模式报错)
console.log(config.apiUrl); // 还是 https://api.example.com

// 实现常量对象
const constants = {};
Object.defineProperty(constants, 'PI', {
value: 3.14159,
writable: false,
configurable: false // 不能删,不能重定义
});

writable 效果

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...in 遍历
for (let key in user) {
console.log(key); // name, age(没有 password)
}

// Object.keys
console.log(Object.keys(user)); // ['name', 'age']

// 但属性确实在
console.log(user.password); // secret123

// 想看所有属性(包括不可枚举)
console.log(Object.getOwnPropertyNames(user)); // ['name', 'age', 'password']

用在哪:

  • 隐藏内部属性
  • 实现私有属性(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; // TypeError

Object.defineProperty(obj, 'locked', {
value: '尝试修改',
writable: true
}); // TypeError

configurable 效果

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
  • 这俩是互斥的

get set 效果

实际应用案例

案例 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; // ✗ 报错:年龄必须是 0-150 之间的数字
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); // 计算面积 → 200
console.log(rect.area); // 200(直接返回缓存)
rect.invalidateCache();
console.log(rect.area); // 计算面积 → 200

案例 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; // 触发更新 → count 变化了:1
state.count = 2; // 触发更新 → 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);
// {
// value: 'test',
// writable: true,
// enumerable: true,
// configurable: true
// }

// 获取所有属性的描述符
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
// Proxy 实现响应式(Vue 3 的做法)
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; // 读取:count
state.count = 1; // 设置:count = 1
state.newProp = 2; // 设置:newProp = 2(也能拦截)

总结

属性描述符 作用 默认值
value 属性值 undefined
writable 可修改 false
enumerable 可枚举 false
configurable 可删除/重定义 false
get 读取时调用 undefined
set 设置时调用 undefined

Object.defineProperty 到底怎么用?
http://bestkele.com/2021/06/28/concept/Object-defineProperty/
作者
kele
发布于
2021年6月28日
许可协议