当前位置: 首页 > news >正文

【结合vue源码,分析vue2及vue3的数据绑定实现原理】

结合vue源码,分析vue2及vue3的数据绑定实现原理

      • Vue 2 数据绑定实现
        • 整体思路
        • 详细实现
          • 1. `Observer` 类:数据劫持
          • 2. `Dep` 类:依赖收集
          • 3. `Watcher` 类:订阅者
      • Vue 3 数据绑定实现
        • 整体思路
        • 详细实现
          • 1. `reactive` 函数:创建响应式对象
          • 2. 依赖收集和触发更新
          • 3. `effect` 函数:副作用收集
      • 总结

Vue 2 数据绑定实现

整体思路

Vue 2 的数据绑定主要基于 Object.defineProperty() 方法来实现数据劫持,配合发布 - 订阅模式,当数据发生变化时通知视图更新。主要涉及三个核心部分:Observer 用于数据劫持、Dep 进行依赖收集、Watcher 作为订阅者接收更新通知。
在这里插入图片描述

详细实现
1. Observer 类:数据劫持
// 定义 Observer 类,用于对对象属性进行劫持
class Observer {
    constructor(data) {
        // 遍历对象属性
        this.walk(data);
    }

    walk(data) {
        if (!data || typeof data!== 'object') {
            return;
        }
        Object.keys(data).forEach(key => {
            this.defineReactive(data, key, data[key]);
        });
    }

    defineReactive(obj, key, val) {
        // 创建一个 Dep 实例用于依赖收集
        const dep = new Dep();
        // 递归处理子对象
        new Observer(val);
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get() {
                // 收集依赖
                if (Dep.target) {
                    dep.depend();
                }
                return val;
            },
            set(newVal) {
                if (newVal === val) {
                    return;
                }
                val = newVal;
                // 对新值进行递归处理
                new Observer(newVal);
                // 通知依赖更新
                dep.notify();
            }
        });
    }
}

Observer 类会遍历对象的所有属性,使用 Object.defineProperty() 重写属性的 gettersetter。在 getter 中收集依赖,在 setter 中通知依赖更新。

2. Dep 类:依赖收集
// Dep 类用于管理依赖
class Dep {
    constructor() {
        // 存储依赖的数组
        this.subs = [];
    }

    depend() {
        if (Dep.target) {
            Dep.target.addDep(this);
        }
    }

    addSub(sub) {
        this.subs.push(sub);
    }

    notify() {
        this.subs.forEach(sub => sub.update());
    }
}
Dep.target = null;

Dep 类有一个 subs 数组来存储依赖(Watcher 实例)。depend 方法用于收集依赖,notify 方法用于通知所有依赖更新。

3. Watcher 类:订阅者
// Watcher 类作为订阅者,监听数据变化
class Watcher {
    constructor(vm, expOrFn, cb) {
        this.vm = vm;
        this.cb = cb;
        this.getter = parsePath(expOrFn);
        this.value = this.get();
    }

    get() {
        Dep.target = this;
        const value = this.getter.call(this.vm, this.vm);
        Dep.target = null;
        return value;
    }

    addDep(dep) {
        dep.addSub(this);
    }

    update() {
        const oldValue = this.value;
        this.value = this.get();
        this.cb.call(this.vm, this.value, oldValue);
    }
}

function parsePath(path) {
    const segments = path.split('.');
    return function (obj) {
        for (let i = 0; i < segments.length; i++) {
            if (!obj) return;
            obj = obj[segments[i]];
        }
        return obj;
    };
}

Watcher 类在实例化时会触发 get 方法,从而触发 getter 进行依赖收集。当数据变化时,Dep 会调用 Watcherupdate 方法,update 方法会重新获取数据并调用回调函数更新视图。

Vue 3 数据绑定实现

整体思路

Vue 3 使用 ES6 的 Proxy 对象来实现数据劫持,结合 WeakMap 进行依赖收集,相比 Vue 2 更加灵活高效。核心部分包括 reactive 函数创建响应式对象、effect 函数用于副作用收集、tracktrigger 函数进行依赖收集和触发更新。

