vue 响应式原理模拟2 - Observer

我们已经实现了 Vue 接收存储参数并将 $data 中的属性转换成 getter/setter,注入到 vue 实例中。

在上篇中我们也提到过 $data 中的 setter 是真正监视数据变化的地方 ,所以还需要将 $data 中的数据转换成 getter/setter,当触发其 set 方法修改数据时发出通知。

创建 Observer

所以我们要创建 Observer 类,在其中实现以下功能:

  1. $data 中的属性转换成响应式对象
  2. $data 中的属性如果是对象,则该对象的属性也要转换为响应式对象
  3. $data 中的数据发生变化时发送通知

我们模拟 vue 中的写法,定义 walkdefineReactive 方法,在 walk 中循环调用 defineReactive 方法,在 defineReactive 方法中使用 Object.defineProperty 将数据转换为 getter/setter

js 复制代码
class Observer {
    constructor (data) {
      this.walk(data)
    }
    walk (data) {
      // 判断data是否是对象
      if (!data || typeof data !== 'object') return
      Object.keys(data).forEach(key => {
        this.defineReactive(data, key, data[key])
      })
    }
    defineReactive (obj, key, val) {
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get () {
          return val
        },
        set (newValue) {
          if (newValue === val) return
          val = newValue
          // 发送通知
        }
      })
    }
  }

使用 Observer 类

vue 类中创建 observer 实例

js 复制代码
this._proxyData(this.$data)
+ new Observer(this.$data)

html 中引入 observer.js 文件

通过以上两步就引入了 observer,此时在控制台上打印 vm,就可以看到 $data 中的属性也已经转换成了 getter/setter

defineReactive 的第三个参数

js 复制代码
Object.keys(data).forEach(key => {
    // 有 data 和 key,为什么还要传递 data[key] ?
    this.defineReactive(data, key, data[key])
})

在调用 defineReactive 方法的时候,有 datakey,为什么还要传递 data[key],是因为我们在使用 Object.definePropertyget 方法的时候需要用到 val,我们试着将这个 val 改成 obj[key] 试试:

js 复制代码
get () {
  // return val
  return obj[key]
},

然后在 html 中打印 vm.msg 的值,在控制台上我们可以看到如下的错误: 这个错误说明在我们获取 vm.msg 的时候发生了死循环,造成了堆栈溢出。

当我们获取 vm.msg 的时候首先执行了 vm.msgget 方法,此方法中返回的是 $data.msg ,所以我们又触发了 $data.msgget 事件,如果返回 obj[key] (就是 $data.msg),那就会循环触发 $data.msgget 事件。

其实重点在于在 Object.defineProperty 中定义 get 方法前从 obj[key] 中获取到值,而不是每次 get 的时候再去获取,也不一定需要传参过来,只要在此之前获取即可。

由于 Object.defineProperty 中使用的 obj 就是 $data,这是定义在 vm.$data 上的值,他不会销毁,所以形成了闭包 ,保证 get 方法中的 val 一直存在于闭包中。

嵌套对象转换为响应式

当前 data 中的 msgcount 都是原始值,如果 data 中存在对象,那么对象中的属性暂时还不是响应式的,我们需要将嵌套对象中的属性也改变为响应式的。

js 复制代码
defineReactive (obj, key, val) {
    // 如果val是对象,把val内部的属性转换成响应式数据
    + this.walk(val)
    Object.defineProperty...
}

这样可以保证初始化的时候如果属性为对象,那么循环调用 walk 方法将内部属性都转换成 getter/setter。但是当我们重新给 data 中的值赋值为对象的时候,这里的对象就不是响应式的了,所以我们还需要在修改值的时候将对象属性的内部也转换为响应式的。

js 复制代码
defineReactive (obj, key, val) {
    + let that = this
    this.walk(val)
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get () {
        return val
      },
      set (newValue) {
        if (newValue === val) return
        val = newValue
        + that.walk(newValue)
        // 发送通知
      }
    })
  }
相关推荐
ℋᙚᵐⁱᒻᵉ鲸落3 小时前
移动端滑动手势冲突:overflow: auto导致外层横向滑动失效
前端·javascript·css·vue.js·html
默_笙4 小时前
🎃 前端学了 Next.js,后端该学啥?NestJS 就是 Node 版的蜜雪冰城
前端·javascript
前端snow5 小时前
ai agent --- 实现 openclaw 定时间效果
前端
码云之上5 小时前
换模型之后,Chatbot 为什么要自己做 compact?
前端·agent·前端工程化
星栈6 小时前
自定义 UI-UX-Pro-Max 的 CSV 知识库,把 AI 生成的后台拉回行业该有的样子
前端·agent·weui
研☆香6 小时前
简单的图片上传 删除 预览
前端
ITresearchGuest6 小时前
我用 AI 一小时写了一个世界杯数据可视化平台|前端 VibeCoding 初体验
前端·人工智能·信息可视化
慧一居士7 小时前
Vite项目中使用Less步骤,详细使用示例
前端·css
花间相见8 小时前
【LangChain组件03】—— LangChain Agents 执行与状态:工作流程与状态管理实战
java·前端·langchain
kyriewen8 小时前
面试官让我用 AI 重构一个 8 年陈的 React 组件——他说他不看代码,只看我会不会拆
前端·javascript·面试