美文网首页
Vue响应式-依赖收集

Vue响应式-依赖收集

作者: Raral | 来源:发表于2021-01-16 08:51 被阅读0次

依赖收集

为什么要收集依赖

其目的在于我们观察数据的属性值发生改变时,可以通知哪些视图层使用了该数据。

咋样实现收集依赖

通过一个事件发布订阅模式设计模式,然而在这种设计模式去实现依赖收集,在vue有两个角色(对象),订阅者Dep 和 观察者Watcher

  1. 订阅者Dep

    1. 为什么引入Dep
      收集依赖需要找一个存储 依赖的地方,为此我们创建它用来添加依赖,删除依赖,和 向依赖发送消息。
      于是我们先来实现一个订阅者Dep类,用于解耦属性的依赖收集和派发更新操作,说具体点,它主要作用存放 watcher 观察者对象。我们可以把Watcher 理解一个中介的角色,数据发生变化时通知它,然后它通知其他地方(比如更新操作)。
    2. 简易实现 Dep
          class Dep {
              constructor () {
                  /* 用来存放Watcher对象的数组 */
                  this.subs = [];
              }
              /* 在subs中添加一个Watcher对象 */
              addSub (sub) {
                  this.subs.push(sub);
              }
              /* 通知所有Watcher对象更新视图 */
              notify () {
                  this.subs.forEach((sub) => {
                      sub.update();
                  })
              }
          }
      
      注意
      1. 用 addSub 方法可以在目前的 Dep 对象中增加一个 Watcher 的订阅操作;
      2. 用 notify 方法通知目前 Dep 对象的 subs 中的所有 Watcher 对象触发更新操作。
  2. 观察者Watcher

    1. 为什么引入Watcher
      Vue中定义一个watcher表示观察订阅依赖。
      当属性值发生改变,我们要通知到数据的地方,而使用这个数据的地方很多,而且类型不一样。可能时模板中,可能是watch methods ...,这时需要抽象出一个集中处理这些情况,然后我们在收集阶段只收集封装好的类的实例进来,通知也通知它一个,再由它负责通知其他地方。
    2. 简易实现 Watcher
    class Watcher {
        constructor(obj, key, cb) {
            // 将 Dep.target 指向自己
            // 然后触发属性的 getter 添加监听
            // 最后将 Dep.target 置空
            Dep.target = this
            this.cb = cb
            this.obj = obj
            this.key = key
            this.value = obj[key]
            Dep.target = null
        }
        update() {
            // 获得新值
            this.value = this.obj[this.key]
        // 我们定义一个 cb 函数,这个函数用来模拟视图更新,调用它即代表更新视图
            this.cb(this.value)
        }
        }
    

依赖收集

所谓的依赖,其实就是Watcher。至于如何收集依赖,总结起来就一句话,在getter中收集依赖,在setter中触发依赖。先收集依赖,即把用到该数据的地方收集起来,然后等属性发生变化时,把之前收集好的依赖循环触发一遍就行了。

具体来说,当外界通过Watcher读取数据时,便会触发getter从而将Watcher添加到依赖中,哪个Watcher触发了getter,就把哪个Watcher收集到Dep中。当数据发生变化时,会循环依赖列表,把所有的Watcher都通知一遍。

最后我们对 defineReactive 函数进行改造,在自定义函数中添加依赖收集和派发更新相关的代码,实现了一个简易的数据响应式。

class Dep {
    constructor() {
        // 用来存放watcher对象的数组
        this.subs = [];
    }
    // 添加watcher对象
    addSub(sub) {
        this.subs.push(sub);
    }
    // 通知所有watcher对象更新视图,通过update 更新
    notify() {
        this.subs.forEach(sub => {
            sub.update();
        })
    }
}


class Watcher {
    constructor(obj,key, cb) {
        //将Dep.taget 指向自己
        // 然后触发属性的getter添加监听
        // 最后将Dep.target置空
        Dep.target = this;
        this.cb = cb;
        this.obj = obj;
        this.key = key;
        this.value = obj[key];
        Dep.target = null;
    }

    update() {
        //获取新值
        this.value = this.obj[key];
        // 我们顶一个cb 函数, 这个函数用来模拟视图的更新,调用它即代表更新视图
        this.cb(this.value);
    }
}

//响应式

function render() {
    console.log("视图更新")
}
//重写数组方法
let methods = ["pop","push","unshift","shift","sort","reverse","splice"];
let arrayProto = Array.prototype;
let proto = Object.create(arrayProto);
methods.forEach(method => {
    proto[method] = function() {
        arrayProto[method].call(this, ...arguments);
    }
})

//观察者
function observe(obj) {
    //判断是否数组,如果是改变数组的原型
    if(Array.isArray(Obj)) {
        obj._proto_ = proto;
        return
    }

    if(!obj || typeof obj !=="object") {
        return
    }
    Object.keys(obj).forEach(key => {
        defineReactive(obj,key, obj[key])
    })
    
}

//使用Object.defineProerty 拦截数据/代理数据
function defineReactive(data, key, value) {
    observe(value);//递归子属性
    let dp = new Dep();//新增一个dep
    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        get:function getter() {
            //将watcher 添加到订阅
            if(Dep.target) {
                dp.addSub(Dep.target);
            }
            return value;
        },
        set: function setter(newValue) {
            if(newValue != value) {
                value = newValue;
                render();//更新视图

                dp.notify();//通知更新
            }
        }
    })
}

class Vue {
    constructor(options) {
        this._data = options.data;
        observe(this._data);
        // 新建一个watcher对象,这时候Dep.target 指向watcher对象
        new Watcher();

        console.log("模拟视图渲染")
    }

}




相关文章

  • Vue源码分析—响应式原理(二)

    依赖收集 Vue会把普通对象变成响应式对象,响应式对象getter相关的逻辑就是做依赖收集,我们来详细分析这个过程...

  • Vue响应式-依赖收集

    依赖收集 为什么要收集依赖 咋样实现收集依赖 通过一个事件发布订阅模式设计模式,然而在这种设计模式去实现依赖收集,...

  • vue源码解析

    知识要点 vue工作机制 vue响应式的原理 依赖收集与追踪 编译compile vue工作机制 初始化 在new...

  • 响应式原理(二)

    依赖收集 通过上一节的分析我们了解 Vue 会把普通对象变成响应式对象,响应式对象 getter 相关的逻辑就是做...

  • vue2.0源码解析 - 依赖收集

    依赖收集通过上一节的分析我们了解 Vue 会把普通对象变成响应式对象,响应式对象 getter 相关的逻辑就是做依...

  • vue响应式和依赖收集

    看了vue源码后实现的一个很简单很简单的vue? 目的主要是串一下new Vue()之后到组件挂载的流程,及数据更...

  • Vue依赖收集响应式原理

    我的理解: 初始化状态时,会进行属性初始化,包含Props, Methods的初始化;数据响应式的初始化,包括da...

  • Vue原理学习(三)

    响应式依赖收集原理 在Vue原理学习 (二)中,介绍Object.defineProperty中的[[Getter...

  • 3.响应式系统的依赖收集追踪原理

    响应式系统的依赖收集追踪原理 为什么要依赖收集? 先举个栗子? 我们现在有这么一个 Vue 对象。 然后我们做了这...

  • 03.响应式系统的依赖收集追踪原理

    响应式系统的依赖收集追踪原理 为什么要依赖收集? 先举个栗子? 我们现在有这么一个 Vue 对象。 然后我们做了这...

网友评论

      本文标题:Vue响应式-依赖收集

      本文链接:https://www.haomeiwen.com/subject/kmpwaktx.html