前言

最近在研究Vue的源码,探索一下Vue的响应式原理。研究它是怎么实现的。Vue2.0是用 Object.defineProperty 去实现的, Vue3.0是用Proxy 去实现的。然后再结合观察者模式去实现数据跟视图的更新。本节先分享一下基础原理,下一节分享一下具体的实现。

数据响应式的核心原理

Vue2.x

  • Vue2.x深入响应式原理
  • MDN-Object.defineProperty
  • 兼容IE8以上
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>vue2.0响应式原理</title></head><body><div id="app">hello</div><script>
  // 绑定单个
  let data = {msg: 'hello word',count: 10
  }  let vm = {}  Object.defineProperty(vm, 'msg', {configurable: true, // 是否能被改变,是否能被delete删除enumerable: true, // 是否可枚举,遍历get() {      console.log('get:' + data.msg)      return data.msg
    },set(newVal) {      if(data.msg === newVal) return  data.msg = newVal      console.log('set:' + data.msg)      document.getElementById('app').innerText = data.msg
    }
  })  console.log(vm.msg)
  vm.msg = 'xxx'

  console.log('===================')  // 绑定多个

  let data2 = {msg: 'hello word',count: 10,obj: {      name: '你好啊'},arr: [1, 2, 3]
  }  let vm2 = {data: data2
  }  function defineProperty(obj, key, val) {// 如果value是对象,则继续对他下级成员进行响应式监听observer(val)Object.defineProperty(obj, key, {      configurable: true, // 是否能被改变,是否能被delete删除  enumerable: true, // 是否可枚举,遍历  get() {console.log('get:' + val)return val
      },      set(newVal) {if(val === newVal) return// 如果新设置的值是对象,则继续对他下级成员进行响应式监听observer(newVal)
        val = newValconsole.log('set:' + key + ':' + val)
      }
    })
  }  function observer(data) {// 如果不是对象,则返回if(!data || typeof data != 'object' ) returnObject.keys(data).forEach(key => {
      defineProperty(data, key, data[key])
    })
  }
  observer(vm2.data)  console.log(vm2)</script></body></html>复制代码

Vue3.x

  • MDN-Proxy
  • 直接监听对象,而不是属性
  • IE不支持,性能由浏览器优化
<!DOCTYPE html><html lang="en"><head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>vue3.0响应式原理</title></head><body>
  <div id="app">hello</div>
  <script>let data = {      msg: 'hello word',      count: 10,      obj: {name: '你好啊'  },      arr: [1, 2, 3]
    }let vm = new Proxy(data, {      get(target, key) {console.log('get', key, target[key])return target[key]
      },      set(target, key, newVal) {if(target[key] == newVal) returnconsole.log('set', key, newVal)
        target[key] = newVal
      }
    })console.log(vm.msg)  </script></body></html>复制代码

发布订阅模式

发布订阅模式由三者组成:发布者、订阅者、信号中心。我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern)

  • 我们先看下Vue的自定义事件$on和$emit
let vm = new Vue()
vm.$on('dataChange', () => {
 console.log('dataChange')
})
vm.$on('dataChange', () => {
 console.log('dataChange1')
})
vm.$emit('dataChange')复制代码
  • 自己实现一个发布订阅模式的思路
    • 定义一个类,初始化的时候,定义一个对象subs,用来存储各种事件,以及订阅这个事件的数组对象
    • 创建一个订阅方法,把订阅的事件和对象存储到subs中,如果没有这个事件,则创建一个,有的话,则添加到订阅的数组对象中
    • 创建一个发布方法,把发布事件,所订阅的所有对象,都通知一遍
class EventEmitter {  constructor() {// 存储所有事件对象this.subs = {}
  }

  $on(eventType, fn) { // 订阅this.subs[eventType] = this.subs[eventType] || []this.subs[eventType].push(fn)
  }

  $emit(eventType, ...params) { // 发布if(this.subs[eventType]) {      this.subs[eventType].forEach(fn => {
        fn(...params)
      })
    }
  }
}let eve = new EventEmitter()

eve.$on('click', function(data, two) {  console.log('click1', data, two)
})

eve.$on('click', function(data) {  console.log('click2', data)
})

eve.$on('change', function() {  console.log('change')
})

eve.$emit('click', 1, 2)
eve.$emit('change')复制代码

观察者模式

  • 观察者 -- Watcher
    • update() 这里处理当事件发生时,所要做的事情
  • 目标  -- Dep
    • subs 存储所有观察者的数组
    • addSub() 添加观察者的方法,参数是观察者对象
    • notify() 循环subs,通知所有观察者,调用观察者的update方法
class Dep {  constructor() {this.subs = []
  }  addSub(sub) {if(sub && sub.update) {      this.subs.push(sub)
    }
  }  notify() {if(!this.subs.length) returnthis.subs.forEach(sub => {
      sub.update()
    })
  }
}class Watcher {  update() {console.log('update')
  }
}let dep = new Dep()let watch = new Watcher()
dep.addSub(watch)
dep.notify()复制代码

总结

  • 观察者模式是由具体目标调度,比如当事件触发,Dep 就会去调用观察者的方法,所以观察者模式的订阅者与发布者之间是存在依赖的。
  • 发布/订阅模式由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在。

实现一个简单的vue-响应式模拟(一)_vue

实现一个简单的vue-响应式模拟(二)