vue原理简单实现(三)

书接上文

组件的依赖收集(watcher的收集)  定义一个watcher类

  • 在上一篇中说了组件挂载的一个过程在最后通过调用mountComponent来更新组件当时是直接调用的这个方法
    而在vue中是将这个方法放到watcher类中所以之前的方法应做相应的改动所以watcher是更新的函数
function mountComponent(vm,el){
    let updateComponent = ()=>{
        vm._update(vm._render())
    }
    new Watcher(vm,mountComponent,()=>{},true)//true表明是渲染watcher,每个组件都有一个渲染watcher
}
  • 当属性取值时需要记住这个watcher,稍后数据变化了去执行自己记住的watcher即可
    1. 首先在数据初始化的时候会先给每个属性通过get加上一个带有dep的域这个是通过defineReactive去实现的 可以看到创建了一个dep实例而get中刚好引用了这个变量所以形成了一个闭包,每个data中的变量都会对应一个dep,每个dep有一个唯一的id,这个id可以用来去重
      实现如下:
export function defineReactive(data, key, value) {
    // value 可能也是一个对象
    let childOb = observe(value);
    let dep = new Dep(); // 每次都会给属性创建一个dep  
    Object.defineProperty(data,key,{
        get(){ 
            if(Dep.target){
                dep.depend();
                if(childOb){
                    childOb.dep.depend();
                    if(Array.isArray(value)){
                        dependArray(value);
                    }
                }
            }
            return value;
        },
        set(newValue){
            if(newValue === value) return;
            observe(newValue);
            value =   newValue  ;
            dep.notify();// 通知dep中的watcher去执行
        }
    })
}
  • 其中dep.depend方法用来收集watcher如何收集的呢?
    1. 首先当数据初始化的时候Dep.target是null这时候不会进行收集
    2. 当初次调用data中的变量时会触发变量的get属性,而初次调用是发生在watcher中的即mountComponent方法,mountComponent调用了vm._render方法这个时候会触发一次get,但是,当模板中多次调用同一个data中的属性的时候会调用多次get这时候通过id进行去重,其次为什么要调用popTarget:防止data中定义的变量没有在模板中使用但是调用的情况下会引起模板的更新这样每个变量过来的时候Dep.target为null,就不用收集依赖了
    3. 当执行watcher类的时候会执行一个getter方法方法中先将当前watcher赋值给Dep.target,这时候就可以进入get中的if条件调用dep.depend方法,通过watcher的addDep方法让当前的watcher记住这个dep,
    4. 同时在addDep方法中调用dep的addSub方法,让这个dep记住这个watcher
    5. 当用户设置值的时候,会走set函数,set调用了dep.notify()函数,这个函数用来遍历存放的watcher并执行watcher的update方法来更新视图,
    6. 这里在update中调用了queueWatcher()方法,这个方法用来做异步更新逻辑
      因为用户可能对某个属性做多次更新,所以会产生多个watcer,每次更新都执行一次watcher即从新渲染会浪费性能,所以用queueWathcer方法把watcher都存起来,并进行一次去重等赋值完成,执行一次异步更新就好了,这样就通过一次渲染搞定了所有的变更
    7. 在处理异步更新的时候用到了nextTick,这个方法是个全局的方法,用来处理异步事件其中的flag是保证不再创建多个promise,因为每创建一个promise的微任务就会在调用栈中执行一次浪费性能,同时也能保证在**上一次对状态进行更改完成后才执行nextTick中的操作(以调用nextTick之前的对状态更改为一个节点)**因为在执行nextTick之前如果有状态的更改就会将这个更改的callback放入callbacks这样在循环执行的时候就会有一个先后顺序,如果无更改则先将nextTick中的操作先执行,然后在执行更新操作即其他的callbacks
  • watcher类
let id = 0
class Watcher{
    constructor(vm,exprOrFn,cb,options){
        this.vm = vm
        this.cb = cb
        this.options = options
        this.id = id++
        this.getter = exprOrFn
        this.deps = []
        this.depId = new Set()
        this.get()
    }
    get(){
        this.pushTarget(this)
        this.getter()
        this.popTarget()
    }
    addDep(dep){
        let id = dep.id
        if(!this.depId.has(id)){
            this.deps.push(dep)
            this.depId.add(id)
            dep.addSub(this)
        }
    }
    run(){
        this.get()
    }
    update(){
        queueWatcher(this);
    }
}

let has = {}
let queue = []
let callbacks = []
let flag = false
function flushCallbacks(){
    callbacks.forEach(callback=>{
        callback()
    })
    callbacks = []
    flag = false
}
function nexTick(cb){
    callbacks.push(cb)
    if(!flag){
        flag = true;
        Promise.resolve().then(flushCallbacks)
    }
}
function flushSchedularQueue(){
    queue.forEach(watcher=>{
        watcher.run()
    })
    queue = []
    has = {}
}
function queueWatcher(watcher){
    let id = watcher.id
    if(has[id] == null){
        queue.push(watcher)
        has[id] = true;
        nextTick(flushSchedularQueue)
    }
}
  • Dep类
let id = 0
class Dep{
    constructor(){
        this.id = id++
        this.subs = []
    }
    depend(){
        Dep.target.addDep(this)
    }
    addSub(watcher){
        this.subs.push(watcher)
    }
    notify(){
        this.subs.forEach(watcher=>{
            watcher.update()
        })
    }
}
Dep.target = null
export function pushTarget(watcher){
    Dep.target = watcher
}
export function popTarget(){
    Dep.target = null
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值