前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入浅出 Vue :变化侦测

深入浅出 Vue :变化侦测

作者头像
Cellinlab
发布2023-05-17 14:45:55
1710
发布2023-05-17 14:45:55
举报

# Object 的变化侦测

# 变化侦测及实现

Vue.js 自动通过状态生成 DOM,并将其显示到页面,这个过程叫渲染。

在运行时应用内部的状态会不断发生变化,需要不停地渲染。确定状态发生了什么变化通过“变化侦测”实现,一般分“推”和“拉”两种。

Vue.js 的变化侦测属于“推”(push),相对于“拉”,“推”知道的信息更多,可以更细粒度的更新。当然,粒度越细,意味每个状态绑定的依赖就越多,依赖追踪在内存上的开销就会越大。所以,在后来的 Vue.js 2.0 开始,引入了虚拟 DOM,将粒度调整为中等粒度,即一个状态所绑定的依赖不再是具体 DOM,而是一个组件(状态变化后会通知组件,组件内部再使用虚拟 DOM 进行比对)。

JavaScript 中有两种方法可以侦测到变化:Object.definePropertyProxy。在 Vue.js 3.0 之前都是使用第一种方法。

/**
 * 对 Object.defineProperty 的封装
 * @param {*} data 
 * @param {*} key 
 * @param {*} val 
 */
function defineReactive (data, key, val) {
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function () {
      console.log('get value')
      return val
    },
    set: function (newVal) {
      console.log('change value')
      if (newVal === val) {
        return
      }
      val = newVal
    }
  })
}

const a = { name: 'default' }

defineReactive(a, 'name', a.name)

console.log(a.name) // get value
// default

a.name = 'new name' // change value
console.log(a.name) // get value 
// new name

a.name = 'other name' // change value
console.log(a.name) // get value
// other nam

# 收集依赖

侦测数据变化的目的是在数据变化时,及时地通知使用到数据的地方,进行对应的操作。

Vue.js 的实现方法是,将用到数据的地方都收集起来,等到数据变化时,对所有依赖触发一次通知。简单说,即在 getter 中收集依赖,在 setter 中触发依赖。

function defineReactive (data, key, val) {
  const dep = []
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function () {
      console.log('get value')
      dep.push(window.target) // 收集依赖
      return val
    },
    set: function (newVal) {
      console.log('set value')
      if (newVal === val) {
        return
      }
      // 通知依赖
      for (let i = 0; i < dep.length; i++) {
        dep[i](newVal, val)
      }
      val = newVal
    }
  })
}

window.target = (newVal, oldVal) => {
  console.log('change value', oldVal, '->', newVal)
}

const a = { name: 'default' }

defineReactive(a, 'name', a.name)

console.log(a.name) // 触发 getter 进行依赖收集
// get value
// default

a.name = 'new name' // 触发 setter 进行依赖通知
// set value
// change value default -> new name

console.log(a.name)
// get value
// new name

可以将依赖收集的逻辑封装成 Dep 类,专门用于管理依赖。

class Dep {
  constructor () {
    this.subs = []
  }

  addSub (sub) {
    this.subs.push(sub)
  }

  removeSub (sub) {
    remove(this.subs, sub)
  }

  depend () {
    if (dataConsumer) {
      this.addSub(dataConsumer)
    }
  }

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

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

function defineReactive (data, key, val) {
  const dep = new Dep()
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function () {
      dep.depend()
      return val
    },
    set: function (newVal) {
      if (newVal === val) {
        return
      }
      val = newVal
      dep.notify()
    }
  })
}

const dataConsumer = {
  data: null,
  update: function () {
    console.log('update')
  }
}

const a = { name: 'default' }

defineReactive(a, 'name', a.name)

dataConsumer.data = a.name

a.name = 'new name' // update

# 依赖是谁

收集依赖,是在收集什么?收集的是用到数据的地方,用于在数据发生变化时及时通知使用方。用到数据的地方可能是模板,也可能是用户写的一个 watch.

为了方便集中处理这些情况,抽象出一个类,在收集阶段只收集封装好的这个类的实例进来,通知时也直接通知它,然后由它去通知其他地方。

# Watcher

Watcher 作为一个中介角色,数据发生变化时通知它,它再通知其他地方。

class Watcher {
  constructor (vm, expOrFn, cb) {
    this.vm = vm
    this.getter = parsePath(expOrFn) // 执行 this.getter 可以读取到 data 中的值
    this.cb = cb
    this.value = this.get()
  }

  get () {
    dataConsumer = this
    let value = this.getter.call(this.vm, this.vm) // 触发依赖收集
    dataConsumer = 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
  }
}

# 递归侦测所有的 key

之前的实现中只侦测数据中的一个属性,需要将所有属性都侦测,可以封装一个 Observer 类。作用是将一个数据内的所有属性转换成 getter/setter 的形式,然后去追踪变化。

# Array 的变化侦测

# 变化侦测相关 API 的实现

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020/9/1,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • # Object 的变化侦测
    • # 变化侦测及实现
      • # 收集依赖
        • # 依赖是谁
          • # Watcher
            • # 递归侦测所有的 key
            • # Array 的变化侦测
            • # 变化侦测相关 API 的实现
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档