Vue 源码剖析 —— 变化侦测相关 API 实现原理

栏目: JavaScript · 发布时间: 4年前

内容简介:vm.$watch(expOrFn, callback, [options])由于前面还提到,

vm.$watch(expOrFn, callback, [options])

  • 返回值: unwatch { Function }
  • 用法:用于观察一个表达式或 computed 函数在 Vue.js 实例上的变化,同时给回调函数传入新数据和旧数据作为参数。
  • options参数: {deep, immediate} ,其中 deep 指定是否观察对象内部值的变化, immediate 指定是否立即执行回调函数

实现原理

Vue.prototype.$watch = function(expOrFn, cb, options) {
  const vm = this
  options = options || {}
  const watcher = new Watcher(vm, expOrFn, cb, options)
  if (options.immediate) {
    cb.call(vm, watcher.value)
  }
  return function unwatch() {
    watcher.teardown()
  }
}
复制代码

由于 $watch 函数的第一个参数 expOrFn 可以是函数也可以是字符串表达式,所以这里需要改写一下 Watcher 类:

class Watcher {
  constructor (vm, expOrFn, cb) {
    this.vm = vm
    // 新增
    if (typeof expOrFn === 'function') {
    // 为函数时,不止可以返回动态数据,其中所有访问到的数据也都会被 Watcher 观察
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
    }
    this.cb = cb
    this.value = this.get()
  }
  ...
}
复制代码

前面还提到, vm.$watch 函数最后返回的是一个 unwatch 函数,顾名思义,它的作用是取消观察函数。执行 unwatch 函数其实就是执行当前 Watcher 实例的 teardown 函数。目前我们并没有在 Watcher 内部记录订阅的 Dep ,所以又要进行一次改写:

class Watcher {
  constructor (vm, expOrFn, cb) {
    this.vm = vm
    this.deps = [] // 新增
    this.depIds = new Set() // 新增
    if (typeof expOrFn === 'function') {
    // 为函数时,不止可以返回动态数据,其中所有访问到的数据也都会被 Watcher 观察
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
    }
    this.cb = cb
    this.value = this.get()
  }
  ...
  addDep (dep) {
    const id = dep.id
    if (!this.depIds.has(id)) {
      this.depIds.add(id)
      this.deps.push(dep)
      dep.addSub(this)
    }
  }
}
// 给 Dep 加上 id
let uid = 0
class Dep {
  constructor () {
    this.subs = []
  }

  addSub (sub) {
    // 新增
    if(somethingToWatch) {
      somethingToWatch.addDep(this)
    }
    //this.subs.push(sub)
  }
  ...
}

复制代码

到现在,不仅是 Dep 会记录数据发生变化时,需要通知哪些 Watcher ,而 Watcher 中也同样记录了自己会被那些 Dep 通知,也就是说,这是一个多对多的关系。添加了 deps 属性后, teardown 函数的实现就很简单了,这里就省略不写。

最后,可以看下 deep 参数的实现。当 deeptrue 时,需要监听当前对象的所有子值。实现原理和 Watcher 监听某个值的过程类似,通过访问这个数据,触发当前数据收集依赖的逻辑,把自己收集进去,故可以通过递归遍历当前要监听对象的所有子值来实现。

vm.$set

API 用法

vm.$set(target, key, value)

  • 用法: 在 target 中添加属性 key , 如果 target 是响应式的,那么新增的 key 值也是响应式的。

对于数组的处理

数组的处理比较直观,可以直接利用包裹好的 splice 函数实现:

function set(target, key, val) {
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key) 
    target.splice(key, 1, val)
    return val
  }
}
复制代码

对于对象的处理

function set(target, key, val) {
  ...
  // 新增
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }

  const ob = target.__ob__
  // _isVue 标记是否是 Vue 实例, ob.vmCount 标记是否是根数据对象
  if (target._isVue || (ob && ob.vmCount)) {
    // 报错
    ...
    return val
  }

  if (!ob) {
    target[key] = val
    return val
  }
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}
复制代码

vm.$delete

API 用法

vm.$delete(target, key)

由于在 Vue.js 中,删除某个属性无法自动向依赖发送通知,所以包装了一个 vm.$delete 避免出现下面这种比较丑陋的代码 =. =:

delete this.obj.name
this.obj.__ob__.dep.notify()
复制代码

下面是 vm.$delete 的实现:

function delete(target, key) {
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)
    return
  }
  const ob = target.__ob__
  if (!hasOwn(target, key)) return
  delete ob[key]
  if (!ob) return
  ob.dep.notify()
}
复制代码

本系列文章均是深入浅出 Vue.js的学习笔记,有兴趣的小伙伴可以去看书哈。


以上所述就是小编给大家介绍的《Vue 源码剖析 —— 变化侦测相关 API 实现原理》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Mission Python

Mission Python

Sean McManus / No Starch Press / 2018-9-18 / GBP 24.99

Launch into coding with Mission Python, a space-themed guide to building a complete computer game in Python. You'll learn programming fundamentals like loops, strings, and lists as you build Escape!, ......一起来看看 《Mission Python》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具