Vue2响应式原理二:Watcher

侦听器

当监听的属性改变时,会触发相应的回调函数,多次改变时,只会触发一次,并且是异步执行。

实现思路:为 data中的每一个属性都创建一个 Dep实例来存储 Watcher实例,在属性的 getter函数中收集依赖,setter函数中派发任务。当属性改变时会触发 setter函数,进而对 Dep实例中的 Watcher实例进行循环调用触发回调。

Dep

Dep实例主要依赖收集依赖和派发任务。

class Dep {
  // 存储 watcher
  subs = []

  // 收集 watcher
  depend() {
    if (Dep.target) {
      this.subs.push(Dep.target)
    }
  }

  // 调用 watcher的run方法触发回调
  notify() {
    for (const watcher of this.subs) {
      // 触发回调
      watcher.run()
    }
  }
}

// ...

function defindReactive(target, key, value) {
  observer(value)
  const dep = new Dep() // 为每一个属性创建dep

  Object.defineProperty(target, key, {
    get() {
      dep.depend() // 收集依赖
      return value
    },
    set(val) {
      if (val === value) return
      dep.notify() // 派发任务
      value = val
    }
  })
}

Watcher

Watcher实例主要用来存储回调并触发。在实例化时将实例挂载到 Dep.target上方便对其进行获取,再读取属性触发属性的 getter方法,从而对watcher进行收集。

class Watcher {
  constructor(vm, key, cb) {
    this.vm = vm
    this.key = key
    this.cb = cb

    this.get()
  }

  get() {
    Dep.target = this
    this.vm[this.key] // 读取属性,触发属性的 getter函数
    Dep.target = null
  }

  run() {
    this.cb.call(this.vm)
  }
}

初始化

vue有两种方法创建侦听器,一是在 watch对象中定义,二是调用 $watch方法创建。

class Vue {
  constructor(options) {
    // 略...
    this.initData()
    this.initWatch() // 在 initData后调用,需要依赖 initData创建的 Dep实例。
  }

  // 略...

  initWatch() {
    const watch = this.$options.watch
    if (!watch) return

    const keys = Object.keys(watch)
    for (const key of keys) {
      this.$watch(key, watch[key])
    }
  }

  $watch(key, cb) {
    new Watcher(this, key, cb)
  }
}

改进

此时 watch的主要功能已经实现,但还有两点需要改进.。

  1. watch的回调是异步调用
  2. 多次触发时,只会执行一次

异步

通过 Promise的 then方法将回调加入到异步队列 或 queueMicrotask

run() {
  Promise.resolve().then(() => {
    this.cb.call(this.vm)
  })
}

多次触发

实现思路:定义一个数组来收集待执行的 watcher的id,如果已经在数组中,则不再触发回调。

let watcherId = 0
const watcherQueue = []

class Watcher {
  constructor(vm, key, cb) {
	// ...
    this.id = watcherId++
    // ...
  }

  run() {
    if (watcherQueue.includes(this.id)) return
    watcherQueue.push(this.id) // 数组中添加待执行 watcher的id

    Promise.resolve().then(() => {
      this.cb.call(this.vm)
      // 回调执行完,在数组中删除对应watcher的id
      watcherQueue.splice(watcherQueue.indexOf(this.id), 1)
    })
  }
}

完整代码

class Vue {
  constructor(options) {
    const data = options.data
    this.$options = options
    this._data = typeof data === 'function' ? data() : data

    this.initData()
    this.initWatch()
  }

  initData() {
    const data = this._data
    observer(data)

    const keys = Object.keys(data)
    for (const key of keys) {
      Object.defineProperty(this, key, {
        get() {
          return data[key]
        },
        set(value) {
          data[key] = value
        }
      })
    }
  }

  initWatch() {
    const watch = this.$options.watch
    if (!watch) return

    const keys = Object.keys(watch)
    for (const key of keys) {
      this.$watch(key, watch[key])
    }
  }

  $watch(key, cb) {
    new Watcher(this, key, cb)
  }
}

function observer(data) {
  const dataType = Object.prototype.toString.call(data)

  if (dataType !== '[object Object]' && dataType !== '[object Array]') {
    return
  }

  // 对象、数组处理...
  new Observer(data)
}

function defindReactive(target, key, value) {
  // 递归观察 value
  observer(value)
  const dep = new Dep()

  Object.defineProperty(target, key, {
    get() {
      dep.depend()
      return value
    },
    set(val) {
      if (val === value) return
      dep.notify()
      value = val
    }
  })
}

class Observer {
  constructor(data) {
    if (Array.isArray(data)) {
      data.__proto__ = arrayMethods
      this.observeArray(data)
    } else {
      this.walk(data)
    }
  }

  // 处理对象
  walk(data) {
    const kyes = Object.keys(data)

    for (const key of kyes) {
      defindReactive(data, key, data[key])
    }
  }

  // 处理数组
  observeArray(arr) {
    arr.forEach(observer)
  }
}

class Dep {
  subs = []

  depend() {
    if (Dep.target) {
      this.subs.push(Dep.target)
    }
  }

  notify() {
    for (const watcher of this.subs) {
      watcher.run()
    }
  }
}

let watcherId = 0
const watcherQueue = []

class Watcher {
  constructor(vm, key, cb) {
    this.vm = vm
    this.key = key
    this.cb = cb
    this.id = watcherId++

    this.get()
  }

  get() {
    Dep.target = this
    this.vm[this.key]
    Dep.target = null
  }

  run() {
    if (watcherQueue.includes(this.id)) return
    watcherQueue.push(this.id)

    Promise.resolve().then(() => {
      this.cb.call(this.vm)
      watcherQueue.splice(watcherQueue.indexOf(this.id), 1)
    })
  }
}

const methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sotr', 'splice']
const arrayMethods = methods.reduce((obj, method) => {
  obj[method] = function (...args) {
    const res = Array.prototype[method].apply(this, args)

    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
      default:
        break
    }
    inserted?.forEach(observer)

    return res
  }

  return obj
}, Object.create(Array.prototype))

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值