likes
comments
collection
share

教你手写实现数据绑定的响应式原理

作者站长头像
站长
· 阅读数 19

数据绑定响应式原理

前言

  • 请带着以下 2 个问题进行思考 ~
  • 为什么要探究响应式绑定的实现原理呢?
  • 理解响应式绑定的底层运行机制能带给我们什么?

那么为什么要探究响应式绑定的实现原理呢?

  1. 帮助理解 MVVM 模式:响应式绑定是 MVVM 架构的基础,掌握其原理有助于更深入地理解 MVVM 的工作方式
  2. 实现自定义数据绑定:了解原理后,我们可以自己实现一个简单的响应式数据绑定机制
  3. 分析性能优化方案:掌握原理后,可以对性能瓶颈进行分析,选择更优的实现方案
  4. 掌握核心技术要点:Object.defineProperty、代理PROXY观察者模式等是响应式关键技术
  5. 提升框架使用能力:掌握原理后可以更灵活地使用 VueReact 等框架的响应式编程

理解响应式绑定的底层运行机制能带给我们什么?

  1. 阅读结束你可以回过头想想带给你了什么 接下来让我们进入正题,,开始分析响应式绑定原理 ~

实现一个简单的响应式编程机制

  1. 响应式编程是当数据模型发生变化时,自动更新视图的一种编程范式。
  2. 它可以极大地简化状态管理。
  3. 今天我们来手动实现一个简单的响应式编程机制,加深对其工作原理的理解。
  4. 核心代码实现上,我们创建了一个 Depend 类,它用来收集依赖于某个数据的观察者函数。
  5. 我们还创建了一个 Reactive 函数,通过 ProxyObject.defineProperty 的方式把对象属性转换成 gettersetter

小二 ~ 上代码

let activeReactiveFn = null; // 响应式函数
// 定义一个数据绑定收集类
class Depend {
    constructor() {
        this.reactiveFns = new Set(); // 使用 Set 主要解决保证收集的响应式函数不重复
    }
    // 收集响应式函数
    depend() {
        if (activeReactiveFn) {
            this.reactiveFns.add(activeReactiveFn);
        }
    }
    // 通知更新
    notify() {
        this.reactiveFns.forEach((fn) => fn());
    }
}

// 响应式函数
function watchFn(fn) {
    activeReactiveFn = fn;
    fn();
    activeReactiveFn = null;
}
// 收集数据更新方法(重点~)
let targetMap = new WeakMap();
function getDepend(target, key) {
    let map = targetMap.get(target);
    if (!map) {
        map = new Map();
        targetMap.set(target, map);
    }
    let depend = map.get(key);
    if (!depend) {
        depend = new Depend();
        map.set(key, depend);
    }
    return depend;
}
// 模拟 Vue3 实现 reactive()
function reactive(obj) {
    Object.keys(obj).forEach((key) => {
        let value = obj[key];
        Object.defineProperty(obj, key, {
            get: function () {
                const depend = getDepend(obj, key);
                depend.depend();
                return value;
            },
            set: function (newValue) {
                value = newValue;
                const depend = getDepend(obj, key);
                depend.notify();
            }
        });
    });
    return obj;
}
const info = reactive({
    name: 'codewhite',
    profession: 'programmer'
});

watchFn(function () {
    console.log('-------------------第一个 name 函数开始--------------------');
    console.log('Hello World');
    console.log('info', info.profession);
    console.log('info', info.name);
    console.log('-------------------第一个 name 函数结束--------------------');
});
info.name = 'gucy';

那么上面的代码的工作流程是怎样的呢?

  • 解释代码工作流程
  1. 在调用 watchFn 注册观察者函数时,通过 activeReactiveFn 标记当前观察者函数。
  2. getter 中收集 Depend 依赖,即加到依赖项中。
  3. setter 触发时,调 notify 方法,遍历执行所有依赖函数。
  4. 这样就实现了数据变化驱动视图更新的响应式效果。
  5. 我们也可以注意到,这里我使用了 WeakMap 来存储依赖关系。因为 WeakMap 的键是弱引用,不会影响垃圾回收,可以防止内存泄漏。
  6. ProxyObject.defineProperty 都可以用来实现响应式转换,前者兼容性稍差但更简洁。我们也可以扩展功能,实现深层嵌套对象的响应式转换。
  7. 通过自己实现一个简单的响应式绑定模型,可以加深对响应式编程的理解,理解依赖收集和触发更新的内部原理。这种理解可以帮助我们更好地使用 Vue、React 等框架的响应式系统。

读到这里,是不是看见 reactive() 很熟悉呢?

  • 是的,在我们 Vue3 开发中复杂的响应式数据绑定就是通过 reactive 来实现的
  • 那么以上的代码是基于 Vue2 使用了 Object.defineProperty 来实现的,我们能不能写一个 基于 proxy 来实现呢?当然可以,接下来我们继续!

Proxy 运行的机制分析

我们先来学习了解一下 Proxy

  • Proxy 的作用
  1. 可以监听对象身上发生的所有操作,如属性读取、函数调用等。
  2. 可以实现基于对象操作的拦截和自定义操作,实现数据绑定、严格控制属性访问等高级功能。
  3. 可以作为对象的访问管道,对外部的访问进行过滤和改写。
  4. 可以扩展和改写原有对象的默认行为。
  5. 可以实现完善的对象访问日志和错误处理机制。
  • Proxy 的使用
const p = new Proxy(target, handler);
  • 参数解释
  1. target:要监控的目标对象
  2. handler:一个对象,定义了代理的行为 handler(捕获器)
  • 常用捕获器(trap)有:
    • get - 监听对象属性访问
    • set - 监听对象属性设置 
    • apply - 监听函数调用
    • construct - 监听 new 操作符
    • deleteProperty - 监听 delete 操作
    • getPrototypeOf - 监听 Object.getPrototypeOf 操作

学会了 Proxy 请看下面的实现代码吧!

function reactive(obj) {
    return new Proxy(obj, {
        get: function (target, key, receiver) {
            // 根据 target, key 获取对应的 depend
            const depend = getDepend(target, key);
            depend.depend();
            return Reflect.get(target, key, receiver);
        },
        set: function (target, key, newValue, receiver) {
            Reflect.set(target, key, newValue, receiver);
            const depend = getDepend(target, key);
            depend.notify();
        }
    });
}

结束语

学会了吧,带着上述两个问题阅读完后,可自行思考,也去写一个数据响应式玩玩吧,再会 ~