《源码系列》助你理解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进行关联,当前的首要问题就是在哪里以及如何创建观察者呢?这部分我们放到下一章讲解。

相关推荐
小酒星小杜6 小时前
在AI时代,技术人应该每天都要花两小时来构建一个自身的构建系统 - 总结篇
前端·vue.js·人工智能
燕山石头6 小时前
jeecg统一异常处理根据不同模块返回指定响应信息
前端
PyHaVolask6 小时前
CSRF跨站请求伪造
android·前端·csrf
程序员海军6 小时前
我的2025:做项目、跑副业、见人、奔波、搬家、维权、再回上海
前端·程序员·年终总结
我来整一篇6 小时前
[Razor] ASP.NET Core MVC 前端组件快速使用总结
前端·asp.net·mvc
P7Dreamer6 小时前
微信小程序处理Range分片视频播放问题:前端调试全记录
前端·微信小程序
RedHeartWWW6 小时前
初识next-auth,和在实际应用中的几个基本场景(本文以v5为例,v4和v5的差别主要是在个别显式配置和api,有兴趣的同学可以看官网教程学习)
前端·next.js
C_心欲无痕6 小时前
前端页面中,如何让用户回到上次阅读的位置
前端
C_心欲无痕7 小时前
前端本地开发构建和更新的过程
前端
Mintopia7 小时前
🌱 一个小而美的核心团队能创造出哪些奇迹?
前端·人工智能·团队管理