vue 2.0 中数据响应式原理

712 阅读3分钟

首先将数据响应的关系图通过图形的形式画出来

数据响应流程图.png

通过流程图可以看到 大致分为几个不同的方法和类

  1. 能够对数据进行观察的 observe 和 observer类 对数组的处理
  2. 对数据进行观察的 defineReactive
  3. 数据依赖的 Dep
  4. 对依赖添加处理的 Watch

几个文件的关系图

微信截图_20210810072419.png

  • index.js
import defineReactive from './data/defineReactive.js'
import {observe} from './data/observer'
import Watcher from './data/watcher.js'
var obj = {
  'a': {
    "b":{
      'n': 5
    }
  },
  'b': 6,
  'g':['90','76','766','56']
}
observe(obj)
new Watcher (obj, 'a.b.n', (val) => {
  console.log('********',val)
})
obj.a.b.n = 890
  • observer.js
import {def} from './utils.js'
import defineReactive from './defineReactive.js'
import {arrayMethods} from './array.js'
import Dep from './dep.js'
export default class Observer {
  constructor (value) {
    this.dep = new Dep()
    // 一个不可枚举属性   this 在类中不是指的类本身 , 指的是实例
    // 通过def 函数给 value 添加__ob__属性,并且指向 实例本身
    def(value, '__ob__', this, false)
    // Observer 的终极目标是将 obj 中每个层级的属性转化为响应式的
    if (Array.isArray(value)) {
      // 如果是数组,强行将原型指向 新的地址
      Object.setPrototypeOf(value, arrayMethods)
      // 让这个数组变的 observe
      this.observeArray(value)
    }else{
      this.walk(value)
    }
  }
  walk (value) {
    // 遍历每个 KEY 值去将每个层级的属性变为响应式
    for(let key in value) {
      defineReactive(value,key)
    }
  }
  observeArray (arr) {
    for (let i=0, l= arr.length; i<l; i++) {
      observe(arr[i])
    }
  }
}
export function observe (value) {
  // 判断是不是对象,如果不是对象直接返回
  if (typeof value !== 'object') return;
  var ob;
  // 判断该对象上是不是绑定了__ob__ 如果绑定了 直接使用
  if (typeof value.__ob__!=='undefined') {
    ob = value.__ob__
  }else {
    ob = new Observer(value)
  }
  return ob
}
  • utils.js
// def 函数利用defineProperty 给对象添加属性和属性值
export function def (obj, key, val, enumerable) {
  Object.defineProperty(obj, key, {
    value : val,
    enumerable, // 是不是课枚举的
    writable: true, // 是不是可写的
    configurable: true // 是不是课设置的
  })
}

// 返回一个高阶函数, 在watcher 中使用,是获取对象的值使用的
export function parsePath (str) {
  let segments = str.split('.')
  return (obj) => {
    for (let i =0; i< segments.length; i++) {
      if (!obj) return;
      obj = obj[segments[i]]
    }
    return obj
  }
}
  • array.js
// 这里是对数组中的一些函数,利用装饰者模式来添加一些新的功能--触发 数据更新

import {def} from './utils.js'
// 找到Array 的原型对象
const arrayPrototype = Array.prototype
// 以array.prototype 为原型创建一个 arrayMethods, 这样arrayMethods 上就有了 Array 上的方法
export let arrayMethods = Object.create(arrayPrototype)
let methodNeedChange = [
  'push','pop','shift','unshift','splice','sort','reverse'
]

methodNeedChange.forEach((methodName) => {
  // 备份原来的方法
  const original = arrayMethods[methodName]
  // 对需要改变的函数重新定义
  def(arrayMethods,methodName,function() {
   // 首先执行老方法,利用 apply, 来执行, 
    const result = original.apply(this, arguments)
    // 在每个值的本生绑定了__ob__属性, 绑定的实例本生
    let ob = this.__ob__;
    let args = [...arguments] // 将类数组变为数组
    // 有三种方法 会往数组中添加内容, 需要将添加的内容页变为 observe
    // push unshift splice

    let inserted = []
    switch (methodName) {
      case 'push':
      case 'unshift':
        inserted = args;
        break;
      case 'splice':
      // splice(开始的下标,数量,要添加的值)
        inserted = args.slice(2);
        break;
    }

    if (inserted) {
      // 让新项也变为 observe
      ob.observeArray(inserted)
    }
    console.log('啦啦啦')
    ob.dep.notify()
   
    return result
  },false)
})


  • defineReactive.js

