6.依赖收集

51 阅读3分钟

前言

vue2采用的是观察者的模式每个属性初始化进行劫持的时候会对每个属性创建dep,再将dep存放到渲染的watcher中,当属性发生更改时就会去通知属性的dep然后去调用渲染watcher.

Dep:依赖收集器

Observe监听器: 用于监听属性的变化通知观察者

Watcher观察者: 收到属性的变化,然后更新视图

src下代码改动目录:

- lifeCycle.js
- observe
    - index.js
    - arr.js
    - dep.js
    - watcher.js

代码实现

dep.js文件代码:

let id = 0

export class Dep {
  constructor() { 
    this.id = id++
    this.subs = [] // 这里存放当前属性的watcher
  }
  depend() {
    Dep.target.addDep(this)
    // this.subs.push(Dep.target) // 这里存放当前属性的watcher
  }
  addSub(watcher) { 
    this.subs.push(watcher) // 这里存放当前属性的watcher
  }
  notify() { 
    this.subs.forEach(watcher => watcher.update()) // 通知更新
  }
}
Dep.target=null

watcher.js文件代码

/**
 *  dep  收集器
 *  页面渲染的时候 将渲染逻辑封装到watcher中 vm._update(vm._render())
 *  让dep记住这个watcher即可,稍后属性变化了可以找到对应的dep中存放的watcher进行重新渲染
 *  观察者模式
 * 
 */
import { Dep } from "./dep"
let id = 0
export class Watcher {
  constructor(vm, exprOrFn, cb) {
    this.id = id++
    this.getter = exprOrFn
    this.renderWatcher = cb
    this.deps = [];
    this.depsId = new Set();
    this.get()

  }
  get() {
    Dep.target = this
    this.getter()
    Dep.target = null
  }
  addDep(dep) {
    let id = dep.id
    if (!this.depsId.has(id)) {
      this.deps.push(dep)
      this.depsId.add(id)
      dep.addSub(this)  // watcher 记住dep了 并且已经去重了 此时也让dep记住watcher
    }
  }
  update() {
    queueWatcher(this) // 把当前我watcher 暂存起来
    // this.get() // 重新渲染
  }
  run() {
    console.log("run执行");
    this.get()
  }
}

let queue = []
let has = {}
let pending = false // 防抖
function flushSchedulerQueue() {
  const flushQueue = queue.slice(0)
  queue = [],
  has = {}
  pending = false
  for (let i = 0; i < flushQueue.length; i++) {
    const watcher = flushQueue[i]
    watcher.run()
  }

}
function queueWatcher(watcher) {
  const id = watcher.id
  if (!has[id]) {
    queue.push(watcher)
    has[id] = true
    // 多个组件更新 不管我们的update执行多少次 但只执行一次刷新操作
    if (!pending) {
      nextTick(flushSchedulerQueue)
      pending = true
    }
  }
}
let callbacks = []
let waiting = false
function flushCallbacks() { 
  let cbs = callbacks.slice(0)
  console.log(cbs,"cbs");
  waiting = false
  callbacks = []
 
  cbs.forEach(cb => cb())
}

/**
 * 
 *  nextTick不是创建了异步任务而是将这个任务维护到了队列中
 *  nextTick没有直接使用某个api,而是采用优雅的降级方式
 *  内部先采用的是promise(ie不兼容)  MutationObserver(h5的api)  setImmediate(ie专项的api)  setTimeout
 */
let timerFunc 
if (Promise) {
  timerFunc = () => { 
    Promise.resolve().then(flushCallbacks)
  }
}else if (MutationObserver) {
  let observe = new MutationObserver(flushCallbacks)
  let textNode = document.createTextNode(1)
  observe.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    textNode.textContent = 2
  }
}else if (setImmediate) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
}else { 
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}
export function nextTick(cb) { 
  callbacks.push(cb)
  if (!waiting) { 
   
    timerFunc()
    waiting=true
  }

} 
/***
 *  一个组件有多少个属性 (n个属性对应一个视图) n个dep对应一个watcher
 * 1个属性  对应着多个组件  1个dep 多个watcher
 * 多对多的关系
 * 每个属性都有一个dep(被观察者), Watcher就是观察者(属性变化了就会通知观察者来更新),这就是观察者模式
 */

/observe/index.js文件:


import { arrayProto } from "./arr";
import { Dep } from "./dep";
/**
 * @description 用于监听属性的变化通知订阅者
 * @param {Object} data 
 * @returns {Object}
 */
export function observe(data) { 

  // 判断是否是对象
  if (typeof data !== 'object' || data === null) { 
    return data;
  }
  return new Observe(data);

} 


class Observe { 
  constructor(data) { 
    this.dep=new Dep(); // 创建一个dep对象
    Object.defineProperty(data, '__ob__', {
      enumerable: false, // 不能进行枚举
      value:this
    })
    // 判断对象是否是数组
    if (Array.isArray(data)) {
      data.__proto__ = arrayProto;
      // 如果数组是个对象
      this.observeArray(data);  // 处理数组对象
    } else { 
      // 遍历
      this.walk(data);
    }
   
     
  }
  walk(data) { 
    // 遍历data数据,对每个属性进行监听
    let keys = Object.keys(data);
    for (let i = 0, len = keys.length; i < len; i++) { 
      // 对每个属性进行劫持
      let key = keys[i];
      let val = data[key];
      // 监听
      defineReactive(data, key, val);
    }
  }
   // 对数组对象进行遍历监听
  observeArray(data) { 
    for (let i = 0,len=data.length; i < len; i++) { 
      observe(data[i])
    }
  }

}

function dependArray(value) { 
  for (let e, i = 0, l = value.length; i < l; i++) { 
    e = value[i];
    e && e.__ob__ && e.__ob__.dep.depend();
    if (Array.isArray(e)) {
      dependArray(e);
    }
  }
}
// 对对象中的属性进行劫持
function defineReactive(data, key, value) { 
  let childOb =  observe(value) // 深度监听
  let dep = new Dep(); // 每个属性都有一个dep
  // 代理数据
  Object.defineProperty(data,key,{
    enumerable:true, // 可以枚举
    configurable:true, // 可以删除
    get() {
      
      if (Dep.target) { 
        dep.depend()
        if (typeof childOb ==="object") {
          
          childOb.dep.depend()
          if (Array.isArray(value)) { 
            dependArray(value)
          }
        }
      }
      
      return value;
    },
    set(newValue) {
      if (newValue !== value) {
        observe(newValue) // 如果新的值也是对象也需要进行监听
        value = newValue;
        dep.notify() ; // 通知订阅者更新

      }
    }
  })
}

/observe/arr.js文件:

/* 
  重写数组函数方法
  (1) 获取原来数组方法
  (2) 继承
  (3) 劫持数组方法
*/

// (1) 获取原来数组
let oldArrayprotoMethods = Array.prototype; 
// (2) 继承
export let arrayProto = Object.create(oldArrayprotoMethods);

// 需要劫持的数组方法
let arrayProtoMethods = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
];
// (3) 劫持数组方法
arrayProtoMethods.forEach(item => { 
  arrayProto[item] = function (...args) { 
    let arr = oldArrayprotoMethods[item].apply(this, args);
    let inserted =null
    switch (item) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
      default:
        break
    }
    let ob = this.__ob__ 
    if (inserted) { 
      ob.observeArray(inserted) // 对我添加的对象进行劫持
    }
    ob.dep.notify() // 通知更新
    return arr
  }
})