Object.defineProperty()用法详解
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
通常给对象添加属性的做法
1
2
3
4
5
6
7
8
9
10
11
12
13
let user = {};
// 现在给 `user` 对象增加一个属性 `name`,通常的做法:
user.name="妙公子";
console.log(user);
// {name: "妙公子"}
// 如果想要增加一个sayHi方法:
user.sayHi = () => {
console.log("Hi !");
};
console.log(user);
// {name: "妙公子", sayHi: ƒn}
使用 Object.defineProperty()
的做法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let user = {};
// 使用`Object.defineProperty()`给user添加name属性:
Object.defineProperty(user, 'name', {
value: '妙公子'
});
console.log(user);
// {name: "妙公子"}
// 使用`Object.defineProperty()`给user添加sayHi方法:
Object.defineProperty(user, 'sayHi', {
value: function() {
console.log("Hi !");
}
});
console.log(user);
// {name: "妙公子"}
语法
Object.defineProperty是ES5的属性
Object.defineProperty(object, property, descriptor)
同时定义多个属性时可使用Object.defineProperties()用法详解
参数
object
: 要定义属性的对象
property
: 要定义或修改的属性的名称或 Symbol
descriptor
: 要定义或修改的属性描述符
descriptor是以对象的类型传入:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
descriptor: {
value: 默认为 undefined,
// 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)
writable: 默认为 false,
// 当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变
enumerable: 默认为 false,
// 当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中
configurable: 默认为 false,
// 当且仅当该属性的 configurable 键值为 true 时,该属性才可以被删除,以及除 value 和 writable 特性外的其他特性才能可以被修改
get: 默认为undefined,
// 属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。该函数的返回值就是属性的值。
set: 默认为undefined,
// 属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值)。
}
返回值
返回值为当前对象
示例
参数 descriptor
各个属性的具体用法。
value
属性
使用Object.defineProperty()
给user添加name属性:
1
2
3
4
5
6
7
let user = {};
Object.defineProperty(user, 'name', {
value: '妙公子'
});
console.log(user);
// {name: "妙公子"}
value可以是任何有效的 JavaScript 值,所以
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let user = {};
Object.defineProperty(user, "name", {
value: "妙公子" // 字符串
});
Object.defineProperty(user,"isSlow",{
value: true // 布尔型
});
Object.defineProperty(user,"sayHi",{
value: function () { console.log("Hi !") } // 函数
});
Object.defineProperty(user, "age", {
value: 12 // 数字
});
Object.defineProperty(user, "birth", {
value:{
date:"2018-06-29",
hour:"15:30"
} // 日期
});
writable
属性
当 writable 属性设置为 false 时,该属性被称为“不可写的”。它不能被重新赋值。
1
2
3
4
5
6
let user = {};
Object.defineProperty(user, "name", {
value: "妙公子"
});
console.log(user);
当前user对象已经有了name属性,值为”妙公子”,我们试着改变name的值:
1
2
3
user.name="NiceBoy"
console.log(user);
// {name: "妙公子"}
上面代码并没有设置 writable
这个值,默认是false,所以属性值不会改变,也不会引发错误。
我们再验证把writable
设置为true时候能否修改成功
1
2
3
4
5
6
7
8
9
10
let user = {};
Object.defineProperty(user, "name", {
value: "妙公子",
writable: true
});
console.log(user);
user.name="NiceBoy"
console.log(user);
// {name: "NiceBoy"}
可以看出修改成功
enumerable
属性
enumerable
定义了对象的属性是否可以在 for...in
循环和 Object.keys()
中被枚举。
1
2
3
4
let user ={
name: "妙公子",
age: 25
};
如果获取user对象的全部属性,通常做法:
1
2
3
4
5
6
7
8
9
10
11
let keys=Object.keys(user)
console.log(keys);
// ['name', 'age']
// 或
let keys = [];
for(key in user){
keys.push(key);
}
console.log(keys);
// ['name', 'age']
当使用Object.defineProperty定义user属性时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let user ={
name: "妙公子",
age: 25
} ;
//定义一个性别 可以被枚举
Object.defineProperty(user, "gender", {
value: "男",
enumerable: true
});
//定义一个出生日期 不可以被枚举
Object.defineProperty(user, "birth", {
value: "2020-05-03",
enumerable: false
});
获取(枚举)user属性
1
2
3
4
5
6
console.log(user);
// {name: "妙公子", age: 25, gender: "男", birth: "2020-05-03"}
let keys = Object.keys(user);
console.log(keys);
// ["name", "age", "gender"]
可以看出 enumerable=false
的 birth
属性并没有被枚举出来。
configurable
属性
configurable
特性表示对象的属性是否可以被删除,以及除 value
和 writable
特性外的其他特性(在第一次设置之后)是否可以被修改。
第一次定义属性时把 configurable
设置为 false
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
let user = {
name: "妙公子",
age: 25
};
// 定义一个性别属性 不可以被删除和重新定义特性
Object.defineProperty(user, "gender", {
value: "男",
enumerable: true,
configurable: false // 第一次定义为false
});
//试着删除 gender 属性
delete user.gender;
console.log(user);
// {name: "妙公子", age: 25, gender: "男"}
// 结果显示 gender 没有被删除
// 重新定义特(试着修改enumerable设为false)
Object.defineProperty(user, "gender", {
value: "男",
enumerable: false, // 修改为 false
configurable: false
});
// 会报错,如下:
// Uncaught TypeError: Cannot redefine property: gender
// at Function.defineProperty (<anonymous>)
// at <anonymous>
// 说明 configurable 为 false 时, enumerable 属性不能被重新定义
当configurable为false时,除value和writable以外的其他参数属性(enumerable/configurable/get/set)都不能被修改和删除。
第一次定义属性时把 configurable
设置为 true
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
let user ={
name: "妙公子",
age: 25
};
//定义一个性别 可以被删除和重新定义特性
Object.defineProperty(user, "gender", {
value: "男",
enumerable: true,
configurable: true // 第一次定义时候就设置为true
});
// 删除前
console.log(user);
// {name: "妙公子", age: 25, gender: "男"}
// 删除
delete user.gender;
console.log(user);
// {name: "妙公子", age: 25}
// 重新定义特性
Object.defineProperty(user, "gender", {
value: "男",
enumerable: true,
configurable: false // 修改为 false
})
//删除前
console.log(user);
// {name: "妙公子", age: 25, gender: "男"}
// 删除
delete user.gender;
console.log(user);
// {name: "妙公子", age: 25, gender: "男"}
// 结果显示 删除gender失败
Set 和 Get
get
当访问对象的该属性时(例如:console.log(user.name)
),就会调用这个方法,并返回结果。默认为 undefined。
1
2
3
4
5
6
7
8
9
10
11
12
13
let user ={
name: "妙公子"
};
let val = 18;
Object.defineProperty(user, "age", {
// 当获age属性值的时候触发的函数
get: () => {
console.log('数据被获取');
return val;
},
});
console.log(user.age);
// 数据被获取 18
set
当为对象设置该属性值时,就会调用这个方法。默认为 undefined。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let user ={
name: "妙公子"
};
let val = 18;
Object.defineProperty(user, "age", {
// 当获age属性值的时候触发的函数
get: function(){
console.log('数据被获取');
return val;
},
// 当设置值的时候触发的函数
set: function(newVal){
console.log('数据被修改/设置')
count = newVal;
}
});
//获取值
console.log(user.age); // 数据被获取 18
//设置值
user.age = 22; // 数据被修改
console.log(user.age); // 数据被获取 22
下面这个例子中,getter 总是会返回一个相同的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function TestDefineSetAndGet() {
Object.defineProperty(this, 'myproperty', {
get: function () {
return 'I alway return this string,whatever you have assigned';
},
set: function () {
this.myname = 'this is my name string';
}
});
}
var instance = new TestDefineSetAndGet();
instance.myproperty = 'test';
// 'I alway return this string,whatever you have assigned'
console.log(instance.myproperty);
// 'this is my name string'
console.log(instance.myname);
实现一个自存档对象,当设置temperature 属性时,archive 数组会收到日志条目。
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
function Archiver() {
let temperature = null;
let 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;
};
}
let arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]
注意:
- 当使用了getter或setter方法,不允许使用writable和value这两个属性(如果使用,会直接报错滴)
- get或set不是必须成对出现,任写其一就可以
实际运用
1、对象常量 结合writable: false 和 configurable: false 就可以创建一个常量属性(不可修改,不可重新定义或者删除)
2、优化对象获取和修改属性方式
如果你想禁止一个对象添加新属性并且保留已有属性,就可以使用Object.preventExtensions(…)
3、增加属性获取和修改时的信息
扩展知识点
在一些框架,如vue、express、qjs等,经常会看到对Object.defineProperty的使用。那这些框架是如何使用呢?
- MVVM中数据‘双向绑定’实现