import {observe} from './observer'
import Dep from './dep.js'
export default function defineReactive (data,key,val) {
  const dep = new Dep()
  if (arguments.length == 2) {
    val = data[key]
  }
  let childOb = observe(val)
  Object.defineProperty(data,key,{
    enumerable: true,  //  可被枚举
    configurable: true, // 可以被设置,比如删除
    get () {
      // 如果处于一个依赖收集阶段
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
        }
      }
      console.log('你试图访问obj 的'+key+'属性' + val)
      return val
    },
    set (newVal) {
      console.log('你获取了'+key+'值' + newVal)
      if (val===newVal) {
        return
      }
      val = newVal
      // 当设置了新值, 需要继续添加观察
      childOb = observe(val)
      dep.notify()
    }
  })
}

  • dep.js

let uid =0;
export default class Dep {
  constructor() {
    console.log('我是DEP 类的构造器')
    this.id = uid++
    // 发布订阅者模式 创建一个 订阅者数组  放的是 watcher 实例
    this.subs = []
  }
  addSub (sub) {
    this.subs.push(sub)
  }
  // 添加依赖
  depend () {
    // Dep.target是指定的一个全局位置
    if (Dep.target) {
      this.addSub(Dep.target)
    }
  }
  notify () {
    console.log('我是 notify')
    // 浅克隆一份
    let subs = this.subs.slice()
    // 遍历
    for (let i=0,l=subs.length; i< l; i++) {
      subs[i].update()
    }
  }
}
  • watcher.js
import Dep from './dep.js';
import {parsePath} from './utils.js'
let uid = 0
export default class Watcher{ 
  constructor(target, expression, callBack) {
    this.id = uid++
    this.target = target;
    // parsePath 就是解析表达式的 一个高阶函数
    this.getter = parsePath(expression)
    this.callBack = callBack;
    this.value = this.get()
    console.log('我是watcher的构造器')
  }
  update () {
    this.run()
  }
  get () {
    // 进入依赖收集阶段 让全局的Dep.target 设置为 watcher 本身
    Dep.target = this;
    const obj = this.target;
    // 只要能找 就一直找
    var value
    try {
      value = this.getter(obj)
    } finally {
      Dep.target = null
    }
    return value
  }
  // 得到 并且唤起
  run () {
    this.getAndInvoke(this.callBack)
  }
  getAndInvoke (cb) {
    const value = this.get();
    if (value !== this.value || typeof value == 'object') {
      const oldVal = this.value
      this.value = value
      // 这里就是 watcher 中的 参数, 新值 和旧值
      cb.call(this.target,value,oldVal)
    }
  }
} 

最后总结下:

  1. 在 observe 中通过遍历的方式,给对象和数组添加 添加一个__ob__ 属性,属性值是Observer实例本身。每个实例上也绑定一个 dep
  2. 通过对象遍历的方式,给每个值都变为响应式的
  3. 在defineReactive 中给对象和对象的属性添加为响应式, 对值再调用 observe 是每个层都变为响应式
  4. 调用 new Watcher 的时候 , 首先会调用 watcher中的get 方法,将对象本身设置为Dep.target 的静态属性值,然后获取当前的对象,对应的属性值。 这样就触发了defineProperty 中的get , 来收集依赖, 实际上就是将 watcher 实例本身收集了。
  5. 在index.js 中再改变对象中某个值的时候,会触发 defineProperty 中的set ,触发 notify ,进而将dep 中观察这个对象时收集的依赖触发。就是出触发 watcher 中的update .update中 会重新获取该对象的该属性值,会得到newVal, 也会再次收集依赖, 最后调用 回调函数,将oldVal和newOld 作为参数。