依赖收集
数组数据的依赖也在getter中收集,而给数组数据添加getter/setter都是在Observer类中完成的,所以我们也应该在Observer类中收集依赖
//源码位置: /src/core/observe/index.js
const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args) //执行原生方法
const ob = this.__ob__
// notify change
ob.dep.notify() //通知视图更新
return result
})
})
export class Observer{
constructor(value) {
this.value = value
this.dep = new Dep() //实例化一个依赖管理器,用来收集数组依赖
//给value新增一个__ob__属性,值为该value的Observer实例
//相当于为value打上标记,表示它已经被转化成响应式了,避免重复操作
def(value,'__ob__',this)
if(Array.isArray(value)){
const augment = hasProto ? protoAugment : copyAugment
augment(value, arrayMethods,arrayKeys)
}else{
this.walk(value)
}
}
walk(obj: Object){
//如果时对象就将对象的key枚举出来进行循环
//让每一个对象的属性都执行一遍可观测的方法
const keys = Object.keys(obj)
fot(let i = 0; i < keys.length; i++){
defineReactive(obj,keys[i])
}
}
}
function defineReactive(obj,key,val){
if(arguments.length == 2){
val = obj[key]
}
let childOb = observe(val)
Object.defineproperty(obj,key,{
enumerable: true,
configurable: true,
get(){
if(childOb){
childOb.dep.depend()
}
return val
}
set(newVal){
if(val === newVal){
return
}
val = newVal;
childOb.dep.notify()
}
})
}
export function observe (value, asRootData){
//在这一步如果value不是object就返回,因为数组在可视化后也会循环里面的元素,并调用这个方法,如果数组内的元素时object那么也要执行可观测的操作
if(!isObject(vlaue) || value insatanceof VNode){
return
}
//下面的代码都是要在value是对象的情况下才会执行
let ob
if(hasOwn(value,'__ob__') && value.__ob__ instanceof Observer) {//判断是否有__ob__
ob = value.__ob__
}else{
ob = new Observer(value)
}
return ob
}
//能力检测:判断__proto__是否可用,因为有的浏览器不支持该属性
export const hasProto = '__proto__' in {}
cosnt arrayKeys = Object.getOwnPropertyNames(arrayMethods) //枚举出对象内所有的key,包括不可枚举的属性
function protoAugment(target,src: Object,keys: any){
target.__proto__ = src
}
function copyAugment(target: Object,src: Object,keys: array<string>){
for(let i = 0, i < keys.length; i++){
cosnt key = keys[i]
def(target,key, src[key])
}
}
步骤:在VUE实例初始化的时候,为data实例化一个Observer类,判断data是什么类型,当然肯定是object,那么就进入walk方法中循环数据,把data中的每一个数据都执行一遍defineReactive方法(将数据变得可观测),在defineReactive中首先要通过observe方法获取val的Observer实例,这个时候如果val没有Observer实例,那么就将val通过Observer类递归执行,如果val是数组,就将Array上所有会改变数组的方法都封装,将方法变成可以通知依赖数组更新的方法,如果是对象就接着调用walk方法,将对象中的每一个元素都执行一边defineReactive方法(将数据变得可观测),并在defineReactive方法中通过observe方法获取他的Observer实例,接着通过Object.defineproperty()方法来设置getter与setter,并在getter与setter中获取和通知Dep(维护依赖数组的类)的更新
深度侦测
上面所讲的Array型数据变化侦测仅仅说的是数组自身变化的侦测,比如给数组新增一个元素或删除一个元素,而在VUE中,不论是Object型数据还是Array型数据所实现的数据侦测都是深度侦测,所谓深度侦测就是不但要侦测数据自身的变化,还要侦测数据中所有子数据的变化。举个例子:
let arr = [{
name: 'NLRX',
age: '18'
}]
数组中包含了一个对象,如果对象的某个属性发生了变化也应该被侦测到,这就是深度侦测。
exprot class Observer{
value: any;
dep: Dep;
constructor(value:any){
this.value = value;
this.dep = new Dep()
def(value, '__ob__',this)
if(Array.isArray(value)){
const augment = hasProto ? protoAugment : copyAugment;
augment(value,arrayMethods,arrayKeys)
this.observeArray(value)
}else{
this.walk(value)
}
}
observeArray(item: Array<any>){
for(let i = 0; i < item.length; i++){
observe(item[i])
}
}
}
export function observe(value,asRootData){
if(!isObject(value) || value instanceof VNode){
return
}
let ob
if(hasOwn(value,'__ob__') && value.__ob__ instanceof Observer){
ob = value.__ob__
}else{
ob = new Observer(value)
}
return ob
}
在以上代码中,对于Array型数据,调用了observerArray()方法,该方法内部会遍历数组中的每一个元素,然后通过调用observe函数将每一个元素都转化成可观测的响应式数据。
而对应object方法,我们已经在defineReactive()函数中进行了递归操作
数组新增元素的侦测
对于数组中已有元素我们已经可以将其全部转化成可侦测的响应式数据了,但是如果向数组里新增一个元素的话,我们也需要将新增的这个元素转化成可侦测的响应式数据。
这个实现起来也很容易,我们只需要拿到新增的这个元素,然后调用observe函数将其转化即可。我们知道,可以向数组内新增元素的方法有三个,分别是:push,unshift,splice。我们只需对这三种方法分别处理,拿到新增的元素,再将其转化即可。
methodsToPatch.forEach(function(method){
const original = arrayProto(method)
def(arrayMethods,method,function mutator(...args){
const result = original.apply(this,args)
cosnt ob = this.__ob__
let inserted
switch(method){
case 'push':
case 'unshift':
inserted = args //如果是push或者unshift方法,那么传入参数就是新增元素
break
case 'splice':
inserted = args.slice(2) //如果是splice,那么传入参数列表中下标为2的就是新增的元素
break
}
if(inserted) ob.observeArray(inserted)
ob.dep.notify()
return result
})
})
不足之处
以前我们说过,对于数组变化侦测是通过拦截器实现的,也就是说只要通过数组原型上的方法对数组进行操作就可以侦测到,但是别忘了,我们在日常开发中,还可以通过数组的下标来操作数据。这样的操作方式来修改数组是无法侦测到的。同样VUE也注意到了这个问题,为了解决这一问题,VUE增加了两个全局API:Vue.set和Vue.delete.