vue2数据响应式原理(7) 收集依赖,用get和set叙述出最基础的至高vue哲学

news2024/12/28 21:20:40

收集依赖在整个数据响应式中算是比较难的
首先 要理解这里所指的依赖
依赖 可能vue项目做多了就会想到 npm i 但其实跟这个是没有什么关系的

我们这里所指的依赖 是用到数据的地方
什么地方用到数据 什么地方就是依赖 简单说 就是依赖这个响应式数据

首先 我们看一下 vue1 和 vue2的区别

vue1的话 是 细粒度的依赖 他是 你在dom中 {{ 响应式数据 }} 这样去使用了 他就叫依赖

vue2的话 做了一定的优化 他 中等粒度的依赖 就是 看组件有没有用
在这里插入图片描述
而有一句非常 哲学 的话 在get中收集依赖 在set中触发依赖

简单说 用到这个数据的地方 就会触发get 谁触发get 谁就是依赖
而在set中 触发依赖 去更新依赖的数据

然后呢 关于实现原理 我这里就不直接讲了 开始我看到那个人都麻了 非常的蒙
我打算用我慢慢理解这个东西的顺序讲述出来 帮助大家更好的理解

先打开我们一直在写的案例 然后 在src里面创建一个
Dep.js
先创建一个类的结构

export default class Dep {
    constructor(value) {
        
    }
};

然后 再在src下创建一个 Watcher.js
也是写一个类结构

export default class Watcher {
    constructor(value) {
        
    }
}

然后 我们找到dataResp.js

现在最上面引入一下Dep 这个类

import Dep from './Dep.js';

然后在Observer类中这样改一下

class Observer{
    constructor(value) {
        this.dep = new Dep();
        //相当于  给拿到的对象  其中的__ob__绑定 值为thsi,在类中用this 表示取实例本身给__ob__赋值  最后一个enumerable为false 表示属性不参与for遍历
        def(value,'__ob__',this,false);
        if(Array.isArray(value)){
            Object.setPrototypeOf(value, arrayMethods);
            this.observeArray(value);
        }else{
            this.walk(value);
        }
    }
    walk(value) {
        for(let key in value){
            defineReactive(value,key);
        }
    }
    observeArray(arr) {
        for(let i = 0;i < arr.length;i++) {
            observe(arr[i]);
        }
    }
}

就是 先简答通过new 存了一下 Dep类对象 这里就涉及一个概念

每一个Observer的实例中都有一个Dep实例

而 这里 我们知道 每一个响应式的对象中 都会有一个 ob 而他存的就是 他走到Observer中new的实例对象

除此之外 还有 两个地方需要dep 还在 dataResp 文件下 我们的defineReactive修改函数体如下

const defineReactive = function(data,key,val) {
    const dep = new Dep();
    if(arguments.length == 2){
        val = data[key];
    }

    let subset = observe(val);

    Object.defineProperty(data,key,{
        enumerable: true,
        configurable: true,
        get() {
            console.log(`您正在获取${key}的值`);
            return val
        },
        set(value) {
            console.log(`您正在修改${key}的值,更改后的值为${value}`);
            if(value == val) {
                return
            }
            val = value;
            subset = observe(value);
            dep.notify();
        }
    });
}

我们这里 new得到一个Dep类对象 然后 在set时调用类中的notify

还有就是 既然响应式数据需要监听 我们的数组自然也需要
找到Arrays.js 改写代码如下

import { def } from './def.js';

const arrayPrototype = Array.prototype;

export const arrayMethods = Object.create(arrayPrototype);

const redefineArrayMethod = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse'
]

redefineArrayMethod.forEach(item =>{
    const backupFunction = arrayPrototype[item];
    def(arrayMethods,item,function(){
        const result = backupFunction.apply(this, arguments);
        const ob = this.__ob__;
        const args = [...arguments];
        let inserted = [];
        switch (item) {
            case "push":
            case "unshift":
            inserted = args;
            break;
            case "splice":
                inserted = args.slice(2);
            break;
        }
        if(inserted){
            ob.observeArray(inserted);
        }
        console.log('数组执行了',item,'操作,值被修改为',this);

        ob.dep.notify();

        return result;
    },false);
})

因为我们对象上面是有 ob 这个属性的 我们直接通过const ob = this.ob;获取到 这就是个Observer对象 然后我们直接通过它去用dep下面的notify就好了

