Vue响应式原理之Object.defineProperty()

已经用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 查询 value
for(let key in data){
  if(key === 'name'){
    console.log(data[key]);  // 前端王睿
  }
}

// 通过 value 查询 key
for(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 = '关注公众号前端微站,学习更多前端知识';

*注意:valuewritable不能与存取描述符(getset)同时存在,不然会报错!这是因为两者之间可能会存在互斥关系,例如:value值与get返回值不同,writablefalse而使用get却可改变对象属性的值,等等。

三、使用Object.defineProperty()写个简单的响应式渲染

<input id="input" type="text">
<p id="text"></p>
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,具体功能就是实现上面这个简单的响应式渲染功能。

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):