已经用Vue好几年了,也知道Vue 2.x的底层响应式用的是Object.defineProperty()这个方法来实现的,可一直没有去深入了解过这个方法,今天就让我们一起去探个究竟。
一、对象属性的增删改查
在开始之前,我们先简单了解下如何对对象进行增、删、改、查等操作。
为了更加直观,我们直接上个简单例子。
首先创建一个空对象data
。
let data = {};
然后我们可以对其进行增删改查操作。
1. 新增属性
data.text = '想学习更多前端知识,请关注公众号:前端微站';
2. 删除属性
delete data.text;
3. 修改属性
data.text = '想学习更多前端知识,请关注公众号:前端微站'; // 新增属性data.text = '关注公众号前端微站,学习更多前端知识'; // 修改属性
4. 查询属性
查询有两种,一种是通过属性名(key)查询属性值(value), 另一种则正好相反,是通过属性值(value)来查询属性名(key),不过这两种查询都可以通过for...in
来遍历查询。
// 先给对象再新增一个属性data.name = '前端王睿';// 通过 key 查询 valuefor(let key in data){ if(key === 'name'){ console.log(data[key]); // 前端王睿 }}// 通过 value 查询 keyfor(let key in data){ if(data[key] === '前端王睿'){ console.log(key); // name }}
二、对象的属性描述符
上面所讲到的对象操作应该是司空见惯、众所周知的了,可是,你说好不容易找了个对象,说让你改就改,让你删就删,多没面子!有没有什么办法,我创建好了对象之后,就不让再随意改动呢,至少要我自己能够配置呀!
当然有!这时Object.defineProperty()
就派上用场了!我们可以通过Object.defineProperty()
对对象属性进行描述,也就是告诉我们哪个属性是不能改的,哪个属性是不能删的,等等。这里就要讲到对象的属性描述符,而属性描述符又分为 数据描述符 和 存取描述符。数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。存取描述符是由 getter 函数和 setter 函数所描述的属性。
1. 数据描述符
我们同样按照增、删、改、查的顺序对其进行一一列举,可以看出通过Object.defineProperty()
方式新增的对象属性,默认都是不可被删除、修改和枚举的。
① value
表示该对象属性的值,默认值为undefined
。例如:
let data = {};Object.defineProperty(data,'text',{ value: '想学习更多前端知识,请关注公众号:前端微站'});console.log(data); // {text: '想学习更多前端知识,请关注公众号:前端微站'}
这看起来跟直接通过data.text = '想学习更多前端知识,请关注公众号:前端微站'
新增对象属性的效果一样,具体区别请接着往下看。
② configurable
默认值为false
,表示该对象属性不能被删除,只有为true
时才可删除。例如:
let data = {};Object.defineProperty(data,'text',{ value: '想学习更多前端知识,请关注公众号:前端微站'});delete data.text; // 删除失败console.log(data.text); // 想学习更多前端知识,请关注公众号:前端微站
删除没效果嘛!这时只需设置configurable
值为true
即可继续愉快地删除了。
let data = {};Object.defineProperty(data,'text',{ value: '想学习更多前端知识,请关注公众号:前端微站', configurable: true});delete data.text; // 删除成功console.log(data.text); // undefined
③ writable
默认值为false
,表示该对象属性不能被修改,只有为true
时才可修改。例如:
let data = {};Object.defineProperty(data,'text',{ value: '想学习更多前端知识,请关注公众号:前端微站'});data.text = '关注公众号前端微站,学习更多前端知识'; // 修改失败console.log(data.text); // 想学习更多前端知识,请关注公众号:前端微站
修改失败了!这时给它设置writable
值为true
即可继续愉快地修改了。
let data = {};Object.defineProperty(data,'text',{ value: '想学习更多前端知识,请关注公众号:前端微站', writable: true});data.text = '关注公众号前端微站,学习更多前端知识'; // 修改成功console.log(data.text); // 关注公众号前端微站,学习更多前端知识
④ enumerable
默认值为false
,表示该对象属性不能被枚举,只有为true
时才可枚举。例如:
let data = { text: '想学习更多前端知识,请关注公众号:前端微站'};Object.defineProperty(data,'name',{ value: '前端王睿'});console.log(data); // {text: "想学习更多前端知识,请关注公众号:前端微站", name: "前端王睿"}for(let key in data){ console.log(data[key]); // 想学习更多前端知识,请关注公众号:前端微站}
我们发现,data
对象中虽然已经有两个属性,可我们发现最终却只能遍历出text
这一个属性。这时只需给name
属性设置enumerable
值为true
即可愉快地枚举出来了。
let data = { text: '想学习更多前端知识,请关注公众号:前端微站'};Object.defineProperty(data,'name',{ value: '前端王睿', enumerable: true // 想学习更多前端知识,请关注公众号:前端微站});console.log(data); // {text: "想学习更多前端知识,请关注公众号:前端微站", name: "前端王睿"}for(let key in data){ console.log(data[key]); // 想学习更多前端知识,请关注公众号:前端微站 // 前端王睿}
2. 存取描述符
以下这两个函数就是Vue中用于进行数据监听的Object.defineProperty()
中属性描述符的两个核心方法。
① get
属性的 getter 函数,默认值为undefined
。当访问该属性时,会调用此函数,返回值会被用作该属性的值。例如:
let data = { text: '想学习更多前端知识,请关注公众号:前端微站'};Object.defineProperty(data,'text',{ get(){ return '关注公众号前端微站,学习更多前端知识' }});console.log(data); // {text: "关注公众号前端微站,学习更多前端知识"}
可以看到,此时data
其实已经被我们修改了!
② set
属性的 setter 函数,默认值为undefined
。当属性值被修改时,会调用此函数,该方法接受一个参数,也就是被赋予的新值。例如:
let data = { text: '想学习更多前端知识,请关注公众号:前端微站'};Object.defineProperty(data,'text',{ set(value){ console.log(value); // 关注公众号前端微站,学习更多前端知识 }});data.text = '关注公众号前端微站,学习更多前端知识';
*
注意:value
和writable
不能与存取描述符(get
和set
)同时存在,不然会报错!这是因为两者之间可能会存在互斥关系,例如:value
值与get
返回值不同,writable
为false
而使用get
却可改变对象属性的值,等等。
三、使用Object.defineProperty()
写个简单的响应式渲染
let oText = document.getElementById('text'), oInput = document.getElementById('input');let data = { text: ''};Object.defineProperty(data,'text',{ set(value){ oText.innerHTML = value; // 当修改data.text值时,自动更改oText中的文字内容 }});oInput.value = data.text = '前端微站';oInput.addEventListener('keyup', function () { data.text = this.value;});
重点总结
①
Object.defineProperty()
中的 数据描述符 可以用来禁止对象属性的 删除、修改和枚举 操作②
Object.defineProperty()
中的 存取描述符 可以用来对 对象赋值 和 获取属性值 进行拦截操作
结束语
看我在这啰嗦了半天,可最终能不能掌握Object.defineProperty()
的用法还是应该在实践中多总结,而不是简单地看完了就结束了。最后给大家安排个作业,利用Object.defineProperty()
的存取描述符,实现一个超简易的Vue,具体功能就是实现上面这个简单的响应式渲染功能。