《源码系列》助你理解vue响应式源码——实现Observer数据劫持

前言

在上一节我们实现了Compile类来进行视图的初始化 时光机这一节来实现一下对所有数据的劫持监听Observer类,相对来说较为简单。

Observer语法

实现数据劫持必不可少的就是使用Object.defineProperty方法(V2),所以在实现Observer之前先来了解一下它。

定义

Object.defineProperty() 静态方法会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。

参数

该方法有三个参数分别为对象本身,要监听的属性,属性描述符,其中属性描述符又分为数据描述符和存取描述符,存取描述符顾名思义就是get set。数据描述符中有value(默认值)enumberable(是否可枚举)writable(是否可以被修改)configurable(是否可删除)需要注意的是描述符对象中不能同时存在valueget或者writableset,原因也很简单,如果设置了valueget将会一直调用get方法。

形式

js 复制代码
const obj = {};
Object.defineProperty(obj, "name", {
  enumerable: false,
  configurable: false,
  writable: false,
  value: "skyler",
});

缺点

该方法也存在一些弊端,也是vue中存在的并且改用proxy来进行响应式的原因之一。

1、不能监听添加的额外属性或者删除属性;

js 复制代码
const obj = { foo: 'hello' };

Object.defineProperty(obj, 'foo', {
  get() {
    console.log('get foo');
    return this._foo;
  },
  set(value) {
    console.log('set foo');
    this._foo = value;
  }
});

obj.bar = 'world'; // 无法被监听到
delete obj.foo; // 无法被监听到

2、不能监听数组下标的变化

js 复制代码
const arr = [1, 2, 3];
Object.defineProperty(arr, '0', {
  get() {
    console.log('get 0');
    return this._0;
  },
  set(value) {
    console.log('set 0');
    this._0 = value;
  }
});

arr[0] = 0; // 可以被监听到
arr.push(4); // 无法被监听到

3、性能问题

使用Object.defineProperty监听数据变化,需要为每个属性都创建一个 settergetter,这会带来一定的性能问题,特别是当数据对象较大时。

实现Observer数据劫持

作用

  1. 劫持监听所有属性

实现

新建Observer.js文件并在index.html文件中进行引入

js 复制代码
<script src="./Observer.js"></script>
<script src="./Mvue.js"></script>

然后在Mvue.js文件中给Observer类传递要监听的数据源

js 复制代码
class MVue {
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;
        this.$options = options;
        if (this.$el) {
            // 1、实现一个数据观察者
            new Observer(this.$data);
            // 2、实现一个指令解析器
            new Compile(this.$el, this)
        }
    }
}

创建Observer类,判断传递过来的数据是否为对象,这里只考虑data为对象的情况,如果是对象就获取到对象中的每一个属性并传入defineReactive中进行监听,该方法就是用来定义响应式数据的。

js 复制代码
class Observer {
    constructor(data) {
        this.observer(data)
    }
    observer(data) {
        if (data && typeof data == 'object') {
            Object.keys(data).forEach(key => {
                this.defineReactive(data, key, data[key])
            })
        }
    }
 }

考虑到person:{name:'Joker'}的这种情况也就是对象中还有对象的情况时,需要对其进行递归遍历,目的就是要为每个属性添加上响应式。

js 复制代码
   defineReactive(obj, key, value) {
        // 为每个属性添加响应式
        this.observer(value)
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get() {
                return value
            },
            set:(newVal)=> {
                if (newVal != value) {
                    value = newVal
                }
            }
        })
   }

这时候我们在控制台中测试一下可以发现已经为每个属性添加上了getset,但是如果为其重新赋值一个对象的话并没有被监听到,解决方法也很简单,就是在做set操作的时候重新调用一下observer方法并传入新的值即可。

js 复制代码
     set: (newVal) => {
       this.observer(newVal)
          if (newVal != value) {
              value = newVal
          }
      }

到此为止数据劫持这部分就做完了,下一步就是要为每个属性创建观察者,当数据发生变化时要去通知观察者更新视图,观察者会有很多所以我们会使用订阅器Dep来对它进行收集并且还要与Observer进行关联,当前的首要问题就是在哪里以及如何创建观察者呢?这部分我们放到下一章讲解。

相关推荐
掘了3 分钟前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 分钟前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅28 分钟前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment1 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅1 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊1 小时前
jwt介绍
前端
爱敲代码的小鱼1 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
吹牛不交税2 小时前
admin.net-v2 框架使用笔记-netcore8.0/10.0版
vue.js·.netcore
Cobyte2 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc