vue2响应式原理

响应式原理

vue会递归遍历data()返回的对象,使用es5的Object.defineProperty()覆盖data上的属性,从而拦截对data上属性的读写。比如data上有一个username属性:

data.username = (()=>{
  let value = data.username // 必须将其保存为一个副本,因为如果在get/set中直接对data.username进行读写的话,会陷入死循环
  Object.defineProperty(data, 'username', {
    // 拦截对data.username的取值
    get value() {
      return value
    },
    // 拦截对data.username的赋值
    set value(newValue) {
			value = newValue
    }
  })
})()

实现了对data的拦截后,就可以使用观察者模式对数据变化的发起者和依赖者进行管理。注意,vue使用的是观察者模式,而不是发布/订阅者模式。两者的区别是后者多了一个“经纪人(Broker)”。

简单的vue实现

被观察者/依赖

class Dep {
  current = null // 当前待注册的观察者
  watchers = [] // 观察者列表
  // 注册观察者
  depend() {
    if (Dep.current) {
      this.watchers.push(Dep.current)
    }
  }
  // 通知观察者
  notify() {
    this.watchers.forEach(watcher => watcher.onNofity())
  }
}

观察者

class Watcher {
  data = null // 数据对象
  key = null // 被观察属性的键
  value = null // 被观察属性的旧值
  cb = null // 被观察属性变化后的回调
  constructor(data, key, cb) {
    this.data = data
    this.key = key
    this.cb = cb
    // 注册依赖
    Dep.current = this // 缓存自己
    this.value = this.data[this.key] // 观察数据
    Dep.current = null // 释放自己
  }
  // 被通知时调用
  onNofity() {
    let newValue = this.data[this.key]
    let oldValue = this.value
    if (newValue !== oldValue) {
      this.value = newValue
      this.cb(newValue, oldValue)
    }
  }
}

Vue类

// 简陋版Vue类
class MyVue {
  /* data是一个对象。
   * inputs数组,是<template/>中输入数据的部分。
   * 		其元素结构为{key, ele, event},key为data中属性的键,ele为输入控件,event为输入事件名。
   * outputs是数组,是<template/>中输出数据的部分。
   * 		其元素结构为{key, ele, attr},key为data中属性的键,ele为输出控件,attr代表输出到ele的什么属性上。
   */
  constructor(data, inputs, outputs) {
    this.data = data
    // 给obj中的每个属性创建一个依赖管理器
    Object.keys(data).forEach(key => {
      let dep = new Dep()
      let val = data[key] // 得将值保存为一个副本,因为defineProperty()后直接读写obj[key]会陷入死循环
      Object.defineProperty(data, key, {
        get() {
          dep.depend()
          return val
        },
        set(newVal) {
          val = newVal
          dep.notify()
        }
      })
    })
    // 给inputs中每个元素注册监听器
    inputs.forEach(input => {
      input.ele.addEventListener(input.event, e => {
        this.data[input.key] = e.target.value
      })
    })
    // 给outputs中每个元素创建观察者
    outputs.forEach(output => {
      // 将data[key]首次渲染到视图
      output.ele[output.attr] = this.data[output.key]
      // 观察数据变化,更新视图
      new Watcher(this.data, output.key, value => {
        output.ele[output.attr] = value
      })
    })
  }
}

测试

window.onload = function {
  const data = {
    name: 'initial value'
  }
  // inputs和outputs是编译<template/>后得到的
  const inputs = [
    {
      key: 'name',
      ele: document.querySelector('#input'),
      event: 'input'
    }
  ]
  const outputs = [
    {
      key: 'name',
      ele: document.querySelector('#output'),
      attr: 'innerHTML'
    }
  ]

  const myVue = new MyVue(data, inputs, outputs)
  }
<div id="output"></div>
<input id="input" />
posted @ 2021-09-01 11:40  hdxg  阅读(204)  评论(0)    收藏  举报
// 侧边栏目录 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css