数据响应原理(四)- 数据依赖(全部处理逻辑)

213 阅读5分钟

依赖

需要用到数据库的地方,称为依赖 vue 1 用到dom 是依赖 vue 2 用到数据是的依赖 在getter中收集依赖,在setter中触发依赖

逻辑

数据 => Observe(拆分对象,对数据每个子属性进行观测),每个__ob__实例会有个dep => Dep负责收集依赖和通知全部依赖组件更新,每个引用了此对象的component就是对此数据依赖,每次数据【使用-get】 就会 【收集依赖】 , 每次数据被改变,便会通过收到依赖数组subs, 通知notify全部Watcher => Watcher 是个中间媒介,通过Watcher更新组件**【component】**

图例.png

Observer

拆分对象,对数据每个子属性进行观测(数组单独处理) 每个Observer实例上都要有dep实例


  • defineReactive中--创建dep实例

Dep类

专门用来管理依赖,使用发布订阅模式,当数据发生变化时,会循环依赖列表,把所有的Watcher都通知一遍 每一个Observer(用于拆分对象,和observe循环调用)实例都会有一个dep所以,每个对象的每个层级都会有dep(常量没有) ---具体实现

  • 设置DEP 实例ID
  • 存储当前Watcher的依赖
  • 添加订阅-方法
  • 添加依赖-方法
  • 通知全部Watcher去更新全部-component

Watcher

Watcher是一个中介,data(数据)变化时通过Watcher中转,通知component 保存着Watch内容的回调函数 ---具体实现

初始化

  • 设置watcher的id
  • 存储传入对象的target,作为系统的targe
  • 根据表达式存储当前路径
  • 保存回调函数
  • 根据表达式[获取数据方法]value

数据更新 获取数据方法

  • 调整target指向(全局只有一个)
  • 获取数据
  • 最终得到数据,并释放出 全局target

其他逻辑处理

  • 增强数组方法中处理中,增加对ob的dep 发布订阅(触发依赖)
  • defineReactive中get中(收集依赖),判断是否处于数据收集阶段,如果是则 调用当前实例dep 给当前target添加依赖,子元素同样添加依赖
  • defineReactive中set中(触发依赖),调用当前实例dep增加发布订阅

源码

// 入口
import observe from './observe'
import Watcher from './Watcher'
/* const obj = {
  a: {
    b: {
      m:5
    },
    c: 5
  },
  n: 4,
  g: [33,22,55]
} */
const obj = {
  a: {
      m: {
          n5
      }
  },
  b10,
  c: {
      d: {
          e: {
              f6666
          }
      }
  },
  g: [22334455]
};
observe(obj)
// obj.a.c = 10
new Watcher(obj, 'a.m.n'(val)=> {
  console.log('★★★★★', val)
})
obj.a.m.n = 33
// obj.g.push([33,44])
// console.log(obj.g, '--------pushed-------')
console.log(obj);



// dep.js
let uid = 0;
export default class Dep {
  constructor () {
    console.log('来自DEP类的构造器')
    this.id = uid++;
    // 用数组存储自己的订阅者。subs是英语subscribes订阅者的意思。
    // 这个数组里面放的是Watcher的实例
    this.subs = [];
  }
  // add添加订阅
  addSub(sub) {
    this.subs.push(sub)
  }
  // 添加依赖
  depend() {
    // Dep.target就是一个我们自己指定的全局的位置,你用window.target也行,只要是全剧唯一,没有歧义就行
    if (Dep.target) {
        this.addSub(Dep.target);
    }
  }
  // 通知更新
  notify() {
    console.log('来自DEP-Notify')
    // 浅克隆
    const subs = this.subs.slice();
    // 遍历
    for (let i = 0, l = subs.length; i < l; i++) {
        subs[i].update();
    }
  }
}

// defineReactive.js
import observe from './observe'
import Dep from './Dep.js'
export default function defineReactive(obj, key, val) {
  const dep = new Dep()
  if (arguments.length == 2) {
    val = obj[key]
  }
  // 子元素observe,形成多文件互相调用成为递归
  let childOb = observe(val);
 
  Object.defineProperty(obj, key, {
     // 可枚举
    configurabletrue,
    // 可以被配置,比如可以被delete
      enumerabletrue,
      // getter
    get() {
      console.log('-----------------访问属性', key, ':', val)
      console.log(Dep.target,' ----------------------')
        // 如果现在处于依赖收集阶段
        if (Dep.target) {
          dep.depend();
          if (childOb) {
              childOb.dep.depend();
          }
        }
        return val
      },
      // setter
      set(newData) {
        if (val === newData) {
          return 
        }
        console.log('-----------------设置属性', key, ':', newData)
        val = newData
        // 当设置了新值,这个值页要被observe,防止新值又是个对象
        childOb = observe(newData)
        // 发布订阅模式,通知dep
        dep.notify();
      } 
  });
}

