手写Vue源码之 依赖收集
首先明确为什么药依赖收集?是为了当数据的属性发生了更改之后可以通知那些曾经使用了数据的地方。
对象依赖收集的过程:初始化的时候,会给每一个数据定义响应式,同时会给每一个属性一个new Dep(),每一个dep 有一个 唯一标识id,这个Dep就是用来收集依赖的,
然后当页面取值的时候会调用get方法,这个方法会判断当前渲染watcher存在不存在,如果存在就调用dep.depend()方法,
通过这个方法会执行watcher的addDep方法,addDep会对当前要收集的watcher进行过滤,判断当前属性的watcher在不在dep中,如果存在就不会再存入dep,如果不存在就存入dep,
当改写一个属性值的时候,会走set方法,set方法会调用dep.notify(),notify()会调用watcher的updata方法,updata()会调用get方法取值。get方法会收集依赖。
import {observe} from './index'
import {observeArray} from './array'
import Dep from './dep'
export function defineReactive (data,key,value) { //定义响应式的变化
observe(value) //如果传入的属性还是一个对象,就递归观察,例如school:{name:'zf',age: 18} 需要给name和age也增加get和set
//拦截 分别给每一个属性增加一个get和set
let dep = new Dep() //dep收集依赖,收集的时watcher
Object.defineProperty(data,key,{
//依赖收集
get(){
if(Dep.target) {
//我们希望存入的watcher不能重复,如果重复会造成更新时多次渲染
// dep.addSub(Dep.target)
//换个写法
dep.depend(); //让dep中可以存入watcher,也可以让watcher中存放dep,实现以多对多的关系
}
return value
},
//通知依赖更新
set(newValue){
if(newValue === value) return
observe(newValue)
value = newValue
dep.notify()
}
})
}
class Observe {
constructor (data) { //data就是刚才定义得vm_data
//将数据使用defineProperty重新定义
//判断是否是数组
if(Array.isArray(data)){ //是数组需要重写数组方法
//只能拦截数组的方法,数组理的每一项还需要去观测一下
data._proto_ = arrayMethods //让数组通过链来查找我没自己改写的原型
//如果数组里面放的是对象,会观测
observeArray(data)
}else { //对象
this.walk(data)
}
}
walk(data) {
let keys = Object.keys(data)
for (let i = 0; i < keys.length; i++) {
let key = keys[i]; //data里面得key
let value = data[keys[i]] //key对应的值
defineReactive(data,key,value) //给每一个值定义成响应式
}
}
}
export default Observe
//export default导出的是一个具体的值
//export 导出的是一个变量
dep.js
let id =0;
class Dep {
constructor(){
this.id = id++
this.subs = []
}
addSub(watcher){ //订阅就是将调用addSub时出传入的内容保存到数组中
this.subs.push(watcher)
}
notify(){ //发布 让watcher依次执行
this.subs.forEach(watcher =>{
watcher.update()
})
}
depend(){
if(Dep.target){ //渲染watcher
//wathcer中存入dep,互相记忆
Dep.target.addDep(this) //调用watcher的addDep方法
}
}
}
//用来保存当前的watcher
let stack = []
export function pushTarget(watcher){
Dep.target = watcher
stack.push(watcher)
}
export function popTarget(){
stack.pop()
Dep.target = stack[stack.length-1]
}
export default Dep //用来收集依赖,收集的是一个个的watcher
watcher.js
import {pushTarget,popTarget} from './dep' let id = 0 //watcher的唯一标识 class Watcher{ //每次产生一个watcher都要有一个唯一标识 constructor(vm,exprOrfn,cb = {},opts = {}){ //cb = {},opts = {} 默认值 this.vm = vm //当前组件实例 this.exprOrfn = exprOrfn //判断用户传入的是表达式还是函数msg if(typeof exprOrfn === 'function') { this.getter = exprOrfn //getter就是传入的第二个参数 } this.cb = cb //用户传入的回调函数 vm.$watch('msg',cb) this.opts = {} //其他参数 this.id = id++ this.deps = [] this.depId = new Set() this.get() //每次创建一个watcher就执行自己的get方法 } get() { pushTarget(this) //渲染watcher保存起来 Dep.target = watcher this.getter() //让当前传入的函数updataComponent执行,从实例上取值渲染页面 会调用Object.defineProperty 的get方法 popTarget() //取完值后 清空当前watcher } addDep(dep){ //过滤,不要重复存入watcher到dep中,也不要重复存dep。 let id = dep.id if(!this.depId.has(id)) { this.depId.add(id) this.deps.push(dep) //将dep存入watcher dep.addSub(this) //将watcher存入dep } } updata(){ this.get() } run(){ this.get() } } //渲染watcher、计算属性、vm.$watch 都属于Watcher实例 export default Watcher//1.默认会创建一个渲染watcher // 2.依赖收集 pushTarget(this) Dep.target = watcher // this.getter() //更新视图 取值会调用get方法 get方法会给当前属性加一个dep 收集依赖dep.addSub(Dep.target) //3.当用户修改了属性,就会调用set方法 dep.notify() watcher.update() 触发get方法,再进行依赖收集
不积跬步无以至千里