那么 首先 Dep 我们现在还是一个空的类 你这样直接调他里面的notify肯定报错 我们来写一下Dep.js

var uid = 0;
export default class Dep {
    constructor(value) {
        this.subs = [];
        this.id = uid++;
    }
    addSub(sub) {
        this.subs.push(sub);
    }
    depend(){
        if (Dep.target) {
            this.addSub(Dep.target);
        }
    }
    notify() {
        const subs = this.subs.slice();
        for (let i = 0;i < subs.length; i++) {
            subs[i].update();
        }
    }
};

首先 这个uid的作用在于每个id用于区分 第一个自然是 1 然后没实例一个 就会加一

然后 subs 则是用来存放订阅的数据的

然后 相对 我们的depend 也比较需要理解 这一块 先判断 拿不拿到当前的目标 Dep.target 这就是收集依赖的方法 谁触发了 get谁就是依赖 我们就需要通过Dep.target拿到当前这个目标节点 然后 通过addSub将这个依赖加到订阅数组里

那么 get收集依赖 我们自然就要改一下dataResp.js下defineReactive下get的代码了

const defineReactive = function(data,key,val) {
    const dep = new Dep();
    if(arguments.length == 2){
        val = data[key];
    }

    let subset = observe(val);

    Object.defineProperty(data,key,{
        enumerable: true,
        configurable: true,
        get() {
            if (Dep.target) {
                Dep.depend();
                if(subset) {
                    subset.dep.depend();
                }
            }
            console.log(`您正在获取${key}的值`);
            return val
        },
        set(value) {
            console.log(`您正在修改${key}的值,更改后的值为${value}`);
            if(value == val) {
                return
            }
            val = value;
            subset = observe(value);
            dep.notify();
        }
    });
}

其实也就是在get中 判断当前有没有Dep.target这个目标节点 如果有就调一下depend存一下当前依赖订阅
然后判断 子集subset有没有 有的话 就也一起调用了

然后 我们写一下Watcher.js

import Dep from './Dep.js';
var uid = 0;

export default class Watcher {
    constructor(target, expression, callback) {
        this.id = uid++;
        this.target = target;
        this.getter = parsePath(expression);
        this.callback = callback;
        this.value = this.get();
    }
    update() {
        this.run();
    }
    get() {
        var value;
        Dep.target = this;
        const obj = this.target;
        try {
            value = this.getter(obj);
        } finally {
            Dep.target = null;
        }

        return value;
    }
    run() {
        this.getAndInvoke(this.callback);
    }
    getAndInvoke(cb) {
        const value = this.get();
        if (value !== this.value || typeof value == "object") {
            const oldValue = this.value;
            this.value = value;
            cb.cal1(this.target, value, oldValue);
        }
    }
}
function parsePath(str) {
    var segments = str.split( ' .' );
    return (obj) => {
        for(let i = 0 ; i < segments.length; i++) {
            if (!obj) return;
            obj = obj[segments[i]]
        }
        return obj;
    }
}

最后 我们来实例化监听一个依赖实例收集
来到src下的 output.js 编写代码如下

import { observe } from "./dataResp"
import Watcher from "./Watcher"
const output = () => {
    var obj = {
        data: {
            data: {
                map: {
                    dom: {
                        isgin: true
                    }
                },
                arg: 13
            },
            name: "小猫猫"
        },
        bool: [1,2,3,4]
    };
    observe(obj);
    new Watcher(obj, "data.data.arg",(res) =>{
        console.log("arg的值被改为了"+res);
    })
    obj.data.data.arg = 24;
    document.getElementById("text").innerHTML = obj.data.name;
}

export default output

这里 我们new了一个Watcher类实例 他弟第一个参数 要监听那个对象 我们传了obj 第二个 要监听哪一个具体字段 这里 我们指向了 data.data.arg
这里 大家可以仔细看一下Watcher中的parsePath函数 做的正式 一层一层去把他找到的一个事情 然后 第三个参数 当数据改变触发依赖时 要做的事情

这里 我们简单输出了一下 arg的值被改为了 加他的新的值
监听完 我们也是马上写了 obj.data.data.arg = 24; 去修改监听的值来触发依赖
我们运行代码
在这里插入图片描述

