BubblyPoker's Blog

浅谈Object.defineProperty

最近在学习Vue的时候,得知Vue的数据的双向绑定就是利用Object的defineProperty方法实现的,就是在set方法中进行处理的,这个以后再说。

今天主要是研究一下Object.defineProperty方法的使用,并且最后附上一个使用类二进制标记语法处理包装defineProperty方法的例子。

好吧,废话不多说,进入正文…


简介

Object.defineProperty()方法的作用就是在一个Object上定义或者修改一个属性,然后其返回值就是这个Object。

语法

Object.defineProperty(obj, prop, descriptor)

defineProperty方法接收3个参数,这三个参数分别表示

obj: 需要定义或修改属性的对象
prop: 定义或修改的属性的属性名
descriptor: 属性描述符,定义属性的配置

prop参数最终都会被转化成String类型,例如输入prop为undefined,那么最终属性名为’undefined’,其他类同

用法

1
2
3
4
5
6
7
var obj = {}
Object.defineProperty(obj, "name", {
configurable: true,
enumerable: true,
writable: true,
value: "BubblyPoker"
})

或者

1
2
3
4
5
6
7
8
9
Object.defineProperty(obj, "name", {
configurable: true,
enumerable: true,
set: function(newVal){},
get: function(){}
})
//如果想查看某个属性的描述符可以使用,返回一个包含描述符的对象
Object.getOwnPropertyDescriptor(obj, "name")

可以看到defineProperty方法有两种描述符定义属性,前一种的descriptor叫做数据描述符(data descriptor),后面的一种的descriptor叫做存取器描述符(accessor descriptor),那它俩有啥区别呢?

看ECMAScript给出的区别:

A data property descriptor is one that includes any fields named either [[Value]] or [[Writable]]
An accessor property descriptor is one that includes any fields named either [[Get]] or [[Set]]

意思就是说

  • 数据描述符:至少包含value和writable中的一个
  • 存取器描述符:至少包含set和get中的一个

描述符必须只能是两种其中之一,不能两者同时存在,不然会报TypeError

Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute

而configurable和enumerable是任一个描述符都可以有的

如果定义的时候只给出了configurable和enumerable这两项,则它被视为数据描述符

下面我们看一下描述符中各个属性的作用

描述符属性

  • configurable

    false:不能删除该属性,不能将该属性转换成有其他描述符定义的属性,不能将writable值由false改为true,但是可以由true改为false,不能改变enumerable值(不管原来值是什么)

    truefalse不能的都能

    默认为false

    需要注意的是,不管是false还是true,value属性都能改变

  • enumerable

    false:属性不能被for in和Object.keys遍历到

    默认为false

  • writable

    false:不能改变value,即不能给属性赋值

    默认为false

    false的时候,虽然不能改变属性的值,但是试图给属性赋值的时候不会报错

  • value

    属性的值,可以是任何ECMAScript规范内的类型

    默认为undefined

  • set

    给属性提供setter的方法,默认undefined,该方法只接受一个参数–一个新的属性值

  • get
    给属性提供getter的方法,默认undefined,该方法的返回值作为属性的值

其他

一般情况下,如果没有显式地声明configurable,enumerable和writable三个参数的话,那么它们三个默认是false,但是对于一般的对象属性赋值,会间接地把writable,configurable和enumerable三个属性设置为true

1
2
3
4
5
6
7
8
9
var obj = {}
obj.name = "BubblyPoker"
//等同于
Object.defineProperty(obj, "name", {
value : "BubblyPoker",
writable : true,
configurable : true,
enumerable : true
})

当你试图重新定义属性时,如果没有显式地指定configurable和enumerable的值,则其默认值还是会取原属性描述符对应的configurable和enumerable的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var obj = {}
//先将configurable和enumerable设置为true
Object.defineProperty(obj, "name", {
configurable: true,
enumerable: true,
writable: false,
value:"BubblyPoker"
})
//此时,再重新定义name属性
Object.defineProperty(obj, "name", {
writable: true,
value:"zhuzhiyang"
})
Object.getOwnPropertyDescriptor(obj, "name")
//configurable和enumerable仍然为true,而不是默认值false

兼容性

应该说现在主流的浏览器都支持这个属性,具体可以看这里

桌面端

FF(Gecko) Chrome IE Opera Safari
4.0(2) 5 9[1] 11.6 5.1[2]

移动端

FF Mobile(Gecko) Android IE Mobile Opera Mobile Safari Mobile
4.0(2) All 9 11.5 All

IE8其实实现了Object.defineProperty方法,但是只能用在DOM对象上面,如果用在原生的Object上面会报错
Safari5中也支持,但不能是DOM对象

类二进制标记语法(binary-flags-like)

场景

当你需要用defineProperty方法定义多个对象的多个属性时

Code

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
var result = {};
function setProp (flag, obj, prop, getter, setter) {
if (flag & 8) {
// 存取器描述符
if (getter) {
result.get = getter;
} else {
delete result.get;
}
if (setter) {
result.set = setter;
} else {
delete result.set;
}
delete result.value;
delete result.writable;
} else {
// 数据描述符
if (arguments.length > 3) {
result.value = getter;
} else {
delete result.value;
}
result.writable = Boolean(flag & 4);
delete result.get;
delete result.set;
}
result.enumerable = Boolean(flag & 1);
result.configurable = Boolean(flag & 2);
Object.defineProperty(obj, prop, result);
return obj;
}

由于存取器描述符最多有四种情况(configurable和enumerable属性组合),数据描述符最多有八种情况(configurable,enumerable和writable属性组合),所以一共最多有12中情况,可以利用二进制的位与&来区别各种情况

所以,当flag的可能值以及获得的描述符如下

flag configurable enumerable writable
0(0000) × × ×
1(0001) × ×
2(0010) × ×
3(0011) ×
4(0100) × ×
5(0101) ×
6(0110) ×
7(0000)
8(1000) × × no
9(1001) × no
10(1010) × no
11(1011) no

其中0~7为数据描述符,8~11为存取器描述符

使用

1
2
3
4
5
6
7
8
9
10
11
var obj = {}
// 定义一个configurable,enumerable,writable均为false的数据描述符类型的属性
setProp(1, obj, "name", "BubblyPoker")
//定义一个configurable,enumerable均为false的存取器描述符类型的属性
setProp(8, obj, "name2", function(){
return this.name || "zhuzhiyang";
}, function(newVal){
this.name = newVal;
})

总结

利用Object.defineProperty方法可以达到很多意想不到的结果,例如,属性定义的时候在set方法里添加一个自定义的钩子,那么在赋值的时候就会自动调用这个钩子,以达到一些自定义的效果,get方法也一样。

坚持原创技术分享,您的支持将鼓励我继续创作!