ECMAScript 5 出了一个【属性描述符】,主要是为了【给属性增加更多的控制】。下面我们就谈论这样一个新的特性-Object.defineProperty()
。
Object.defineProperty() 方法直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象。
语法定义
Object.defineProperty(obj, prop, descriptor)
- obj : 需要定义属性的对象
- prop : 需被定义或修改的属性名
- descriptor : 需被定义或修改的属性的描述符
为什么我们要用这个新特性?
我们给对象创建属性有下面这样几种方法:1
2
3
4
5
6
7
8
9
10var person = {};
//第一种方式
person.name = 'Seven';
//第二种方式
person[name] = 'Seven';
//第三种方式
Object.defineProperty(person, 'name', {
value : 'Seven'
})
person.name //Seven
看起来好像最后一种新方法最麻烦,那我们为什么还要用最后一种方法呢?因为后面的descriptor可以让我们制定更多对对象属性的策略。
descriptor数据描述符-writable
上面我们提到了更多策略,其实这个更多策略就是让属性可以更多不同的权限,比如让它只能读,不能写。
当属性特性(property attribute) writable 设置为false时,表示 non-writable,属性不能被修改。
1 | Object.defineProperty(person,'sex'{ |
当我们设置了descriptor里面的writable为false的时候,这个sex就不可以更改了。虽然你可以设置,但是设置后结果还是girl不会变的。
有人会说,这样不是很不好吗?如果我们在不知情的情况下改写了,连报错都不报。会造成很多bug。很难排查出到底是哪里出了bug。
解决方法是写上use strict
。这样就会报错了。
1 | // 定义严格模式 |
descriptor数据描述符-enumerable
上面已经提到了一个writable
,表示是否可写。现在在看一个数据描述符enumerable
。
属性特性 enumerable 定义了对象的属性是否可以在 for…in 循环和 Object.keys() 中被枚举。
1 | var o = {}; |
相当于定义了enumerable
后,enumerable
可以将其“藏”起来,不被循环看见。
那么属性描述符可以修改吗?比如原来我设置了writable为false,我现在想改变了。那我可以更改吗?这就又有一个专门的属性描述符。
descriptor数据描述符-configurable
如果属性已经存在,Object.defineProperty()将尝试根据描述符中的值以及对象当前的配置来修改这个属性。如果描述符的 configurable 特性为false(即该特性为non-configurable),那么除了 writable 外,其他特性都不能被修改,并且数据和存取描述符也不能相互切换。
如果一个属性的 configurable 为 false,则其 writable 特性也只能修改为 false。
如果尝试修改 non-configurable 属性特性(除 writable 以外),将会产生一个TypeError 异常,除非当前值与修改值相同。
configurable 特性表示对象的属性是否可以被删除,以及除 writable 特性外的其他特性是否可以被修改。
1 | var obj = { }; |
因为configurable是true,所以writable可以更改。
1 | var o = {}; |
存取描述符
上面介绍的是数据描述符,现在介绍下存取描述符。
1 | var obj = { }; |
就是说我们可以利用get/set来对属性进行一定的控制和更改。我们最后通过obj.attr得到的值其实就是通过get返回的值。而我们通过obj.attr = value,设置的值其实就是set的值。
1 | var pattern = { |
上面一个例子是从mdn上面学习到的,仔细看,如果能看明白,应该就能明白这个set/get的用法了。
获取对象描述符
Object.getOwnPropertyDescriptor
可以帮助我们获得对象描述符。比如我们定义:1
2
3
4
5
6
7
8
9
10
11var obj = {};
Object.defineProperty(obj, attr, {
value : 'a',
writable : false,
configurable : true
})
var descriptor = Object.getOwnPropertyDescriptor(obj, attr);
console.dir(descriptor);
结果是:{ value: 'a', writable: false, enumerable: false, configurable: true }
。这是console.log
的结果。console.dir
会显示的很好看。
注意事项
数据描述符和存取描述符不能混合使用,否则会报错:TypeError: property descriptors must not specify a value or be writable when a getter or setter has been specified
。
1 | Object.defineProperty(o, "conflict", { |
定义多个属性
1 | var obj = {}; |
用途
这一部分是我觉得最有价值的一部分了。是我在这篇文章里看到并学习的。感谢。可以优化对象获取和修改属性的方式。下面我就直接引用这个文章里的描述了。
这个优化对象获取和修改属性方式,是什么意思呢? 过去我们在设置dom节点transform时是这样的。1
2
3
4
5//加入有一个目标节点, 我们想设置其位移时是这样的
var targetDom = document.getElementById('target');
var transformText = 'translateX(' + 10 + 'px)';
targetDom.style.webkitTransform = transformText;
targetDom.style.transform = transformText;
通过上面,可以看到如果页面是需要许多动画时,我们这样编写transform属性是十分蛋疼的。
但如果通过Object.defineProperty, 我们则可以1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//这里只是简单设置下translateX的属性,其他如scale等属性可自己去尝试
Object.defineProperty(dom, 'translateX', {
set: function(value) {
var transformText = 'translateX(' + value + 'px)';
dom.style.webkitTransform = transformText;
dom.style.transform = transformText;
}
}
//这样再后面调用的时候, 十分简单
dom.translateX = 10;
dom.translateX = -10;
//甚至可以拓展设置如scale, originX, translateZ,等各个属性,达到下面的效果
dom.scale = 1.5; //放大1.5倍
dom.originX = 5; //设置中心点X
总结
主要就是学习了Object.defineProperty
这个新的特性。先前知道这个,但是没有仔细去学习,最近在了解vue
的时候,忽然感觉这个要好好学下了。因为这些MVVM
大都是通过这个新特性来实现数据绑定的。当然angular
不是的,angular
是通过脏数据。下面推荐和感谢几篇文章: