Vue.set()

vue2.0中不能间接监听对象中新增属性的变动,如果须要监听,须要通过 Vue.set( target, propertyName/index, value )办法增加

set函数通过Object.defineProperty将传入的对象上的属性变为响应式属性,简易版实现如下:

  const set = (target, prop, initValue) => {    let value = initValue    let dep = new Dep()    return Object.defineProperty(target, prop, {      get() {        dep.depend()        return value      },      set(newValue) {        value = newValue        dep.notify()      }    })  }

这段代码中的逻辑与ref函数中的逻辑反复,将代码提取放到createReactive函数中。

数组响应式

Vue源码中对于push pop shift unshift splice sort reverse这些办法进行了解决,使得通过这些办法操作数组时能感知到数据的变动。

解决数组原型上的push办法

  1. 通过set生成一个响应式数组,在执行set函数时,曾经增加了依赖
  2. 革新数组原型上的push办法。首先将原型上的push办法存储起来,再从新定义Array.prototype.push。
  3. 在新办法中首先执行原本的push操作,而后须要调用notify办法,触发依赖的执行。notify办法挂载在createReactive函数内的dep实例上,这里的this即createReactive函数中的target对象,所以能够革新createReactive函数,将dep实例挂载到target的_dep属性上。这样就能够拿到并触发notify了。
  let createReactive = (target, prop, value) => {    // let dep = new Dep()    target._dep = new Dep()    if (Array.isArray(target)) {      target.__proto__ = arrayMethods    }    return Object.defineProperty(target, prop, {      get() {        target._dep.depend()        return value      },      set(newValue) {        value = newValue        target._dep.notify()      }    })  }    let push = Array.prototype.push  let arrayMethods = Object.create(Array.prototype)  arrayMethods.push = function (...args) {    push.apply(this, [...args])    // 这里须要调用notify办法    // notify办法挂载在createReactive函数内的dep实例上,批改为挂载到target上    // 这里通过this就能够拿到notify办法    this._dep && this._dep.notify()  }

残缺带示例代码:

<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>Document</title></head><body>  <button id="add">add</button>  <div id="app"></div>  <hr>  <button id="addArr">addArr</button>  <div id="appArr"></div></body><script>  let active  let effect = (fn, options = {}) => {    // 为什么要减少一个_effect函数    // 因为须要给_effect减少属性    // 也能够间接给fn减少,然而因为援用类型的起因,会对fn函数造成净化    let _effect = (...args) => {      try {        active = _effect        return fn(...args)      } finally {        active = null      }    }    _effect.options = options    _effect.deps = [] // effect和dep的关系-1    return _effect  }  let cleanUpEffect = (effect) => {    // 革除依赖    // 须要反向查找effect被哪些dep依赖了    // 在effect上增加[] 建设双向索引    const { deps } = effect    console.log(deps)    console.log(effect)    if (deps.length) {      for (let i = 0; i < deps.length; i++) {        deps[i].delete(effect)      }    }  }  let watchEffect = (cb) => {    /* active = cb    active()    active = null */    let runner = effect(cb)    runner()    return () => {      cleanUpEffect(runner)    }  }  let nextTick = (cb) => Promise.resolve().then(cb)  // 队列  let queue = []  // 增加队列  let queueJob = (job) => {    if (!queue.includes(job)) {      queue.push(job)      // 增加之后,将执行放到异步工作中      nextTick(flushJob)    }  }  // 执行队列  let flushJob = () => {    while (queue.length > 0) {      let job = queue.shift()      job && job()    }  }  let Dep = class {    constructor() {      // 寄存收集的active      this.deps = new Set()    }    // 依赖收集    depend() {      if (active) {        this.deps.add(active)        active.deps.push(this.deps) // effect和dep的关系-2      }    }    // 触发    notify() {      this.deps.forEach(dep => queueJob(dep))      this.deps.forEach(dep => {        dep.options && dep.options.schedular && dep.options.schedular()      })    }  }  let createReactive = (target, prop, value) => {    // let dep = new Dep()    target._dep = new Dep()    if (Array.isArray(target)) {      target.__proto__ = arrayMethods    }    return Object.defineProperty(target, prop, {      get() {        target._dep.depend()        return value      },      set(newValue) {        value = newValue        target._dep.notify()      }    })  }  let ref = (initValue) => createReactive({}, 'value', initValue)  const set = (target, prop, initValue) => createReactive(target, prop, initValue)  let computed = (fn) => {    let value    let dirty = true // 为true表明依赖的变量产生了变动,此时须要从新计算    let runner = effect(fn, {      schedular() {        if (!dirty) {          dirty = true        }      }    })    return {      get value() {        if (dirty) {          // 何时将dirty重置为true,当执行fn后          // 因而须要通过配置回调函数,在执行fn后将dirty重置为true          // value = fn()           value = runner()          dirty = false        }        return value      }    }  }  let watch = (source, cb, options = {}) => {    const { immediate } = options    const getter = () => {      return source()    }    // 将函数增加到count的依赖下来,当count变动时    let oldValue    const runner = effect(getter, {      schedular: () => applyCb()    })    const applyCb = () => {      let newValue = runner()      if (newValue !== oldValue) {        cb(newValue, oldValue)        oldValue = newValue      }    }    if (immediate) {      applyCb()    } else {      oldValue = runner()    }  }  let push = Array.prototype.push  let arrayMethods = Object.create(Array.prototype)  arrayMethods.push = function (...args) {    console.log(this)    push.apply(this, [...args])    // 这里须要调用notify办法    // notify办法挂载在createReactive函数内的dep实例上,批改为挂载到target上    // 这里通过this就能够拿到notify办法    this._dep && this._dep.notify()  }  // set示例:  let count = ref(0)  /* // count.v新增属性,不会有响应式变动  document.getElementById('add').addEventListener('click', function () {    if (!count.v) {      count.v = 0    }    count.v++  })  let str  let stop = watchEffect(() => {    str = `hello ${count.v}`    document.getElementById('app').innerText = str  }) */  document.getElementById('add').addEventListener('click', function () {    if (!count.v) {      set(count, 'v', 0)      watchEffect(() => {        str = `hello ${count.v}`        document.getElementById('app').innerText = str      })    }    count.v++  })  // 数组push示例:  let arrValue = 0  // set函数中曾经对依赖进行了一次增加  let countArr = set([], 1, 0)  document.getElementById('addArr').addEventListener('click', function () {    arrValue++    countArr.push(arrValue)  })  watchEffect(() => {    str = `hello ${countArr.join(',')}`    document.getElementById('appArr').innerText = str  })</script></html>