Vue源码分析(响应式)

20 篇文章 1 订阅

Vue源码分析(响应式)

写在前面的话:当我们在使用vue的时候,可能会非常好奇,为什么vue能实现这种响应式的数据更新,为什么可以动态渲染。

这篇文章并不是直接对vue的源码进行阅读,而是通过一些小demo理解vue的作用原理。参考往年尤雨溪的公开课程。

getter-setter

我们首先要实现的就是vue的核心数据监听,如何监听对象中属性的变化并修改它。相信大家也都了解,就是Object.defineProperty(vue2)

定义一个convert函数,实现对象的getter和setter监听

// 代码中api的用法请参考MDN
function convert(obj) {
  Object.keys(obj).forEach(key => {
    // 创建internalValue存储对象的值
    let internalValue = obj[key]
    Object.defineProperty(obj, key, {
      get() {
        console.log(`getting key "${key}":${internalValue}`);
        return internalValue
      },
      set(newValue) {
        console.log(`setting key "${key}"to:${newValue}`);
        internalValue = newValue
      }
    })
  })
}

测试效果

let obj = {
  name: 'mkbird',
  age: 20
}
convert(obj)
obj.name // should log: 'getting key "name":mkbird '
obj.age = 21 // should log:' setting key "age"to:21 '
console.log(obj) // { age:21, name:'mkbird' }

观察上面的代码就可以发现使用Object.defineProperty,obj有多少个属性就要绑定多少次,而且还有一些限制,于是在vue3中使用了proxy

依赖跟踪

创建一个Dep依赖跟踪类,它包含两个方法

  • depend用于收集依赖项
  • notify用于触发依赖项执行

下面是Dep类的效果,调用dep.depend收集依赖,当调用dep.notify时,会再次执行依赖项

autorun函数是接收一个函数,这个函数帮助我们创建一个响应区,当代码放在这个响应区内,就可以通过dep.depend方法注册依赖项

// 即我们期望的功能为
const dep = new Dep()

autorun(() => {
  dep.depend()
  console.log('updated')
})
// should log: "updated"

dep.notify()
// should log: "updated"

我们可以将上面的过程理解为发布订阅模式
请添加图片描述

整个代码分为两个部分

  1. 能全局保存依赖项的autorun函数
  2. Dep类创建订阅任务队列,添加依赖项和执行依赖项
window.Dep = class Dep {
  constructor() {
    // 订阅任务队列
    this.subscribers = new Set()
  }
  // 用于注册依赖项
  depend() {
    if (activeUpdate) {
      this.subscribers.add(activeUpdate)
    }
  }
  // 用于发布消息,触发依赖项重新执行
  notify() {
    this.subscribers.forEach(subscriber => subscriber())
  }
}

// 下面代码的作用
// 将当前执行的wrappedUpdate存入activeUpdate全局变量中,用于之后注册依赖
// 执行update函数本身
let activeUpdate
function autorun(update) {
  const wrappedUpdate = () => {
    activeUpdate = wrappedUpdate
    update()
    activeUpdate = null
  }
  wrappedUpdate()
}
// 上面为什么要在autorun中嵌套wrappedUpdate呢
// 因为本demo只实现了最精简的功能,实际项目中wrappedUpdate还要有依赖筛选等功能

测试效果

const dep = new Dep()
// 创建想要执行的函数,并且使用dep.depend()表明我想要订阅该事件
let myFun = () => {
  dep.depend()
  console.log('updated')
}
// 在autorun区域内执行并订阅
autorun(myFun) // should log: "updated"
// 使用dep.notify()发布事件
dep.notify() // should log: "updated"
基于数据监听和依赖跟踪实现Vue的更新系统

前面已经实现了convert函数用于对象的getter和setter监听,也通过发布订阅模式实现了依赖的跟踪,接下来将二者结合实现vue的数据更新系统。

我们将convert改名为observe在getter中调用dep.depend订阅事件,在setter中调用dep.notify发布事件,即可实现vue的数据更新。

// 加一层Object判断
function isObject(obj) {
  return typeof obj === 'object'
    && !Array.isArray(obj)
    && obj !== null
    && obj !== undefined
}

// 监听
function observe(obj) {
  if (!isObject(obj)) {
    throw new TypeError()
  }
  // 遍历属性
  Object.keys(obj).forEach(key => {
    let internalValue = obj[key]
    let dep = new Dep()
    Object.defineProperty(obj, key, {
      get() {
        // 订阅
        dep.depend()
        return internalValue
      },
      set(newValue) {
        if (internalValue !== newValue) {
          internalValue = newValue
          // 发布
          dep.notify()
        }
      }
    })
  })
}

// 创建dep
window.Dep = class Dep {
  constructor() {
    this.subscribes = new Set()
  }
  depend() {
    // 订阅
    if (activeUpdate) {
      this.subscribes.add(activeUpdate)
    }
  }
  notify() {
    // 发布
    this.subscribes.forEach(subscriber => subscriber())
  }
}

// 创建事件辅助函数
let activeUpdate = null
function autorun(update) {
  function wrappedUpdate() {
    activeUpdate = wrappedUpdate
    update()
    activeUpdate = null
  }
  wrappedUpdate()
}

测试代码

const state = {
  a: 0
}
// 监听数据
observe(state)
// 创建事件
autorun(() => {
  console.log(state.a); // should log: 0
})
state.a++  // should log: 1

下一节:Vue源码分析(插件编写)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会飞的战斗鸡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值