Vue源码分析之数据绑定Dep与Watcher的关系

安稳与你 提交于 2020-03-07 02:25:20

数据绑定

  • 数据绑定
  1. 一旦更新了data中的某个属性数据,所有界面上直接(this.xxx='aaa')或间接(计算属性或方法)使用了此属性的节点都会更新
  • 数据劫持
  1. 数据劫持是vue中用来实现数据绑定的一种技术
  2. 基本思想:通过defineProperty()来监视data中所有属性(任意层次)数据的变化,一旦变更就去更新界面

Dep与Watcher的关系

  1. Dep何时创建?—— 初始化时给data的属性进行数据劫持时创建的
  2. 有几个?—— 与data中的属性一一对应
  3. 结构是什么?—— id:标识,subs:[]相关的n个watcher容器

 

  1. Watcher何时创建?—— 初始化时解析大括号表达式/一般指令时创建
  2. 有几个?—— 与模板表达式(不包含事件指令)一一对应
  3. 结构是什么?
this.cb = cb  // 用于更新界面的回调
this.vm = vm  // vm
this.exp = exp  // 对应的表达式
this.depIds = {}  // 相关的n个dep容器对象
this.value = this.get()  // 当前表达式对应的value
  • 当执行vm.name='abc'表达式时,data中的name属性值变化会触发set方法,set中通知dep,dep通知所有watcher更新
  • Dep与Watcher之间是多对多的关系
  • 一个data属性对应一个Dep,一个Dep对应n个Watcher(属性多次在模板中被使用时n>1:{{a}}/v-text='a')
  • 一个表达式对应一个Watcher, 一个Watcher对应n个Dep(多层表达式时n>1:a.b.c)
function MVVM (options) {
    this.$options = options
    var data = this._data = this.$options.data

    // 对data中所有层次的属性通过数据劫持实现数据绑定
    observe(data, this)
}

var compileUtil = {
    bind: function (node, vm, exp, dir) {
        var updateFn = updater[dir+'Updater']
        updateFn && updateFn(node, this.getVMVal(vm, exp))
        
        // 为表达式创建一个对应的watcher,实现节点的更新
        new Watcher(vm, exp, function (value, oldValue) {//当表达式对应的一个属性值变化时回调
            // 更新界面中的指定节点
            updateFn && updateFn(node, value, oldValue)
        })
    },

    updateFn: function (node, val) {
        node.textContent = typeof val === 'undefined' ? '' : val
    },

    getVMVal: function (vm, exp) {
        var val = vm
        exp = exp.split('.')
        exp.forEach(function (k, v) {
            val = val[k]
        })
        return val
    },
}

var uid = 0
function Dep () {
    this.id = uid++
    this.subs = []
}

Dep.prototype = {
    notify: function () {
        // 遍历所有watcher,通知watcher更新
        this.subs.forEach(function (sub) {
            sub.update()
        })
    },

    // 建立dep与watcher之间的关系
    depend: function () {
        Dep.target.addDep(this)
    },

    addSub: function (sub) {  // 添加watcher到dep中
        this.subs.push(sub)
    }
}

Dep.target = null

function Watcher (vm, exp, cb) {
    this.cb = cb  // 更新界面的回调
    this.vm = vm  //
    this.exp = exp  // 表达式
    this.depIds = {}  // 包含所有相关的dep的容器对象
    this.value = this.get()  // 得到初始值保存
}

Watcher.prototype = {
    // 得到表达式的值,建立dep与watcher的关系
    get: function () {
        Dep.target = this;
        var value = compileUtil.getVMVal(this.vm, this.exp)
        // 去除dep中指定的当前watcher
        Dep.target = null
        return value
    },

    update: function () {
        this.run()
    },

    run: function () {
        var value = this.get()
        var oldValue = this.value
        if (value !== oldValue) {
            this.cb.call(this.vm, value, oldValue)
        }
    },

    addDep: function (dep) {
        // 判断dep与watcher的关系是否已经建立
        if (!this.depIds.hasOwnProperty(dep.id)) {
            dep.addSub(this)  // 将watcher添加到dep中
            this.depIds[dep.id] = dep  // 将dep添加到watcher中
        }
    }
}

function Observer (data) {
    this.data = data
    this.walk(data)
}

Observer.prototype = {
    walk: function (data) {
        var me = this

        Object.keys(data).forEach(function (key) {
            // 对指定属性实现响应式绑定
            me.defineReactive(key, data[key], me.data)
        })
    },

    defineReactive: function (key, val, data) {
        // 创建属性对应的dep对象
        var dep = new Dep()
        // 通过间接的递归调用实现对data中所有层次属性的数据劫持
        var childObj = observe(val)

        // 给data重新定义属性,添加set和get方法
        Object.defineProperty({
            configurable: false,
            enumerable: true,
            get: function () {
                if (Dep.target) {
                    dep.depend()  //建立dep与watcher的关系
                }
                return val
            },
            set: function (newVal) {  // 监视key属性的变化,更新界面
                if (newVal === val) {
                    return
                }
                val = newVal
                childObj = observe(newVal)  // 新值是object的话,进行监听
                dep.notify()  // 通知所有相关的订阅者
            },
        })
    }
}

function observe (data, vm) {
    if (!data || typeof data !== 'object') {
        return
    }

    return new Observer(data)
}

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!