Vue—Vue中的双向数据绑定原理

641 阅读3分钟

一、双向绑定

  • 双向绑定是指视图层和数据层的双项绑定
  • 视图改变,数据同步,view→model:事件监听
  • 数据改变,视图同步,model→view:数据劫持+发布订阅者模式

二、原理

1. Vue2实现双向数据绑定

  • 数据劫持+发布订阅者模式
  • 对象
    • 对象存储是将一个指向对象的指针(即引用)存储在栈中,指针指向真正的存储在堆中的位置
    • 对象的属性描述符分为数据描述符存取描述符
      • 数据描述符:writable(可修改)、enumerable(可枚举)、configurable(可配置),属性如果是可配置的,就可以使用Object.defineProperty(...)来修改属性描述符
      • 操作描述符:function get() → getter(获取属性值时调用)、function set → setter(设置属性值时调用)
    • Object.defineProperty
      • 此方法可用来创建、修改、添加多个属性值
  • 数据劫持
    • 通过定义Object.defineProperty的set函数,当数据发生改变就触发此函数,将处理数据更新的一些方法存在set函数中来实现实时更新。
  • 发布订阅模式
    • 定义对象之间一对多的依赖关系,当一个对象的状态改变(发布者)时,所有依赖于他的对象(订阅者)都将得到通知
    • 数据变化为发布者,依赖对象为订阅者

2. Vue2中的实现

  • 数据改变,视图同步
    • 主要通过Observer(监听器)、Dep(消息订阅器)、Watcher(订阅者)、Compiler(解析器)
  • 具体过程
    • 监听器劫持并监听所有属性,如果属性变化,就通知订阅者
    • 订阅器收集订阅者,对监听器和订阅者进行统一管理
    • 订阅者收到属性的变化通知并执行相应的方法,从而更新视图
    • 解析器解析每个节点的相关指令,对模板数据和订阅器进行初始化
    • 流程图:编译器首先根据元数据初始化视图,并且将更新函数绑定到订阅者上,一个属性对应一个订阅者,消息订阅器负责把一个个订阅者收集起来,一旦监听器发现属性中有发生变化的,就将变化通知给对应的消息订阅器,订阅器找到对应的订阅者调用他的更新函数,实现视图的更新。

三、实现

1. Vue 2-:Object.defineProperty

  • 参数
    • obj:操作对象
    • prop:操作对象的某个具体属性
    • descriptor:被定义或修改的属性描述符
  • 返回值
    • 被传递给函数的对象
  • 实现
    • (1)Observer监听器实现数据可监测
    // 循环遍历数据对象的每个属性
    function observable(obj) {
      if (!obj || typeof obj !== 'object') {
        return
      }
      let keys = Object.keys(obj)
      keys.forEach((key) =? {
        defineReactive(obj, key, obj[key])
      })
      return obj
    }
    // 将对象的属性用Object.defineProperty进行设置,使数据可观测
    function defineReactive(obj, key, val) {
      var dep = new Dep()
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        // 取值操作
        get() {
          // 植入订阅器
          if (Dep.target) {
            dep.addsub(Dep.target)
          }
          return val
        },
        // 赋值操作
        set(newVal) {
          if (newVal === val) return
          val = newVal
          dep.notify()
        }
      })
    }
    
    • (2)Dep订阅器实现依赖收集
    function Dep() {
      this.subs = []
    }
    Dep.prototype = {
      addSub: function(sub) {
        this.subs.push(sub)
      },
      notify: function() {
        this.subs.forEach(function(sub) {
          sub.update()
        })
      }
    }
    Dep.target = null // 全局唯一的Watcher
    
    • (3)Watcher订阅者实现
    function Watcher(vm, exp, cb) {
      this.vm = vm // Vue的实例对象
      this.exp = exp // node节点的v-model等指令的属性值或插值符号中的属性
      this.cb = cb // Watcher绑定的更新函数
      this.value = this.get()
    }
    Watcher.prototype = {
      update: function() {
        this.run()
      },
      run: function() {
        var value = this.vm.data[this.exp]
        var oldVal = this.value
        if (value !== oldVal) {
          this.value = value
          this.cb.call(this.vm, value, oldVal)
        }
      },
      get: function() {
        Dep.target = this // 将自己赋值成全局的订阅者
        var value = this.vm.data[this.exp] // 强制执行监听器里的get函数
        Dep.target = null
        return value
      }
    }
    
    • (4)Compile解析器实现解析DDM节点
    function Compile(el, vm) {
      this.vm = vm
      this.el = document.querySelector(el)
    }
    Compile.prototype = {
      compileText: function(node, exp) {
        var self = this
        var initText = this.vm[exp]
        this.updateText(node, initText)
        new Watcher(this.vm, exp, function(value) {
          self.updateText(node, value)
        })
      },
      updateText: function (node, value) {
        node.textContent = typeof value == 'undefined' ? '' : value;
      },
    }
    

2. Vue 3+: proxy && value setter

  • proxy:ES6新增方法,用来修改某些操作的默认行为
  • 参数:支持两个参数
    • 参数1:target(目标对象)
    • 参数2:prop(配置对象,对每一个需要代理的操作,提供一个处理函数,共13种,包括get、set、has、deleteProperty、ownKeys、getOwnPropertyDescriptor、defineProperty、preventExtensions、getprototypeOf、isExtensible、setPrototypeOf、apply、constructimage.png image.png

3. 两者区别