Object.defineProperty可以用来给Object增加新的属性或更新已有属性,并返回该Object.

Object.defineProperty并不单单只是让我们手动给Object添加或修改属性值,它最大的特点是我们可以给对象单个属性值添加属性描述符(PropertyDescriptor).

属性描述符可以给当前对象属性添加额外的一些功能,例如:是否可以被修改、是否可以被枚举、捕获该属性的值的变化和读取.

注意: 冻结一个对象属性操作可以用 Object.freeze()

注意: 可以用Object.defineProperties批量给对象添加或修改属性描述符

语法

1
2
3
4
Object.defineProperty (O,P,Attributes)
// O 必须是个Object,否则JS解释器会抛出 TypeError exception 错误
// P 应该是个字符串或Symbol, 如果都不是 JS引擎会 取它的toString()方法值作为 对象的key
// Attributes 就是 属性描述符类型的对象,通过它定义了该属性的行为

Object.defineProperty是ES5新增的方法,所以IE8以下的浏览器是不支持的(Edge都用 Chromium了,谁还用IE8)

属性描述符

Object.defineProperty属性描述符有两种类型:数据描述符(Attributes of a Data Property) 和存取描述符(Attributes of an Accessor Property),一个属性只能用一种属性描述符进行定义.

数据描述符存取描述符都是有四种属性组成,它们之间是有相同的属性的.

数据描述符(Attributes of a Data Property)

属性名 数据类型 描述
value 任何有效的ECMAScript language type.例如: 字符串,数字,对象,函数等. 默认值是 undefined 当前定义的对象属性值
writable Boolean.默认值是 false 声明当前定义的属性是否可以被修改.如果是 false则表示不能被修改
enumerable Boolean.默认值是 false 声明当前定义的属性是否出现在对象的枚举中.如果是 false则表示不会被枚举.
configurable Boolean.默认值是 false 声明当前定义的属性能否从对象中删除、能否改变当前属性的属性描述符.如果是 false,则以上行为都不能!

例子

1
2
3
4
5
6
var obj1 = Object.defineProperty({},'key', {  enumerable: false, configurable: false, writable: false, value: 1});

console.log(obj1.key); // 1
obj1.key = 2; // 赋值无效, 因为 writable是 false
console.log(obj1.key); // 1

存取描述符

属性名 数据类型 描述
get function object 或 undefined. 默认值 undefined 当访问该属性的时候,该方法会被执行.该方法不会有参数传入,但可以获得this对象.
set function object 或 undefined. 默认值 undefined 当给该属性赋值的时候,该方法会被执行.新的属性值会作为函数参数传入.set不是必须,但它会影响到get方法的取值.
enumerable Boolean.默认值是 false 声明当前定义的属性是否出现在对象的枚举中.如果是 false则表示不会被枚举.
configurable Boolean.默认值是 false 声明当前定义的属性能否从对象中删除、能否改变当前属性的属性描述符.如果是 false,则以上行为都不能!

例子

1
2
3
var obj2 = Object.defineProperty({}, 'key', {'enumerable': true,'configurable': true, get() {return this.value}});
obj2.key = 1;
console.log(obj2.key); // undefined 因为没有设置 setter方法

Object.defineProperty第一个参数是可以接受任何Object类型的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Archiver() {
var temperature = null;
var archive = [];

Object.defineProperty(this, 'temperature', {
get: function() {
console.log('get!');
return temperature;
},
set: function(value) {
temperature = value;
archive.push({ val: temperature });
}
});

this.getArchive = function() { return archive; };
}

var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]

Vue的数据更新实现

Vue.2x在数据和界面同步的实现上,就是用Object.defineProperty实现的.Vue.2x会将 data属性中定义的对象数据遍历一遍,通过Object.defineProperty包装一下,以获得监听数据变化的能力.

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="myapp">
<input v-model="message" /><br>
<span v-bind="message"></span>
</div>
<script type="text/javascript">
var model = {message: "" };
var models = myapp.querySelectorAll("[v-model=message]");
for (var i = 0; i < models.length; i++) {
models[i].onkeyup = function() {
model[this.getAttribute("v-model")] = this.value;
}
}
// 观察者模式 / 钩子函数 // defineProperty 来定义一个对象的某个属性
Object.defineProperty(model, "message", {
set: function(newValue) {
var binds = myapp.querySelectorAll("[v-bind=message]");
for (var i = 0; i < binds.length; i++) {
binds[i].innerHTML = newValue;
};
var models = myapp.querySelectorAll("[v-model=message]");
for (var i = 0; i < models.length; i++) {
models[i].value = newValue;
};
this.value = newValue;
},
get: function() {
return this.value;
}
})
</script>
</body>
</html>

上面👆代码运行效果

参考

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

https://tc39.es/ecma262/#sec-object.defineproperty