方法浅析"/>
JS Object.defineProperty()方法浅析
定义
Object.defineProperty()方法会修改或者定义一个属性,并返回这个对象。
语法
Object.defineProperty(obj, prop, descriptor)
obj: 将要被修改/定义的对象。
prop:要修改/定义的属性名称
descriptor: 将被定义或者修改的属性描述符
属性描述符
对象中目前存在的属性描述符有数据描述符和存取描述符两种。数据描述符是一个具有值的属性,可能可写也可能不可写;而存取描述符则是由getter-setter函数对所描述的属性。描述符必须是两者其一,不能同时为两者。
对于两种属性符来说,它们都有以下可选键值:
configurable: 当且仅当该键值为true时,该描述符所描述的属性可变、可删除。
enumerable:当且仅当该键值为true时,该描述符可枚举。默认false。
两种属性符分别所特有的键值如下:
-
数据描述符
value: 该属性对应的值。
writable: 当且仅当该值为true时,该属性才能被赋值运算符改变。默认为false。
-
存取描述符
get: 一个给属性提供getter的方法。当访问该属性时,getter方法会被执行,方法执行时只会传入this对象。
默认为undefined。
set: 提供setter。默认undefined
let obj = {}
let descriptor = Object.create(null); // 避免继承属性
descriptor.value = 'static'
console.log(Object.getOwnPropertyDescriptor(descriptor, 'value'));
// {value: "static", writable: true, enumerable: true, configurable: true}
Object.defineProperty(obj, 'type', descriptor);
console.log(Object.getOwnPropertyDescriptor(obj, 'type'));
// {value: "static", writable: false, enumerable: false, configurable: false}
可以看到,在使用赋值运算符进行赋值时,其属性的几个描述键值都为true;而如果使用Object.defineProperty()则这些键值都为false。这也就是为什么默认情况下通过该方法定义/修改的属性都不可变。
上面的第6行与下面代码效果相同:
Object.defineProperty(obj, 'type', {value: "static", writable: false, enumerable: false, configurable: false
});
当然你不能在一个描述符中同时使用两种描述符所特有的键值。考虑以下代码:
Object.defineProperty(obj, 'type', {value: "static", writable: false, enumerable: false, configurable: false,set: (value) => {},get: () => {}
});// TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute
重点介绍:存取描述符
之所以要重点介绍存取描述符,是因为这就是Vue框架实现双向绑定所依赖的方法。这部分也会对该双绑机制进行分析。
-
存取描述符功能
如上文所说,存取描述符特有键值get和set的值是函数对象。在读取该属性时会调用getter()函数,而写入该属性时会调用setter()函数。比如说下面代码:
function GLHF() {let hello = "HF!";Object.defineProperty(this, 'hello', {get: () => {console.log("GL!");return hello;},set: (value) => {console.log("HF!");hello = value;}}) }let obj = new GLHF(); console.log(obj.hello);// GL! HF! obj.hello = 'GG!'// HF! console.log(obj.hello);// GL! GG!
可以看到,在引用变量时触发了getter(),使用赋值运算符时触发了setter()。并且这里使用了闭包的模式,getter()和setter()都只使用了一个变量。
-
典型应用:Vue的双向绑定机制
目前Vue2.0中所采用的双向绑定机制是数据劫持配合发布者-订阅者模式。这一部分只会重点分析Vue的数据劫持机制。
数据劫持有这么几种方法实现:本文分析的函数Object.defineProperty()、ES6中新推出的Proxy对象和已经废弃的Object.observe()。
数据劫持的效果就是使用相关方法让被劫持的对象属性数据在进行操作(读/写)的时候执行自定义的逻辑。比如上一个代码块就在设置值的时候打出了’GG!’。
而双向绑定就是通过数据劫持,在更新JS对象属性的同时,也将更新通过自定义操作体现在视图上。例如下面代码(伪码):
const obj = {}; Object.defineProperty(obj, 'text', {get: () => {console.log('get value')},set: newVal => {console.log('set value:' + newVal);document.getElementById('input').value = newVal;document.getElementById('span').innerHTML = newVal;} })const input = document.getElementById('input'); input.addEventListener('blur',e => {obj.text = e.target.value; })
这样,当input失焦时,它的值就会被传到obj的text属性中并触发setter(),从而更新span显示的值。
缺陷
可以看一下该函数对于数组属性的监听情况:
const obj = {}
let value = 1;
Object.defineProperty(obj, 'name', {set: newVal => {console.log(newVal);value = newVal;},get: () => {return value;}
});obj.name = [];// []
obj.name[0] = 1; // 无输出 未触发setter
console.log(obj.name); // [1]
obj.name.push(2);// 无输出,未触发getter
可以看到对于属性值为数组的属性defineProperty是无法劫持的。如果没有特别的逻辑的话,也无法对属性值为对象内部的属性进行劫持。
参考及部分代码来源
更多推荐
JS Object.defineProperty()方法浅析
发布评论