Vue 原理之数据响应

定义 Reactive 属性

通过 Object.defineProperty 可以为一个对象设置具有 gettersetter 的属性,于是,我们可以为一个对象添加 reactive 的属性。

function defineReactive (obj, key, val) {
  Object.defineProperty(obj, key, {
    get: function reactiveGetter () {
      return val
    },
    set: function reactiveSetter (newVal) {
      console.log(`${key} is set to ${newVal}`)
      val = newVal
    }
  })
}

const apple = {}

defineReactive(apple, 'color', 'red')
console.log(apple.color)  // -> 'red'
apple.color = 'yellow'  // color is set to yellow

定义 computed 属性

function defineComputed (target, key, computeFunc, updateCb) {
  Object.defineProperty(target, key, {
    get: function computedGetter() {
      // evaluate computeFunc and return value
      return computeFunc()
    },
    set: function computedSetter() {
      // noop
    }
  })
}

为计算属性添加依赖追踪(简单实现)

定义一个全局的变量用于追踪依赖

const Dep = {
  target: null
}

修改 defineComputed

function defineComputed (target, key, computeFunc, updateCb) {
  const onDepUpdated = () => {
    // recompute val when dep update
    const val = computeFunc()
    updateCb(val)
  }

  Object.defineProperty(target, key, {
    get: function computedGetter() {
      Dep.target = onDepUpdated
      const val = computeFunc()
      Dep.target = null
      return val
    },
    set: function computedSetter() {
      // noop
    }
  })
}

修改 defineReactive

function defineReactive (obj, key, val) {
  const deps = []

  Object.defineProperty(obj, key, {
    get: function reactiveGetter () {
      if (Dep.target) {
        deps.push(Dep.target)
      }
      return val
    },
    set: function reactiveSetter (newVal) {
      val = newVal
      deps.forEach(func => {
        func()
      })
    }
  })
}

测试:

defineComputed(apple, 'description', function () {
  return `this is a ${ apple.color } apple`
}, function (newVal) {
  console.log(`description has changed to '${newVal}'`)
})

console.log(apple.color)
// red
console.log(apple.description)
// this is a red apple

apple.color = 'yellow'
// description has changed to 'this is a yellow apple'

console.log(apple.description)
// this is a yellow apple

上面是通过在执行计算属性的 getter 时设置一个标志 Dep.target,调用到计算属性的依赖的 getter 时,会去读取 Dep.target,即可在 deps 中保存下依赖关系。

为计算属性添加依赖追踪(嵌套)

依赖追踪的简单实现中,我们将 onDepUpdated 保存在 Dep.target 中,但是存在多层依赖关系时,简单修改 Dep.target 并不能满足需求。

const apple = {}

defineReactive(apple, 'color', 'red')

defineComputed(apple, 'appleName', function () {
  return `${ apple.color } apple`
}, function (newVal) {
  console.log(`appleName has changed to '${newVal}'`)
})


defineComputed(apple, 'description', function () {
  return `this is a ${ apple.appleName }`
}, function (newVal) {
  console.log(`description has changed to '${newVal}'`)
})

console.log(apple.color)
// red
console.log(apple.appleName)
// red apple
console.log(apple.description)
// this is a red apple

apple.color = 'yellow'
// appleName has changed to 'yellow apple'
// appleName has changed to 'yellow apple'

appleNameonDepUpdateddeps 里被存了两份。且修改 apple.color 后,期望的输出应该是 appleName has changed to 'yellow apple'description has changed to 'this is a yellow apple'

原因在于向 deps 中添加 Dep.target 时并没有判重,且嵌套计算属性时,Dep.target 被最近读取的计算属性所覆盖,考虑使用栈模型解决这个问题。

const Dep = {}
Dep.target = null
const targetStack = []

function pushTarget (_target) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

function popTarget () {
  Dep.target = targetStack.pop()
}

function defineReactive (obj, key, val) {
  const deps = []

  Object.defineProperty(obj, key, {
    get: function reactiveGetter () {
      if (Dep.target && deps.indexOf(Dep.target) === -1) {
        deps.push(Dep.target)
      }
      return val
    },
    set: function reactiveSetter (newVal) {
      val = newVal
      deps.forEach(func => {
        func()
      })
    }
  })
}

function defineComputed (target, key, computeFunc, updateCb) {
  const deps = []

  const onDepUpdated = () => {
    const val = computeFunc()
    updateCb(val)

    deps.forEach(func => {
      func()
    })
  }

  Object.defineProperty(target, key, {
    get: function computedGetter() {
      if (Dep.target && deps.indexOf(Dep.target) === -1) {
        deps.push(Dep.target)
      }
      pushTarget(onDepUpdated)
      const val = computeFunc()
      popTarget()
      return val
    },
    set: function computedSetter() {
      // noop
    }
  })
}

target 存在 targetStack 中,每次执行计算属性的 getter 时,调用 pushTargetpopTarget 来修改 targetStack

模块化 Dep

defineReactivedefineComputedDep 相关的函数抽出。

// dep.js

export default class Dep {
}

Dep.target = null
const targetStack = []

export function pushTarget (_target: Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

export function popTarget () {
  Dep.target = targetStack.pop()
}
import Dep, { pushTarget, popTarget } from './dep'

最终代码

// dep.js

export default class Dep {
}

Dep.target = null
const targetStack = []

export function pushTarget (_target: Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

export function popTarget () {
  Dep.target = targetStack.pop()
}
import Dep, { pushTarget, popTarget } from './dep'

function defineReactive (obj, key, val) {
  const deps = []

  Object.defineProperty(obj, key, {
    get: function reactiveGetter () {
      if (Dep.target && deps.indexOf(Dep.target) === -1) {
        deps.push(Dep.target)
      }
      return val
    },
    set: function reactiveSetter (newVal) {
      val = newVal
      deps.forEach(func => {
        func()
      })
    }
  })
}

function defineComputed (target, key, computeFunc, updateCb) {
  const deps = []

  const onDepUpdated = () => {
    const val = computeFunc()
    updateCb(val)

    deps.forEach(func => {
      func()
    })
  }

  Object.defineProperty(target, key, {
    get: function computedGetter() {
      if (Dep.target && deps.indexOf(Dep.target) === -1) {
        deps.push(Dep.target)
      }
      pushTarget(onDepUpdated)
      const val = computeFunc()
      popTarget()
      return val
    },
    set: function computedSetter() {
      // noop
    }
  })
}