然后 我们再改成这样

import { observe } from "./dataResp"
import Watcher from "./Watcher"
const output = () => {
    var obj = {
        data: {
            data: {
                map: {
                    dom: {
                        isgin: true
                    }
                },
                arg: 13
            },
            name: "小猫猫"
        },
        bool: [1,2,3,4]
    };
    observe(obj);
    new Watcher(obj, "data.data.arg",(res) =>{
        console.log("arg的值被改为了"+res);
    })
    new Watcher(obj, "data.name",(res) =>{
        console.log("name的值被改为了"+res);
    })
    obj.data.data.arg = 24;
    obj.data.name = "大猫猫";
    obj.data.data.arg = 24;
    document.getElementById("text").innerHTML = obj.data.name;
}

export default output

同一时间多监听一个 并来回调一次
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/465358.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux初阶】进程状态 | Linux下常见进程状态讲解 进程循环打印方法 ls>makefile指令 makefile$@^特殊符号的应用

&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f; &#x1f36d;&#x1f36d;系列专栏&#xff1a;【Linux初阶】 ✒️✒️本篇内容&#xff1a;进程状态的概念&#xff0c;进程状态在普遍操作系统层面和Linux层面的理解&#xff0c;Linux常见进程状态&…

【论文阅读】轻量化网络MobileNet-V1

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、摘要二、MobileNet-V1核心点介绍&#xff1a;普通卷积和深度可分离卷积三、两个超参数四。后续实验 前言 今天重温一下轻量化经典论文MobileNet-V1&#x…

拿下车载激光雷达份额「第一」,图达通寻求「变阵」

在2022年的量产元年之后&#xff0c;激光雷达正尝试进入真正大规模量产周期。在此之前&#xff0c;有一些关键问题需要解决&#xff0c;其中包括&#xff1a;其一&#xff0c;帮助主机厂将激光雷达真正用起来&#xff0c;发挥价值&#xff1b;其二&#xff0c;丰富产品品类&…

【C语言】文件的相关操作(一文10分钟彻底弄懂)

前言&#xff1a; 欢迎各位童学来到本文&#xff0c;本文将主要通过一个实战案例&#xff08;奥运会奖牌&#xff09;来帮助各位小伙伴们熟悉并掌握文件的相关操作&#xff0c;相信经过一个实战案例的小项目后大家对文件的相关操作应该都能够轻车熟路了&#xff01; &#x1f…

仔细观察Binder和mmap;分析Android进程间通信

前言 Binder是Android系统中的一种IPC&#xff08;进程间通信&#xff09;机制&#xff0c;它使得不同进程中的组件能够互相交互和通信。在Binder中&#xff0c;一个进程中的客户端和另一个进程中的服务器之间通常通过Binder驱动程序进行通信。这种通信方式能够提供安全性和效…

材料写作技巧:关于“新”排比句40例

1.是新时代新征程举旗定向的“宣言书”&#xff0c;是新时代新征程伟大思想的“领航标”&#xff0c;是新时代新征程推进中国式现代化的“动员令”&#xff0c;是新时代新征程自我革命的“冲锋号”。 2.胸怀家国&#xff0c;树立远大理想&#xff0c;奋力担当新时代青年使命&a…

真题详解(数据流图平衡)-软件设计(五十九)

真题详解&#xff08;磁盘&#xff09;-软件设计&#xff08;五十八)https://blog.csdn.net/ke1ying/article/details/130376289 如何保证数据流图平衡&#xff1f; 父图中输入流输出流的名称和数目必须和子图的相同。 父图中一条输入输出流可以对应子图几条输入输出流&…

MATLAB实现OCR自动阅卷,识别答题卡进行成绩统计

利用MATLAB进行答题卡识别编程设计&#xff0c;最主要的是实现了将答题卡中被填涂的答案提取出来&#xff0c;然后与标准的答案进行比对。通过相关的算法&#xff0c;算出考生填涂正确的题数&#xff0c;并统计计算后的得分。 每种答题卡都有很明显的助识别标记&#xff0c;像…

LeetCode - 494 目标和

目录 题目来源 题目描述 示例 提示 题目解析 算法源码 题目来源 494. 目标和 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你一个整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 或 - &#xff0c;然后串联起所有整数&#xff0c;可以构造一个…

MathType如何成功插入到word

