Object.defineProperty()方法被许多现代前端框架 (如. JS,React.JS) 用于数据双向绑定的实现, 当我们在框架 Model 层设置 data 时, 框架将会通过 Object.defineProperty()方法来绑定所有数据, 并在数据变化的同时修改虚拟节点, 最终修改页面的 Dom 结构.
一, 语法
Object.defineProperty(obj, prop, descriptor)
obj: 必须, 被定义或修改属性的对象;
prop: 必须, 要定义或修改的属性名称;
descriptor: 必须, 属性的描述描述符
返回值
函数将返回传递给他的 obj 对象本身.
二, 描述符 (descriptor) 说明
该方法允许开发者精确的对对象属性的定义和修改. 通过正常赋值进行属性添加而构建的属性会被枚举器方法 (如 for...in 循环或 Object.keys 方法) 获取, 从而导致属性值被外部方法改变或删除. 而 Object.defineProperty()可以避免以上描述的情况, 默认的, 通过 Object.defineProperty()添加的属性是默认不可改变的.
属性描述参数 (descriptor) 主要由两部分构成: 数据描述符 (data descriptor) 和访问器描述符(accessor descriptor).
数据描述符就是一个包含属性的值, 并说明这个值可读或不可读的对象; 访问器描述符就是包含该属性的一对 getter-setter 方法的对象. 一个完整的属性描述 (descriptor) 必须是这两者之一, 并且不可以两者都有.
数据描述符和访问器描述符各自都是对象, 有以下键值对:
configurable(必须) | 仅当设置的属性的描述符需要被修改或需要通过 delete 来删除该属性时,configurable 属性设置为 true。默认为 false。 |
enumerable(必须) | 仅当设置的属性需要被枚举器(如 for…in)访问时设置为 true。默认为 false。 |
value(可选) | 设置属性的值,可以是任何 JavaScript 值类型(number,object,function 等类型)。默认为 undefined。 |
writable(可选) | 仅当属性的值可以被赋值操作修改时设置为 true。默认为 false。 |
访问器描述符可以包含以下可选键值对:
get | 属性的 getter 方法,若属性没有 getter 方法则为 undefined。该方法的返回为属性的值。默认为 undefined |
set | 属性的 setter 方法,若属性没有 setter 方法则为 undefined。该方法接收唯一的参数,作为属性的新值。默认为 undefined。 |
请牢记, 这些描述符的属性并不是必须的, 从原型链继承而来的属性也可填充. 为了保证这些描述符属性被填充为默认值, 你可能会使用形如预先冻结 Object.prototype, 明确设置每个描述符属性的值, 使用 Object.create(null)来获取空对象等方式.
- // using __proto__
- var obj = {
- };
- var descriptor = Object.create(null); // no inherited properties
- // 所有描述符的属性被设置为默认值
- descriptor.value = 'static';
- Object.defineProperty(obj, 'key', descriptor);
- // 明确设置每个描述符的属性
- Object.defineProperty(obj, 'key', {
- enumerable: false,
- configurable: false,
- writable: false,
- value: 'static'
- });
- // 重用同一个对象作为描述符
- function withValue(value) {
- var d = withValue.d || (
- withValue.d = {
- enumerable: false,
- writable: false,
- configurable: false,
- value: null
- }
- );
- d.value = value;
- return d;
- }
- Object.defineProperty(obj, 'key', withValue('static'));
- // 如果 Object.freeze 方法可用, 则使用它来防止对对象属性的修改
- (Object.freeze || Object)(Object.prototype);
使用示例
创建一个属性
如果当前对象不存在我们要设置的属性, Object.defineProperty()会根据方法设置为对象创建一个新的属性. 如果描述符参数缺失, 则会被设置为默认值. 所有布尔型描述符属性会被默认设置为 false. 而 value,get,set 会被默认设置为 undefined. 一个未设置 get/set/value/writable 的属性被称为一个 "原生属性 (generic)", 并且他的描述符(descriptor) 会被 "归类" 为一个数据描述符(data descriptor).
- var o = {
- }; // 创建一个对象
- // 使用数据描述符来为对象添加属性
- Object.defineProperty(o, 'a', {
- value: 37,
- writable: true,
- enumerable: true,
- configurable: true
- });
- // 属性 "a" 被设置到对象 o 上, 并且值为 37
- // 使用访问器描述符来为对象添加属性
- var bValue = 38;
- Object.defineProperty(o, 'b', {
- get: function() {
- return bValue;
- },
- set: function(newValue) {
- bValue = newValue;
- },
- enumerable: true,
- configurable: true
- });
- o.b; // 38
- // 属性 "b" 被设置到对象 o 上, 并且值为 38.
- // 现在 o.b 的值指向 bValue 变量, 除非 o.b 被重新定义
- // 你不能尝试混合数据, 访问器两种描述符
- Object.defineProperty(o, 'conflict', {
- value: 0x9f91102,
- get: function() {
- return 0xdeadbeef;
- }
- });
- // 抛出一个类型错误: value appears only in data descriptors, get appears only in accessor descriptors(value 只出现在数据描述符中, get 只出现在访问器描述符中)
修改一个属性
当某个属性已经存在了, Object.defineProperty()会根据对象的属性配置 (configuration) 和新设置的值来尝试修改该属性. 如果该属性的 configurable 被设置为 false, 则该属性无法被修改(这种情况下有个特殊情况: 如果之前的 writable 设置为 true, 则我们仍可以将 writable 设置为 false, 一旦这么做之后, 任何描述符属性将变得不可设置). 如果属性的 configurable 设置为 false, 则我们无法将属性的描述符在数据描述符和访问器描述符之间转换.
如果新设置的属性和该属性不同, 并且该属性的 configurable 被设置为 false, 则一个类型错误 (TypeError) 会被抛出(除了上一段文字中说的特殊情况). 若新旧属性完全相同, 则什么都不会发生.
可写特性 - writable
当一个属性的 writable 被设置为 false, 这个属性就成为 "不可写的(non-writable)". 该属性不可被重新赋值.
- var obj = {};
- Object.defineProperty(obj,"name",{
- value:"张三",
- writable:false// 当设置为 false 的时候当前对象的属性值不允许被修改
- })
- obj.name="李四"
- console.log(obj.name)// 张三
- var obj = {};
- Object.defineProperty(obj,"name",{
- value:"张三",
- writable:true// 当设置为 true 的时候当前对象的属性值允许被修改
- })
- obj.name="李四"
- console.log(obj.name)// 李四
可枚举特性 - enumerable
属性的 enumerable 值定义对象的属性是否会出现在枚举器 (for...in 循环和 Object.keys()) 中.
- var obj = {name:"张三",age:"李四"}
- Object.defineProperty(obj,"name",{
- enumerable:false// 当设置为 false 的时候对象的属性不可被枚举
- })
- Object.defineProperty(obj,"age",{
- enumerable:false
- })
- console.log(Object.keys(obj))//[]
- var obj = {name:"张三",age:"李四"}
- Object.defineProperty(obj,"name",{
- enumerable:true// 当设置为 true 的时候对象的属性可被枚举
- })
- Object.defineProperty(obj,"age",{
- enumerable:true
- })
- console.log(Object.keys(obj))//["name",age]
可配置特性 - configurable
属性的 configurable 值控制一个对象的属性可否被 delete 删除, 同时也控制该属性描述符的配置可否改变(除了前文所述在 configurable 为 false 时, 若 writable 为 true, 则仍可以进行一次修改将 writable 改变为 false).
- var obj = {};
- Object.defineProperty(obj,"name",{
- value:"张三",
- configurable:false// 当设置为 false 的时候对象的属性不允许被删除
- })
- delete obj.name;
- console.log(obj.name)// 张三
- var obj = {};
- Object.defineProperty(obj,"name",{
- value:"张三",
- configurable:true// 当设置为 true 的时候对象的属性允许被删除
- })
- delete obj.name;
- console.log(obj.name)//undefined
get 和 set
Get: 指读取属性时调用的函数.
Set: 指写入属性时调用的函数
- var obj = {name:"张三"}
- Object.defineProperty(obj,"name",{
- get(){
- console.log("被访问了")// 当被访问的时候会触发 get()方法
- },
- set(newVal){
- console.log("被设置了"+newVal)// 当被设置的时候会触发 set()方法
- }
- })
- obj.name// 输出: 被访问了
- obj.name="李四";// 输出: 被设置了李四
添加属性时的默认值
考虑描述符特性的默认值如何被应用是非常重要的. 正如下面示例所示, 简单的使用 "." 符号来设置一个属性和使用 Object.defineProperty()是有很大区别的.
- var o = {
- };
- o.a = 1;
- // 等同于:
- Object.defineProperty(o, 'a', {
- value: 1,
- writable: true,
- configurable: true,
- enumerable: true
- });
- // 另一方面,
- Object.defineProperty(o, 'a', {
- value: 1
- });
- // 等同于:
- Object.defineProperty(o, 'a', {
- value: 1,
- writable: false,
- configurable: false,
- enumerable: false
- });
configurable 和 writable
一种特殊情况: 当 configurable 为 false 时, 我们唯一仍能改变的属性就是将设置为 true 的 writable 设置为 false.
- var a={
- };
- Object.defineProperty(a,"o",{
- configurable:false,
- value:10,
- writable:true
- });
- console.log(a.o);//10
- a.o=12;// 不报错
- console.log(a.o);//12
- Object.defineProperty(a,"o",{
- configurable:false,
- value:14,
- writable:true
- });
- console.log(a.o);//14
- Object.defineProperty(a,"o",{
- configurable:false,
- value:14,
- writable:false
- });
- a.o=16;// 不报错
- console.log(a.o);//14
- // 报错
- Object.defineProperty(a,"o",{
- configurable:false,
- value:16,
- writable:false
- });
由以上代码可以得出结论, 对于描述符 (descriptor) 为数据描述符 (data descriptor) 的情况:
1. 使用 "." 操作符来设置属性的值永远不会报错, 仅当 writable 为 false 时无效.
2. 只要 writable 为 true, 不论 configurable 是否为 false, 都可以通过 Object.defineProperty()来修改 value 的值.
由此得出结论, 各大浏览器运营商实现的 Object.defineProperty()和标准描述在 configurable 的定义上稍有偏差. 描述符为数据描述符时值的改变与否仅受 writable 的控制.
来源: https://www.cnblogs.com/xuelanying/p/10556571.html