vue源码阅读-究极简化版的依赖收集源码

响应式原理

vue是一个MVVM框架。
通俗理解,就是通过响应式对象,可以实现页面内容发生变动之后触发数据Model变化,Model发生变化可以触发页面重新渲染。
关于原理有好多文章也讲得很清楚,这里做一下源码的大删除,只保留关键部分易于理解。

简单来说

  1. 响应式对象会拥有__ob__属性,用以保存用他自己为入参创建的Observer实例。
  2. data的每一个属性都拥有自己的Dep实例:dep;
  3. Dep.target:target是Dep.prototype的属性。这也就意味着每一个Dep实例共用一个Dep.target。同一时间只会有一个watcher实例。
  4. 读取data[key],会触发这个key的get,此时会判断存在Dep.target的Watcher实例。调用dep.depend,将当前的Watcher塞进dep.subs里。subs会存储所有需要这个值的watcher。
  5. 修改data[key],会调用dep.notify,即触发每个watcher.update,其实就是watcher.get。
  6. watcher.get:
    1> getter: updateComponent = () => { vm._update(vm._render(), hydrating) }
    2> render:生成渲染树,完成对当前vue实例vm的数据访问,触发Observer对data属性做的getter
    3> update里面会调用patch :把 VNode 转换成真正的 DOM 节点。页面将会重新渲染。

Observer

Observer会处理data,使其变成响应式对象。
比较直观地感受,如果我们在编写组件时,template里面要用到的数据没有在data里声明,会出现一个报错,Vue 将警告你渲染函数正在试图访问不存在的 property。

class Observer{
    constructor(data){
        def(value, '__ob__', this) 
        this.walk(data);
    }
    walk(obj){
        const keys=Object.keys(obj);
        for(let i=0;i<keys.length;i++){
            defineReactive(obj,keys[i])
        }
    }
}
function defineReactive(obj,key){
    let val=obj[key];
    const dep=new Dep();
    Object.defineProperty(obj,key,{
        enumerable:true,
        configurable:true,
        get(){
            Dep.target&&dep.depend(); // dep.subs.push(Dep.target)
            return val;
        }
        set(newVal){
            if(val==newVal) return ;
            val=newVal;
            dep.notify();
        }
    })
}

Dep

class Dep{
    subs:[],
    id;
    target,
    depend(){
        Dep.target.addDep(this);
    }
    notify(){
        for(let i in this.subs){
            subs[i].update();
        }
    }
    addSub(watcher){
        this.subs.push(watcher);
    }
    removeSub (sub: Watcher) {
        remove(this.subs, sub) // Array.splice
    }
}

Watcher

class Watcher{
    newDeps:[],
    constructor(){
        this.get();
    }
    addDep(dep){
       this.newDepIds.add(id)
        this.newDeps.push(dep)
        dep.addSub(this);
    }
    update(){
        this.run();
    }
    run(){
        this.get();
    }
    get(){
        pushTarget(this); // Dep.target=this;
        // getter: updateComponent = () => { vm._update(vm._render(), hydrating) }
         // render:生成渲染树,完成对当前vue实例vm的数据访问,触发Observer对data属性做的getter
         //update: patch :把 VNode 转换成真正的 DOM 节点
        this.getter.call(vm,vm);
        popTarget();
    }
    teardown(){
         if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
    }
}
移除监听
  1. 每一个Watcher实例会拥有一个数组[Dep],在getter里使用Dep.depend收集依赖时,调用的Watcher.addDep,不仅会对Observerdep.subs.push(Watcher),也会对当前Watcher.deps.push(dep)。这个Watcher.deps就是收集的依赖。
  2. 如果希望解除data[某属性]watcher的依赖关系,则会调用Watcher.teardown,此时将会对Watcher拥有的deps的每一个dep实例调用removeSub,其实就是使用Array.splice删去当前的Watcher

本文参考:https://juejin.im/post/6844903634891735054

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。