本文相当于笔记,最近在看深入浅出vue.js的记录,需要配合书一起看,如果你也在看这本书可以一起看看这篇文章,不然会看不懂。
vue 在运行是内部的状态不断改变,页面不停的重新渲染,想要达到这样的变化侦测效果第一步就要解决依赖收集的部分。
依赖收集就是记录哪些数据用到了哪些地方,这样在变化的时候就能去精准的通知其去改变。
大体思路就是用Object.defineProperty 设置get、set监听到数据的读取和改变,在get中收集依赖,在set通知收集到的依赖去改变。
Dep收集依赖的地方,可以叫做存储位置
封装一个Dep类管理依赖收集的地方(depend依赖 的缩写,为每个属性都new 一个Dep类,每个属性都有自己对于的依赖收集的地方)
Dep类中 主要的结构
this.subs = [] (subscribe 订阅 缩写)这个数组就是用来收集具体哪里调用的该属性,
depend()函数 push进subs中 就是添加依赖的,判断window.target是否有值 如果有假加入到subs中(window.target)是全局变量
notify()函数 用于更新所有的依赖 里面就是一个循环调用subs的update方法
defineReactive()收集和触发依赖动作
defineReactive() 是个方法 里面封装了 Object.defineProperty 统一设置了get,set为每一个属性,get中收集, set中触发更新
先是调用了observe() 传递进入val
let childOb = observe(val)
这个函数内部判断了是否是对象如果是不是对象就返回空(数组也是对象哦,typeof)
不是对象,有个过滤的机制避免重复侦测,根据__ob__
过滤后调用真正的Observe 该类主要功能是循环对象递归调用defineReactive使其每个属性都具备get,set也就是有收集和触发的功能,Observe是个类 childOb拿到的是他的实例
Observe稍后介绍继续说defineReactive
实例化了let dep = new Dep()
在get中调用了 dep.depend() 以及 childOb.dep.depend()
这么写是为了统一在get中进行依赖收集 childOb 是用来处理 数组的依赖收集,
数组和对象的依赖收集都是一样的 list:[] 要读取这个数组 就要先找到list 肯定会触发list的get
数组的操作方法,push shift 这些改变数组的方法不能触发getter/setter,数组用的是重写覆盖原始的Array原型方法这样就知道了用户操作了那些方法,以这种形式来收集和触发依赖,这也被叫做拦截器
set中调用 dep.notify()
Watcher类 数据变化通知的地方 可以看做vue中的$watch
get()方法 这里很厉害,把this 赋值给全局变量 window.target 然后再去读取对应的数据,这样就触发了上一步写的 defineReactive 中设置好的get get中调用dep.depend(), depend把window.target 加入到 subs数组中,用于以后更改时update,这样就顺利的把前后链接了起来
update()方法 上面说的 notify会循环调用数据的更新就是这里的update方法,该方法里是$watch时写的回调用call保持上下文关系执行,
所以每次声明watch的时候会在constructor中调用this.get(),就可以主动的把this添加到dep中,很神奇。
Observer()递归侦测所有属性
主要用于循环数据下面的所有属性让每个属性都有收集和通知的能力,这其中也处理了数组的原型覆盖,
实例了dep 用于数据依赖收集的
给每个数据加上了一个不可枚举的__ob__
标识,这个标识就是this,主要是两个作用,
1、用于过滤判断当前属性是否已经被侦测变化,
2、数组是侦测变化是改写原型上的方法,原型上从this直接拿到__ob__
就是Observer的实例,实例上有dep 当触发了改写的push后就能去调用dep 中的notify()
这里很巧妙,收集是在defineReactive的get中 调用是在 原型的方法中,Observer相当于是中间的位置,上下都能拿到
如果是对象就循环递归调用 defineReactive
判断如果是数组重新赋值他的__proto__
, __proto__
指向的是实例他的原型,也就是一个对象,所以生成一个对象就好,这个对象以Array.prototype作为基础在上面改写,要保证数据传进来调用 Array.prototype 同样的方法做处理,返回同样的返回值,然后在这其中加入收集和通知就ok了,
arrayMethods = Object,creare(Array.prototype);
只改写了push pop shift unshift splice sort reverse 一共七个可以改变自身的方法,
用defineProperty改写,在value中 用 apply保持上下文并且把 ...args传入进去 让其拥有原本的功能并且返回一样的值,
额外的操作 let ob = this.__ob __;这个是Observer的实例,里面有dep,调用dep.notify()让其更新,
如果使用的是push unshift splice 这三,他们是可以在数组中新增元素的,新增的元素要保持可以侦测变化,同样是调用ob上的方法,observeArray
observeArray 循环传递进来的数组,调用observe,又回来了,---是对象就循环递归调用 defineReactive ,是数组覆盖__proto__
以上这些大致就是变化侦测的基本实现
了解了原理也就知道了为什么vue中一些数据的变化是不能更新的。
this.list[0] = 1;
observe 数组中循环时对象通过,否则不会赋予收集和通知的功能。数组中是个值是不能监测变化的是对象可以。
list.length = 0
vue在处理是根本没有这个回调,所以这样使用时都不知道数组变化了。
delete this.obj.name这样的操作是不能追踪变化的,因为defineProperty 的set 只能知道数据是否被更改不能知道数据是否被删除或是新增。不过也有配套的api解决这个问题 vue.delete