有时候我们重装mathtype的时候&#xff0c;我们的word里嵌入的mathtype没有了&#xff0c;因此我们如何让它重新出来呢&#xff1f;下面我们来看看。 1、我们打开word&#xff0c;点击“选项”&#xff0c;点击“加载项”找到如图所示的路径内容&#xff08;根据自己电脑的实际…

IDA 知识汇总

工具使用-IDA从入门到理解 - 简书作者ID:leishi-yanmu IDA对于各位师傅应该无需简介了,如果写的不对的地方,还望师傅们多多包涵。讲解的时候会涉及到笔者在学习和使用时候的理解。 启动界面介绍: ...https://www.jianshu.com/p/190805574432[原创]【iOS逆向与安全】利用IDA…

实现网页顶部线性加载进度条

插件一&#xff1a;NProgress.js 下载链接&#xff1a;https://github.com/rstacruz/nprogress 插件二&#xff1a;MProgress.js 下载链接&#xff1a;https://github.com/lightningtgc/mprogress.js/ 这两个插件都是实现网页加载进度条&#xff0c;并且默认方法有四个&…

kitti数据集预处理

kitti数据集预处理 0.引言0.1.calib0.2.oxts(gps/imu)0.3.velodyne0.4.image_2/30.5.kitti-step/panoptics0.6.label 1.create_kitti_depth_maps2.create_kitti_masks3.create_kitti_metadata4.extract_dino_features5.run_pca 0.引言 官网参考链接1参考链接2 注&#xff1a;…

Linux基础指令(1)

Linux的基础指令 对于Linux的学习&#xff0c;先从指令开始学&#xff0c;我们先了解操作系统的一般性概念&#xff0c;然后对于Linux的一些基本的指令进行学习&#xff0c;最后我们发现Linux实际上是一个多叉树的目录结构 文章目录 Linux的基础指令操作系统操作系统是什么&am…

数据可视化开源工具软件

数据可视化工具用于通过图形、图表、表格、地图和其他详细的视觉对象来表示信息。 它们通常将数据呈现和分析结合起来&#xff0c;以帮助专业人员在数据驱动领域(如工程、数据科学和业务分析)做出更明智的决策。 选择正确的数据可视化工具将帮助您减少数据错误&#xff0c;并…

基于本地知识构建简易的chatPDF

Langchain chatglm-6b 文章目录 Langchain chatglm-6b前言一、实验记录1.1 环境配置1.2 代码理解1.3 补充内容 二、总结 前言 介绍&#xff1a;一种利用 ChatGLM-6B langchain 实现的基于本地知识的 ChatGLM 应用 Github: https://github.com/imClumsyPanda/langchain-Chat…

阳光开朗孔乙己,会否奔向大泽乡

前言 &#x1f525;学历对职业关系到底有什么影响呢&#xff1f;&#x1f525;学历给我们带来了优势吗&#xff1f;&#x1f525;到底是什么造成了"孔乙己的长衫"&#xff1f; 孔乙己是中国清代作家鲁迅创作的一篇短篇小说&#xff0c;发表于1919年。这部作品被认为是…

Blender 建模案例一(2)

目录 1. 烛台基座1.1 导入图片1.2 从立方体取一个顶点1.3 用点描边1.4 旋转1.5 实体化修改器1.6 删除内部正面1.7 封盖1.8 平滑着色1.9 表面细分修改器1.10 环切线&#xff08;卡线&#xff09; 1. 烛台基座 1.1 导入图片 1.2 从立方体取一个顶点 中间顶点尽量也X轴平行 1.…

FPGA入门系列5--运算符号

文章简介 本系列文章主要针对FPGA初学者编写&#xff0c;包括FPGA的模块书写、基础语法、状态机、RAM、UART、SPI、VGA、以及功能验证等。将每一个知识点作为一个章节进行讲解&#xff0c;旨在更快速的提升初学者在FPGA开发方面的能力&#xff0c;每一个章节中都有针对性的代码…

( 栈和队列) 739. 每日温度 ——【Leetcode每日一题】

❓739. 每日温度 难度&#xff1a;中等 给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &#xff0c;其中 answer[i] 是指对于第 i 天&#xff0c;下一个更高温度出现在几天后。如果气温在这之后都不会升高&#xff0c;请在该位置…