简单实现vue中的data computed watch

573 阅读1分钟

基本

这里实现的是vue中的data computed watch,不涉及其他的功能。使用的是es6的类及方法。不是纯es5。

data

vuedata可以写成对象,也可以写成函数形式。但是由于组件的复用性,防止数据出错,这里直接使用了函数形式。 即,默认用户书写data为:

data(){
    return {
        a:1,
        b:1
    }
}

在vue类中,可以直接 使用 this.$data = data() 将数据挂载到vm实例上。

Computed

这里定义一个Computed

其中实现了几个必须的方法:

· addComputed 对监听数据进行预处理

· upData 更新监听的数据 (供外部调用)

· _addComputedProp 添加新数据

· _collectionDep 提取监听的变量名

里面使用 Object.defineProperty 对数据监听,调用时直接返回值,设置值时就重新计算,这就实现了不改变值就不重新计算的功能

class Computed {
  constructor() {
    // 存储监听的数据
    this.computedData = []
  }

  addComputed(vm, computed, key) {
    /**
     * computed 是一个对象
     * descriptor  对象里的所有属性
     * descriptorFn 对象里的方法
     * value 第一次执行完的值
     * get 存起来的descriptorFn方法
     * dep 依赖的数据 --一个字符数组
     */
    // 使用getOwnPropertyDescriptor是为了只在对象中查找属性
    // 而不能去原型链上找
    const descriptor = Object.getOwnPropertyDescriptor(computed, key),
      /**
       * 这里是为了预防两种使用computed的方法
       * 例如: total(){}   /   total: ()=> {}
       */
      descriptorFn = descriptor.value.get
        ? descriptor.value.get
        : descriptor.value,
       // 第一次执行的值
      value = descriptorFn.call(vm),
      // 将定义的方法保存起来
      get = descriptorFn.bind(vm),
      // 这个方法依赖的数据
      dep = this._collectionDep(descriptorFn)
    // 添加数据
    this._addComputedProp({
      key,
      value,
      get,
      dep,
    })

    // 为监听的的数据添加数据监听
    const dataItem = this.computedData.find((item) => item.key === key)
    Object.defineProperty(vm, key, {
      get() {
        // 获取数据的时候就返回
        return dataItem.value
      },
      set() {
        // 设置数据的时候就重新执行
        dataItem.value = dataItem.get()
      },
    })
  }
  upDate(key, cb) {
    this.computedData.map((item) => {
      const dep = item.dep
      // 修改的监听值是否存在
      const _key = dep.find((el) => el == key)
      if (_key) {
        // 重新执行
        item.value = item.get()
        // 如果有回调函数就执行
        cb && cb(key, item.value)
      }
    })
  }
  _addComputedProp(computedProp) {
    this.computedData.push(computedProp)
  }
  // 处理依赖的数据
  _collectionDep(fn) {
    // 截取this后面的字符
    const matched = fn.toString().match(/this\.(.+?)/g)
    let result = []
    matched.forEach((item) => {
      result.push(item.split('.')[1])
    })
    return result
  }
}

Watch

实现的几个必须的方法:

· addWatcher 对数据预处理

· _addWatcherProp 添加数据

· invoke 添加数据(供外部调用)

class Watcher {
  constructor() {
    this.watchers = []
  }
  addWatcher(vm, watcher, key) {
    this._addWatcherProp({
      vm,
      key,
      //watcher[key] -- 方法
      fn: watcher[key].bind(vm),
    })
  }
  invoke(key, newValue, oldValue) {
    this.watchers.map((item) => {
      if (item.key == key) {
        item.fn(newValue, oldValue)
      }
    })
  }
  _addWatcherProp(watchProp) {
    this.watchers.push(watchProp)
  }
}

Vue

vue类的作用是将前面几个类整和起来。

实现的几个功能:

  • 数据响应式
  • vm挂载data
  • 实例化 Wtach Computed 并且使用它们

image.png


class Vue {
  constructor(options) {
    const { data, computed, watch } = options
    this.$data = data()
    this.init(this, computed, watch)
  }

  init(vm, computed, watch) {
    // 数据响应
    this.initData(vm)
    const computedIns = this.initComputed(vm, computed)
    this.$computed = computedIns.upDate.bind(computedIns)
    const watcherIns = this.initeWatcher(vm, watch)
    this.$watch = watcherIns.invoke.bind(watcherIns)
  }

  initComputed(vm, computed) {
    const computedIns = new Computed()
    for (let key in computed) {
      // key -- 方法名
      // 给computed增加数据
      computedIns.addComputed(vm, computed, key)
    }
    return computedIns
  }
  initeWatcher(vm, watch) {
    const watcherIns = new Watcher()
    // 添加数据
    for (let key in watch) {
      watcherIns.addWatcher(vm, watch, key)
    }
    return watcherIns
  }
  initData(vm) {
    this.reavtive(
      vm,
      (key, value) => {},
      (key, newValue, oldValue) => {
        this.$computed(key)
        this.$watch(key, newValue, oldValue)
      }
    )
  }
  // 数据响应式  数据劫持
  reavtive(vm, __get__, __set__) {
    const _data = vm.$data
    for (let key in _data) {
      Object.defineProperty(vm, key, {
        get() {
          __get__(key, _data[key])
          return _data[key]
        },
        set(newValue) {
          const oldValue = _data[key]
          _data[key] = newValue
          __set__(key, newValue, oldValue)
        },
      })
    }
  }
}

测试一下

const vm = new Vue({
  data() {
    return {
      a: 1,
      b: 2,
    }
  },
  computed: {
    total() {
      console.log('Computed')
      return this.a + this.b
    },
  },
  watch: {
    total(newValue, oldValue) {
      console.log('total', newValue, oldValue)
    },
    a(newValue, oldValue) {
      console.log('a', newValue, oldValue)
    },
    b(newValue, oldValue) {
      console.log('b', newValue, oldValue)
    },
  },
})
console.log(vm)

console.log(vm.total)
console.log(vm.total)
console.log(vm.total)

vm.a = 100
console.log(vm.total)
console.log(vm.total)
console.log(vm.total)

vm.b = 200
console.log(vm.total)
console.log(vm.total)
console.log(vm.total)

结果:

image.png