vue2响应式:核心代码是使用Object.defineProperty()来劫持对象中每一个属性的set和get方法。
先来说说Object.defineProperty,称之为对象的属性描述符,可以设置对象的属性是否可以删除(Configurable)、是否可以枚举(Enumerable)、是否可以修改(Writable)、返回值(value)、set(重要,修改触发的回调函数)、get(重要,获取触发的回调函数)
举例子:
var obj = {
name: '小明',
age: 18
}
Object.defineProperty(obj, "name", {
set(newValue) {
console.log('调用set方法', newValue);
name = newValue
},
get() {
console.log('调用get方法');
return name
}
})
Object.defineProperty(obj, "age", {
configurable: false,
enumerable: true
})
// 调用set
obj.name = 123
// 调用get
console.log(obj.name)
// 尝试删除age
delete obj.age
console.log(obj.age);
这是Object.defineProperty的基本用法,也是Vue2的响应式核心,但是,这么做是有缺点的。
缺点一:如果对象中属性过多,那么需要给每个属性都绑定defineProperty,十分损耗效率。
缺点二:只能监听对象属性的修改、读取,如果涉及删除、增加就无法响应式刷新页面了(只能通过Vue.set)
this.$set(this.obj, key, value)
Vue.set(this.obj, key, value)
this.$delete(this.obj, key)
Vue.delete(this.obj, key)
缺点三:通过下标、length修改数组也不会响应式刷新页面,可以直接重新赋值,或者使用filter、map、concat、slice等生成新数组对其赋值
this.$set(this.arr, index, value)
Vue.set(this.arr, index, value)
this.$delete(this.arr, index)
Vue.delete(this.arr, index)
还可以使用操作数组的函数
splice(),push(), pop(), shift(), unshift(), sort(), reverse()
好了,缺点就这些,下面我们来浅聊一下Vue2响应式是如何实现的。
顺序是:数据劫持(数据代理)、依赖收集 、订阅发布
首先observe函数会将传入的数据进行递归处理成一个个值,然后给他们每个都重写get和set方法。
然后Dep类作为收集者,需要保存收集到的依赖,在observe函数执行同时创建Dep实例,也就是每个属性对应一个Dep实例,在Vue解析到模板中的{{}}时,会创建Watcher实例,也就是观察者,可以收集数据的所有依赖,通过调用dep.depend,存放到对应Dep实例的sub数组中。
最后当数据发生变化,就会触发Dep类中的dep.notify方法,通知所有依赖进行更新。
和Vue2不同的是,它的核心是es6的Proxy结合Reflect实现的,使用代理,可以不直接操作对象,这样就可以监听到所有对象的增删改查,同时也提升了效率。
还是先看看核心Proxy:
es6新增了Proxy类,顾名思义,就是代理的作用。如果我们想监听一个对象,就可以先创建代理对象,之后对对象的所有操作,都由代理对象完成,可以监听我们的所有操作。
Reflect是es6新增的另一个API,是一个对象,顾名思义是反射。提供了很多操作Object的方法,增加Reflect的原因是最开始ECMA设计时,没有考虑到未来会给Object设置很多方法,造成臃肿的现象,而且有些操作放到object里面并不合适,所以新增了这个API。另外在使用Proxy时,可以做到不操作原对象
二者结合:
var obj = {
name: 'xiaoming',
age: 18
}
// 结合好处一:不再直接操作对象
// 结合好处二:返回一个Boolean类型,可以进行判断
var objProxy = new Proxy(obj, {
set: function(target, key, newValue) {
const isSuccess = Reflect.set(target,key,newValue)
if(!isSuccess){
throw new Error(`set ${key} failure`)
}
}
})
Vue3的响应式是将Vue2的Object.defineProperty替换成new Proxy+Reflect,其他思想大致是一样的。