详细实现
1. reactive 函数:创建响应式对象
// reactive 函数用于创建响应式对象
function reactive(target) {
    const handler = {
        get(target, key, receiver) {
            // 收集依赖
            track(target, key);
            return Reflect.get(target, key, receiver);
        },
        set(target, key, value, receiver) {
            const oldValue = target[key];
            const result = Reflect.set(target, key, value, receiver);
            if (oldValue!== value) {
                // 触发更新
                trigger(target, key);
            }
            return result;
        }
    };
    return new Proxy(target, handler);
}

reactive 函数使用 Proxy 对象拦截对象的 getset 操作。在 get 操作中调用 track 进行依赖收集,在 set 操作中调用 trigger 触发更新。

2. 依赖收集和触发更新
// 用于存储对象及其依赖映射的 WeakMap
const targetMap = new WeakMap();

function track(target, key) {
    let depsMap = targetMap.get(target);
    if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()));
    }
    let dep = depsMap.get(key);
    if (!dep) {
        depsMap.set(key, (dep = new Set()));
    }
    if (activeEffect) {
        dep.add(activeEffect);
    }
}

function trigger(target, key) {
    const depsMap = targetMap.get(target);
    if (!depsMap) {
        return;
    }
    const dep = depsMap.get(key);
    if (dep) {
        dep.forEach(effect => effect());
    }
}

targetMap 是一个 WeakMap,用于存储对象及其依赖映射。track 函数负责收集依赖,将 activeEffect 添加到对应的依赖集合中。trigger 函数负责触发更新,遍历依赖集合并执行其中的副作用函数。

3. effect 函数:副作用收集
let activeEffect = null;

function effect(fn) {
    const _effect = function () {
        activeEffect = _effect;
        fn();
        activeEffect = null;
    };
    _effect();
    return _effect;
}

effect 函数用于创建副作用函数,在执行副作用函数之前将其赋值给 activeEffect,以便在 track 函数中进行依赖收集。

总结

  • Vue 2:使用 Object.defineProperty() 实现数据劫持,需要递归处理对象属性,对于新增和删除属性需要额外处理。
  • Vue 3:使用 Proxy 对象实现数据劫持,能拦截更多操作,无需递归处理,对新增和删除属性的处理更加自然,性能和灵活性更高。

相关文章:

  • 【力扣hot100题】(083)零钱兑换
  • Redis 持久化机制详解:RDB/AOF 过程、优缺点及配置。Redis持久化中的Fork与Copy-on-Write技术解析。
  • android studio 2022打开了v1 签名但是生成的apk没有v1签名问题
  • C# 组件的使用方法
  • Python proteinflow 库介绍
  • Java中List方法的使用详解
  • ​​大数据量统计优化方案(日/月/年统计场景)​
  • WORD 中批量将植物拉丁名替换为斜体
  • 淘酒屋(香港)控股助力汾阳白酒国际化:开启中国酒业新征程
  • wsl-docker环境下启动ES报错vm.max_map_count [65530] is too low
  • Easy-Trans 极简数据翻译框架深度实战指南
  • 数据中台、BI业务访谈(二):组织架构梳理的坑
  • 【正点原子】一键锁定IP:STM32MP135 开机就上网!
  • C++ 调试器类 Debugger 的设计与实现
  • 用matplotlib生成一个炫酷的爱心
  • 【项目管理】第9章 项目范围管理
  • MySQL学习笔记二十
  • WebShell详解:原理、分类、攻击与防御
  • opengrok搭建与配置
  • 位掩码、哈希表、异或运算、杨辉三角、素数查找、前缀和
  • 深圳建设网站top028/软件开发需要多少资金
  • 好的结构设计网站/代运营网店公司
  • 台前网站建设/google海外版
  • javaee是做网站的?/百度官网优化
  • 响应式网站导航栏模板/浙江网站推广公司
  • 装修的网站都有哪些/华夏思源培训机构官网