# Vue 双向绑定原理以及实现

# 原理

  • Vue 数据双向绑定是通过数据劫持 和 发布者-订阅者模式的方式来实现的。
  • 数据劫持==> Object.defineProperty
  • 发布者-订阅者模式==> 监听器Observer、订阅者Watcher、消息订阅器Dep、指令解析器Compile
  • 监听器Observer用来监听所有属性的变化,告诉订阅者Watcher是否需要进行更新。
  • 订阅者Watcher存在多个,消息订阅器Dep则是用于专门收集这些订阅者的。
  • 指令解析器Compile用于对节点的扫描和解析

# 基本实现过程

  • 实现一个监听器Observer, 用于劫持和监听所有属性,若有变动,通知订阅者。
  • 实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
  • 实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。

# Observer 监听器

// observe.js
import Dep from './dep.js'
const observer = function(data) {
  if (!data || typeof data !== 'object') return
  Object.keys(data).forEach(function(key) {
    defineReactive(data, key, data[key])
  })
}

const defineReactive = function(data, key, value) {
  // 递归所有的子属性
  observer(value)
  var dep = new Dep()
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,

    get: function() {
      if (Dep.target) {
        dep.addSub(Dep.target)
      }
      return value
    },
    set: function(newValue) {
      if (newValue === value) return
      value = newValue
      console.log('属性' + key + '已经被监听到' + ':' + newValue)
      dep.notify()
    }
  })
}
export default observer

# Watcher 订阅者

// watcher.js
import Dep from './dep.js'
class Watcher {
  constructor(vm, exp, cb) {
    this.cb = cb
    this.vm = vm
    this.exp = exp
    this.value = this.get()
  }

  get() {
    Dep.target = this
    // 强制执行监听器里的get函数
    var value = this.vm.data[this.exp]
    Dep.target = null
    return value
  }

  update() {
    this.run()
  }

  run() {
    var value = this.vm.data[this.exp]
    var oldValue = this.value
    if (oldValue === value) return
    this.value = value
    this.cb.call(this.vm, value, oldValue)
  }
}

export default Watcher

# Dep 订阅器

// dep.js
// 订阅者
class Dep {
  constructor() {
    this.subs = []
  }
  addSub(sub) {
    this.subs.push(sub)
  }

  /**
   * 通知是有的订阅者,去执行更新函数
   */
  notify() {
    this.subs.forEach(function(sub) {
      sub.update()
    })
  }
}
export default Dep

# 构造器

// myVue.js
import observer from './observer.js'
import Watcher from './watcher.js'
class MyVue {
  constructor(data, el, exp) {
    var self = this
    this.data = data

    Object.keys(data).forEach(function(key) {
      self.proxyKey(key)
    })

    observer(data)

    el.innerHTML = this.data[exp]

    new Watcher(this, exp, function(value, oldValue) {
      el.innerHTML = value
    })

    return this
  }
  // 代理属性
  proxyKey(key) {
    const self = this
    Object.defineProperty(this, key, {
      enumerable: true,
      configurable: true,
      get() {
        return self.data[key]
      },
      set(newValue) {
        self.data[key] = newValue
      }
    })
  }
}
export default MyVue

# 使用例子

<div id="testId"></div>
import MyVue from '.myVue.js'
let data = {
  name1: '1'
}
let el = document.getElementById('testId')
let myVm = new MyVue(data, el, 'name1')
setTimeout(_ => {
  myVm.name1 = '2'
}, 3000)
最后一次修改时间: 10/18/2020, 10:04:40 PM