vue2
那么Vue中它是如何做到响应式的呢?
想要完成此过程,我们需要做如下事情:
1)侦测对象数据的变化。
2)收集视图依赖了哪些数据。
3)数据变化时,自动通知和数据相关联的视图页面,并对视图进行更新。
数据双向绑定及响应式:
数据在初始化的时候就将数据绑定成响应式,就是通过object.definePropertyo的set 和get方法,get 收集依赖,set 触发界面更新
function Vue(options) { //获取数据
this._data = options.data;
observe(this._data);
}
function observe(value) { //遍历data中的每一项
//先不考虑value为数组的复杂状况
if (!value || typeof value !== 'object') return;
Object.keys(value).forEach(function(propertyName) {
defineReactive(value, propertyName, value[propertyName])
})
}
function defineReactive(obj, key, val) { //监听每一个data的数据变化
var dep = new Dep();//实例化一个订阅者
console.log('defineReactive==parameter', obj, key, val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
set: function(newVal) {
console.log('newVal', newVal);
if (val == newVal) return;
dep.notify(); //若是新修改的值和原来的值不一样就从新渲染页面
},
get: function() {
dep.addSub(Dep.target); //把watcher实例添加到依赖中
return val;
// 收集依赖
}
})
}
依赖收集器Dep: Dep的主要做用是存放Watcher观察者对象,Dep用于依赖收集和派发更新,它收集所有的观察者,当有数据变动的时候,它会把消息通知到所有的观察者,同时它也调用Watcher实列中的update方法,用于派发更新。
Dep有个subs的数组,用于收集观察者
addSub方法:用于将所有的观察者添加到subs数组中
Dep → Watcher这一步,还需要通知变化,所以需要一个notify方法,内部遍历每一个watcher观察者,去更新发生变化的部分
观察者Watcher:
观察者主要是通过比较新值和旧值的变化,然后决定是否调用回调函数callback更新视图
function Dep() {
//添加观察者watcher
this.subs = [];
this.addSub = function(sub) {
this.subs.push(sub);
console.log("sub==", sub);
}
//通知Watcher更新视图
this.notify = function() {
this.subs.forEach(function(s) {
s.update()
});
}
}
Dep.target = null;
2.观察者Watcher:主要做用是监听数据变化更新视图
function Watcher() {
Dep.target = this;
//在new 一个Watcher 对象时将该对象赋值给Dep.target,在get 中会用到
this.update = function() {
console.log('updata view===')
}
}
vue在observe中注册并调用get,在get中,将Watcher实例经过addSub方法添加到dep的subs数组中,这完成了依赖添加的过程;
当数据变化时,调用Object.defineProperty中的set方法,判断数据是否发生改变,若是改变了,就调用dep中的notify方法通知Watcher,Watcher知道数据变化更新数据.
能够看到array的原理和object相似,可是由于大多数状况下都不是set,而是一些操做数组的方法(push、pull、shift…),因此咱们加了一层拦截器去通知Dep。双向绑定
Object.defineProperty 可以做到对数组的监听,它是支持数组的,但是为什么没用它呢?
如果你知道数组的长度,理论上是可以预先给所有的索引设置 getter/setter 的。但是一来很多场景下你不知道数组的长度,二来,如果是很大的数组,预先加 getter/setter 性能负担较大。总而言之就是理论上 vue 是可以这样做,但是出于性能考虑没这样做,而是用了一种数组变异办法来触发视图更新。
import { def } from '../util/index'
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
/**
* Intercept mutating methods and emit events
*/
;[
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
// 调用数组真正的方法
const result = original.apply(this, args)
// __ob__代表数据是否被observe了
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// inserted表示有数据插入 对新数据进行observe
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})
Vue2 中的变化侦测实现对 Object 及 Array 分别进行了不同的处理,Objcet 使用了Object.defineProperty API ,Array 使用了拦截器对 Array 原型上的能够改变数据的方法进行拦截。虽然也实现了数据的变化侦测,但存在很多局限 ,比如对象新增属性无法被侦测,以及通过数组下边修改数组内容,也因此在 Vue2 中经常会使用到 $set 这个方法对数据修改,以保证依赖更新。
Vue3 中使用了 es6 的 Proxy API 对数据代理,没有像 Vue2 中对原数据进行修改,只是加了代理包装,因此首先性能上会有所改善。其次解决了 Vue2 中变化侦测的局限性,可以不使用 $set 新增的对象属性及通过下标修改数组都能被侦测到。