响应式原理(对象)

说起vue的响应式原理,大家也许只知道它的底层是对js的原生方法defineProperty的封装,而具体的实现却是一头雾水。下面我们就一探究竟。

1、封装defineProperty

主要目的:就是存取拦截;在get中收集依赖,在set中触发依赖;

/**
 * 存取描述符
 */
function defineReactive(data, key, val) {
  // 新增 递归子属性;
  if (typeof val === 'object') {
    new Observer(val)
  }
  let dep = new Dep()
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function () {
      // 收集依赖
      dep.depend()
      return val
    },
    set: function (newval) {
      if (val === newval) {
        return
      }
      val = newval
      // 通知触发收集的依赖项
      dep.notify()
    }
  })
}

2、封装 Dep的类;

主要目的:用于处理依赖。

class Dep {
  constructor() {
    // 收集依赖的集合
    this.subs = []
  }

  // 添加依赖;
  addSub(sub) {
    this.subs.push(sub)
  }

  // 删除依赖;
  removeSub(sub) {
    remove(this.subs, sub)
  }

  // 收集依赖
  depend() {
    // 假设我们把依赖存在 `window.target`上;
    if (window.target) {
      this.addSub(window.target)
    }
  }

  // 通知
  notify() {
    const subs = this.subs.slice()
    for (let i = 0; i < subs.length; i++) {
      subs[i].update()
    }
  }
}

function remove(arr, item) {
  if (arr.length) {
    const index = arr.indexOf(item)
    if (index > -1) {
      return arr.splice(index, 1)
    }
  }
}

3、观察者;

作用:将一个数据内的所有的属性都转换成getter/setter的形式。然后去追踪他们的变化;

class Observer {
    constructor(value) {
      this.value = value
      if (!Array.isArray(value)) {
        this.walk(value)
      }
    }

    // 此方法只在数据类型为对象时,调用;
    walk(obj) {
      const keys = Object.keys(obj)
      for (let i = 0; i < keys.length; i++) {
        defineReactive(obj, keys[i], obj[keys[i]])
      }
    }
  }

4、Watcher;

一个 Watcher 中介的角色,收集的依赖就是它。

class Watcher {
    constructor(vm, expOrFn, cb) {
      this.vm = vm
      // 执行this.getter(),就可以读取data.a.b.c的内容
      this.getter = parsePath(expOrFn)
      this.cb = cb
      this.value = this.get()
    }

    get() {
      window.target = this
      let value = this.getter.call(this.vm, this.vm)
      window.target = undefined
      return value
    }

    update() {
      const oldValue = this.value
      this.value = this.get()
      this.cb.call(this.vm, this.value, oldValue)
    }
  }

// 用于根据路径来读取属性的值;
  const bailRE = /[^\w.$]/

  function parsePath(path) {
    if (bailRE.test(path)) {
      return
    }
    const segments = path.split('.')
    return function (obj) {
      for (let i = 0; i < segments.length; i++) {
        if (!obj) return
        obj = obj[segments[i]]
      }
      return obj
    }
  }

5、使用;

实现:vm.$watch()

// 定义一个对象;
var data = {
    name: '小红',
    age: 18,
    skill: {
      dance: '芭蕾舞',
      exercise: '跑步'
    }
  }
  // 观测所有的属性的变化
  new Observer(data)

  function Vm() {}

  Vm.prototype.$watch = function (obj, path, cb) {
    new Watcher(obj, path, cb)
  }
  var vm = new Vm()

  vm.$watch(data, 'skill.dance', (newVal, oldVal) => {
    console.log('我执行了', newVal, oldVal)
  })

总结;

vue通过对js的原生方法的封装;来达到对数据的访问和赋值的拦截,然后做执行自己的逻辑代码;

要想达到对数据的响应,就要对数据进行处理,就是把用到这个数据的地方收集起来,然后当这个值发生变化时,通过便会通过触发get函数,来通知用到这个数据的地方,则其修改;

posted @ 2020-05-10 11:13  古怪精灵  阅读(540)  评论(0编辑  收藏  举报