// array.js
import { def } from "./utils"
const arrayPrototype = Array.prototype
// 以Array.prototype 为原型创建对象
export const arrayMethods = Object.create(arrayPrototype)
// 要被改写的7个方法,vue只有此7个列表方法数据会更新 单独改一个值不会出发数据更新
const methodsNeedTochange = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
methodsNeedTochange.forEach(methodName => {
  // 备份原有方法
  const original = arrayPrototype[methodName];
  // 定义新方法
  def(arrayMethods, methodName, function () {
     // 恢复原来的功能,才能使用 原有方法
    const result = original.apply(thisarguments); 
    
    // 把类数组对象变为数组
    const args = [...arguments];
    // 把这个数组身上的__ob__取出来,__ob__已经被添加了,为什么已经被添加了?因为数组肯定不是最高层,比如obj.g属性是数组,obj不能是数组,第一次遍历obj这个对象的第一层的时候,已经给g属性(就是这个数组)添加了__ob__属性。
    const ob = this.__ob__;
    // 有三种方法push\unshift\splice能够插入新项,现在要把插入的新项也要变为observe的
    let inserted = [];
    switch (methodName) {
      case 'push':
      case 'unshift':
          inserted = args;
          break;
      case 'splice':
          // splice格式是splice(下标, 数量, 插入的新项)
          inserted = args.slice(2);
          break;
     }
     // 判断有没有要插入的新项,让新项也变为响应的 
    if (inserted) {
       // 因为是整个数组已经 实例化过了 拥有了walk,和observeArray 才可以调用
      ob.observeArray(inserted);
    }
    // 发布订阅模式,通知dep
    ob.dep.notify();
    // pop等方法 要返回值
    return result;
  }, false)
})

//  Watcher.js
import Dep from "./Dep";
let uid = 0;
const parsePath = function (str) {
  const segments = str.split('.');
  return (obj) => {
    // 因为有循环所以会一直查到 传进来表达式的最后一层
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return;
      // console.log(obj, segments, segments[i])
      obj = obj[segments[i]]
    }
    return obj
  }
}
export default class Watcher {
  constructor (target, expression, callback) {
    console.log('Watcher类的构造器')
    this.id = uid++;
    console.log(target, 'target--------------')
    this.target = target;
    // 根据表达式,或得器getter 获取到值
    this.getter = parsePath(expression)// 此处返回的是 parsePath 返回的函数
    this.callback = callback;
    this.value = this.get()
  }
  update() {
    this.run();
  }
  get() {
    // 进入依赖收集阶段,让全局Dep.target设置为Watch本身,那么久进入了依赖收集阶段
    console.log(this'--------------this----------')
    Dep.target = this;
    const obj = this.target
    let value;
    // 此处返回的是 parsePath 返回的函数,将exp字符串传入 找到值
    // 一直找,最终让出target给别的Watcher
    try {
     value = this.getter(obj)
    } finally {
      Dep.target = null
    }
    return value
  }
  // 数据更新时,调用run
  run() {
    console.log('watch---------update-------run------')
    this.getAndInvoker(this.callback)
  }
  // 得到并唤起
  getAndInvoker(cb){
    const value = this.get()
    if (value !== this.value || typeof value == 'object') {
      const oldValue = this.value
      this.value = value
      cb.call(this.target, value, oldValue)
    }
  }
}

// Observer.js
import { def } from './utils'
import defineReactive from './defineReactive'
import { arrayMethods } from './array'
import observe from './observe'
import Dep from './Dep'
export default class Observer {
  constructor(value) {
    // 每一个Observer的实例身上都要有dep
    this.dep = new Dep()
    // 给实例增加了一个不可枚举的__ob__
    def(value, '__ob__'thisfalse)
    // console.log('Observer构造器', value)
    // Observer类的目的是:将一个正常的object转换为每个层级的属性都是响应式(可以被侦测的)的object
    if (Array.isArray(value)) {
      // 将数组的原型,指向arrayMethods
      Object.setPrototypeOf(value, arrayMethods)
      // 数组逐项observe,非数组,子元素已经在defineReactive,中处理了子项目,而数组,则需单独处理
      this.observeArray(value);
    } else {
      this.walk(value)
    }
  }
  // 遍历
  walk(value) {
    for (let i in value) {
      defineReactive(value, i)
    }
  }
  observeArray(arr) {
    for (let i = 0, l = arr.length; i < l; i++) {
      // 逐个进行observe
      observe(arr[i])
    }
  }
 }

// observe.js
import Observer from './Observer'
export default function (value) {
  // 如果不是对象 返回
  if (typeof value != 'object'return
  // 定义ob
  let ob;
  if ('__ob__' in value) {
    ob = value.__ob__;
  } else {
    ob = new Observer(value)
  }
  return ob
}


// utils.js
export function def(obj, key, value, enumerable) {
  Object.defineProperty(obj, key, {
    configurabletrue,
    enumerable,
    writabletrue,
    value
});
}