ccforward / cc

Code & Blog
1.59k stars 193 forks source link

49.理解 Vue.js 的计算属性 #54

Open ccforward opened 7 years ago

ccforward commented 7 years ago

理解 Vue.js 的计算属性

通过模拟一个简单的例子来理解 Vue.js 的计算属性

Object.defineProperty

通过 Object.defineProperty 的 getter setter 来实现对象的数据劫持,例如:

const person = {}

Object.defineProperty(person, 'name', {
  get: function(){
    console.log('getting name')
    return 'Tink'
  }
})

console.log('this person is ', person.name)

// [console]: getting name
// this person is Tink

Observable 对象

Vue.js 最基本的架构,就是可以把一个普通对象转成可被观察(observable)的对象。

对上面的 person 对象实现一个简单的 Observer 如下:

function defineReactive(obj, key, val){
  Object.defineProperty(obj, key, {
    get: function(){
      return val
    },
    set: function(newVal){
      val = newVal
    }
  })
}

const person = {}

defineReactive(person, 'name', 'tink')
defineReactive(person, 'age', 20)

if(person.age >= 18){
  console.log('man')
}else {
  console.log('boy')
}

person.age = 10

定义一个计算属性

首先创建一个名为 defineComputed 的函数来定义计算属性

defineComputed 函数应该可以这样用

defineComputed(
  person, // 计算属性所属的对象
  'status', // 需要做计算的属性
  function(){ // 执行计算的具体方法
    console.log('status getter')
    if(person.age > 18){
      return 'man'
    }else {
      return 'boy'
    }
  },
  function(newVal){
    // 计算属性的值更新后执行
    console.log('status is now: ' + newVal)
  }
)

// 还可以像普通属性一样访问计算属性
console.log('The person is a ' + person.status)

然后,具体的实现一个简单的 defineComputed 函数

function defineComputed(obj, key, computedFn, updateCallBack){
  Object.defineProperty(obj, key, {
    get: function(){
      // 调用 具体的计算函数 并返回计算后的值
      return computedFn()
    },
    set: function(){
      // nothing 不做 setter
    }
  })
}

现在有两个问题

  1. 每次访问对象属性的时候都会调用 计算函数 computedFn
  2. 并不知道什么时候 更新属性 (没有执行回调 updateCallBack)

最后,计算属性的预期应是如下的方式

person.age = 20
// console: status == 'man'

person.age = 10
// console: status == 'boy'

增加依赖 Dependency

首先,添加一个全局的 Dep 对象

const Dep = {
  target: null
}

这就是用来跟踪的 Dependency tracker

然后,给 defineComputed 函数添加依赖

function defineComputed(obj, key, computedFn, updateCallBack){
  const onDependencyUpdate = () => {

  }

  Object.defineProperty(obj, key, {
    get: function(){
      Dep.target = onDependencyUpdate
      const newVal = computedFn()
      // reset target  其他属性就不会再添加这个依赖
      Dep.target = null
      return newVal
    },
    set: function(){
      // nothing 不做 setter
    }
  })
}

我们再来改进最初实现 Observer 的 defineReactive 函数

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

  Object.defineProperty(obj, key, {
    get: function(){
      if(Dep.target && deps.indexOf(Dep.target) == -1){
        deps.push(Dep.target)
      }
      return val
    },
    set: function(newVal){
      val = newVal
      // 通知所有 依赖器 执行计算函数: onDependencyUpdate 
      deps.forEach( updateCallBack => {
        updateCallBack()
      })
    }
  })
}

同时我们改进下 defineComputed 方法中的 onDependencyUpdate 来执行回调

const onDependencyUpdate = () => {
  const val = computedFn()
  updateCallBack(val)
}

图解

person.status 设置为计算属性

person.age = 20

defineComputed({
  person,
  'stauts',
  function(){
    if(person.age > 18){
      console.log('this is man')
      return 'man'
    }else {
      console.log('this is boy')
      return 'boy'
    }
  },
  function(newVal){
    console.log('person.status now is ' + newVal)
  }
})

console.log('person.status is ' + person.status)

Step 1

person.status 的 get() 被调用,并把 Dep.target 设为它的回调 computed1.png

Step 2

执行 person.status 的 get() 里的计算函数 computed2.png

Step 3

person.age 的 get() 检查 Dep 是否有可用的 target computed3.png

Step 4

计算函数获取到新的值并返回。而且现在 person.age 的值只要变化就会通知给 person.status computed4.png

lishuaixingNewBee commented 6 years ago

你可以的