Vue2是如何利用Object.defineProperty实现数据的双向绑定?

我们之前说道过Object.defineProperty方法有一关键特性,就是数据劫持,通过get/set 拦截属性的读取和修改操作。Vue主要是通过数据劫持结合发布-订阅模式来实现的,利用Object.defineProperty来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

1. Vue2 实现过程

我们来具体看一下Vue的实现过程吧

  1. 在Vue类中,接收一个options参数,里面包含data等选项,在构造函数里,我需要将data对象进行响应式处理。这一步应该是通过遍历data的所有属性,并用Object.defineProperty来重新定义它们,使得当属性被访问时收集依赖,被修改时触发更新。

  2. 对于每个属性,需要有一个Dep(依赖收集器)来管理所有的Watcher实例。每当属性被读取时,就将当前的Watcher添加到Dep中;当属性被修改时,Dep就会通知所有Watcher执行更新操作。

  3. 既然有Watcher实例,那么就有一个Watcher类,它负责订阅属性的变化,并在变化时执行回调函数,比如更新视图。在Watcher的构造函数中,需要将自己设置为全局的一个目标实例,然后触发属性的getter,从而将Watcher添加到Dep中。

  4. 还有,可能需要一个编译器(Compiler)来解析模板中的指令,比如v-model,然后初始化相应的数据绑定和事件监听。例如,对于v-model,需要将input元素的值与数据属性同步,并在输入时更新数据属性,从而触发数据的setter,进一步通知更新。

现在,我需要一步步构建这些类。首先是Vue类,它初始化数据,进行响应式处理。然后,每个数据属性都有一个对应的Dep实例。当数据被访问时,收集当前的Watcher;数据变化时,通知Dep里的所有Watcher更新。

然后,Watcher类的作用是连接数据属性和具体的更新函数,比如更新DOM。当数据变化时,Watcher会被触发,执行更新函数。

在这里插入图片描述

2. Vue2 组成结构

总体来说应有以下结构:

  1. Vue类:

作用:

  • 接收options,处理data。

  • 将data的属性转为getter/setter。

  • 调用observe函数进行响应式处理。

  1. Observer类:

作用:

  • 遍历对象的所有属性,转为响应式。

  • 每个属性有一个Dep实例。

  1. Dep类:

作用:

  • 收集Watcher实例(subs数组)。

  • 提供addSub方法添加Watcher。

  • 提供notify方法触发所有Watcher的update。

  1. Watcher类:

作用:

  • 在实例化时,将自身赋值给Dep.target。

  • 访问对应的数据属性,触发getter,从而将自身添加到Dep中。

  • 定义update方法,执行回调函数(比如更新DOM)。

接下来是具体的代码实现。可能还需要处理多个Watcher的情况,以及如何将它们正确地关联到对应的Dep。

然后,需要处理模板编译。比如,对于v-model,需要找到元素,添加事件监听,当输入时更新数据属性。同时,初始化input的值。对于{{}}文本,需要替换为对应的数据,并建立Watcher,当数据变化时更新文本内容。

不过,我们现在只是简单展示一下实现原理,直接操作DOM元素,而不是完整的编译器。例如,手动绑定一个input和显示元素,展示双向绑定的效果。

现在,我需要将上述思路转化为代码。可能先从Vue类开始:

class Vue{
    constructor(options){
        this.$options = options;
        this._data = typeof options.data === 'function' ? options.data() : options.data;

        //调用Observer函数进行响应式处理
        new Observer(this._data);
        this.compile();

    }

    compile() {

        // 处理v-model绑定

        const inputs = document.querySelectorAll('[v-model]');

        inputs.forEach(input => {

            const key = input.getAttribute('v-model').trim();

            input.value = this._data[key];

            input.addEventListener('input', (e) => {

                this._data[key] = e.target.value;

            });

        });

        // 处理{{ }} 绑定

        const elements = document.querySelectorAll('p,span,div'); // 简化处理,实际应根据文本内容匹配

        elements.forEach(element => {

            const matches = element.innerHTML.match(/\{\{\s*(.*?)\s*\}\}/);

            if (matches) {

                const key = matches[1].trim();

                new Watcher(this._data, key, (newVal) => {

                    element.innerHTML = newVal;

                });

                // 初始化显示

                element.innerHTML = this._data[key];

            }

        });

    }
}
 // Observer类:
 // 遍历对象的所有属性,转为响应式
 // 每个属性有一个Dep实例     

class Observer{

   constructor(data){
       this.walk(data);
   }

   walk(data){
       Object.keys(data).forEach(key =>{
           this.defineReactive(data,key,data[key]);
       })
       console.log(Object.keys(data))
   }

   defineReactive(obj,key,val){
       const dep = new Dep();
       Object.defineProperty(obj,key,{
           enumberable:true,
           configurable:true,
           get(){
               if(Dep.target){
                   dep.addSub(Dep.target)
               }
               return val;
           },
           set(newVal){
               if(newVal === val) return;
               val = newVal;
               dep.notify();
           }
       })
   }

}
// 收集Watcher实例(subs数组)
// 提供addSub方法添加wathcer
 // 提供notify方法触发所有的Watcher 和 update。
 class Dep{

     constructor(){
         this.subs = [];
     }
     addSub(sub){
         this.subs.push(sub);
     }
     notify(){
         this.subs.forEach(sub => sub.update())
     }
 }

 Dep.target = null;
// 在实例化时,将自身赋值给Dep.target
// 访问对应的数据属性,触发getter,从而将自身添加到Dep中。
 // 定义update方法,执行回调函数(比如更新Dom)
 class Watcher{
     constructor(data,key,cb){
         this.data = data;
         this.key = key;
         this.cb= cb;
         this.value = this.get();
     }

     get(){
         Dep.target = this; // 将当前Watcher实例赋值给Dep.target
         const value = this.data[this.key]; // 触发getter,从而将Watcher添加到Dep中
         Dep.target = null;//收集完清空
         return value;
     }

     update(){
         const newVal = this.data[this.key];
         if (newVal !== this.value) {

             this.value = newVal;

             this.cb(newVal);

         }
     }
 }

以下是完成的可运行代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>vue2数据的双向绑定</title>
</head>
<body>
    <input type="text" v-model="message">
    <div id="app">
        <p>{{ message }}</p>
    </div>
    <script>

        // 收集Watcher实例(subs数组)
        // 提供addSub方法添加wathcer
        // 提供notify方法触发所有的Watcher 和 update。
        class Dep{

            constructor(){
                this.subs = [];
            }
            addSub(sub){
                this.subs.push(sub);
            }
            notify(){
                this.subs.forEach(sub => sub.update())
            }
        }

        Dep.target = null;


        // 在实例化时,将自身赋值给Dep.target
        // 访问对应的数据属性,触发getter,从而将自身添加到Dep中。
        // 定义update方法,执行回调函数(比如更新Dom)
        class Watcher{
            constructor(data,key,cb){
                this.data = data;
                this.key = key;
                this.cb= cb;
                this.value = this.get();
            }

            get(){
                Dep.target = this; // 将当前Watcher实例赋值给Dep.target
                const value = this.data[this.key]; // 触发getter,从而将Watcher添加到Dep中
                Dep.target = null;//收集完清空
                return value;
            }

            update(){
                const newVal = this.data[this.key];
                if (newVal !== this.value) {

                    this.value = newVal;

                    this.cb(newVal);

                }
            }

        }

        class Vue{
            constructor(options){
                this.$options = options;
                this._data = typeof options.data === 'function' ? options.data() : options.data;

                //调用Observer函数进行响应式处理
                new Observer(this._data);
                this.compile();

            }

            compile() {

                // 处理v-model绑定

                const inputs = document.querySelectorAll('[v-model]');

                inputs.forEach(input => {

                    const key = input.getAttribute('v-model').trim();

                    input.value = this._data[key];

                    input.addEventListener('input', (e) => {

                        this._data[key] = e.target.value;

                    });

                });

                // 处理{{ }} 绑定

                const elements = document.querySelectorAll('p,span,div'); // 简化处理,实际应根据文本内容匹配

                elements.forEach(element => {

                    const matches = element.innerHTML.match(/\{\{\s*(.*?)\s*\}\}/);

                    if (matches) {

                        const key = matches[1].trim();

                        new Watcher(this._data, key, (newVal) => {

                            element.innerHTML = newVal;

                        });

                        // 初始化显示

                        element.innerHTML = this._data[key];

                    }

                });

            }
        }

            // Observer类:
            // 遍历对象的所有属性,转为响应式
            // 每个属性有一个Dep实例     

        class Observer{

            constructor(data){
                this.walk(data);
            }

            walk(data){
                Object.keys(data).forEach(key =>{
                    this.defineReactive(data,key,data[key]);
                })
                console.log(Object.keys(data))
            }

            defineReactive(obj,key,val){
                const dep = new Dep();
                Object.defineProperty(obj,key,{
                    enumberable:true,
                    configurable:true,
                    get(){
                        if(Dep.target){
                            dep.addSub(Dep.target)
                        }
                        return val;
                    },
                    set(newVal){
                        if(newVal === val) return;
                        val = newVal;
                        dep.notify();
                    }
                })
            }

        }

      
        new Vue({
            data: () => ({
                message: "hello world"
            })
        });
    </script>
</body>
</html>

看完了此篇文章,您是不是对Vue2实现原理有了更深刻的理解呢,如果觉得对您有帮助,还请一键三连哦!非常感谢!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Transformer解析——(四)Decoder

本系列已完结&#xff0c;全部文章地址为&#xff1a; Transformer解析——&#xff08;一&#xff09;概述-CSDN博客 Transformer解析——&#xff08;二&#xff09;Attention注意力机制-CSDN博客 Transformer解析——&#xff08;三&#xff09;Encoder-CSDN博客 Transforme…

Vue前端开发-Vant之Layout组件

在Vant 中&#xff0c;Layout组件用于元素的响应式布局&#xff0c;分别由van-row和van-col两个组件来实现&#xff0c;前者表示行&#xff0c;后者被包裹在van-row组件中&#xff0c;表示列&#xff0c;共有24列栅格组成&#xff0c;在van-col组件中&#xff0c;span属性表示所…

【YOLOv8】损失函数

学习视频&#xff1a; yolov8 | 损失函数 之 5、类别损失_哔哩哔哩_bilibili yolov8 | 损失函数 之 6、定位损失 CIoU DFL_哔哩哔哩_bilibili 2.13、yolov8损失函数_哔哩哔哩_bilibili YOLOv8 的损失函数由类别损失和定位损失构成 类别损失&#xff1a;BCE Loss 定位损失…

Mac系统下使用Docker快速部署MaxKB:打造本地知识库问答系统

随着大语言模型的广泛应用&#xff0c;知识库问答系统逐渐成为提升工作效率和个人学习的有力工具。MaxKB是一款基于LLM&#xff08;Large Language Model&#xff09;大语言模型的知识库问答系统&#xff0c;支持多模型对接、文档上传和自动爬取等功能。本文将详细介绍如何在Ma…

(Arxiv-2025)ImageRAG:用于参考引导图像生成的动态图像检索

ImageRAG&#xff1a;用于参考引导图像生成的动态图像检索 paper是Tel Aviv University发布在Arxiv 2025的工作 paper title:ImageRAG: Dynamic Image Retrieval for Reference-Guided Image Generation Code:链接 图 1&#xff1a;使用参考图像扩展图像生成模型的生成能力。 在…

企业知识管理平台重构数字时代知识体系与智能服务网络

内容概要 现代企业知识管理平台的演进呈现出全生命周期管理与智能服务网络构建的双重特征。通过四库体系&#xff08;知识采集库、加工库、应用库、评估库&#xff09;的协同运作&#xff0c;该系统实现了从知识沉淀、结构化处理到价值释放的完整闭环。其中&#xff0c;知识图…

高级推理的多样化推理与验证

25年2月来自波士顿大学、NotBadMath.AI、谷歌、哥伦比亚大学、MIT、Intuit公司和斯坦福大学的论文“Diverse Inference and Verification for Advanced Reasoning”。 OpenAI o1、o3 和 DeepSeek R1 等推理 LLM 在数学和编码方面取得重大进展&#xff0c;但仍发现 IMO 组合问题…

23.1 WebBrowser控件

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 WebBrowser控件类似于IE浏览器的文档界面&#xff08;事实上IE也是使用的这个控件&#xff09;&#xff0c;它提供了显示网页及支持…

Django-Vue 学习-VUE

主组件中有多个Vue组件 是指在Vue.js框架中&#xff0c;主组件是一个父组件&#xff0c;它包含了多个子组件&#xff08;Vue组件&#xff09;。这种组件嵌套的方式可以用于构建复杂的前端应用程序&#xff0c;通过拆分功能和视图&#xff0c;使代码更加模块化、可复用和易于维…

计算机网络安全之一:网络安全概述

1.1 网络安全的内涵 随着计算机和网络技术的迅猛发展和广泛普及&#xff0c;越来越多的企业将经营的各种业务建立在Internet/Intranet环境中。于是&#xff0c;支持E-mail、文件共享、即时消息传送的消息和协作服务器成为当今商业社会中的极重要的IT基础设施。然而&#xff0…

AI学习指南DeepSeek篇(6)-DeepSeek论文介绍

1. DeepSeek LLM: Scaling Open-Source Language Models with Longtermism 发布时间: 2024 年 1 月 5 日 主要内容: 基于 Transformer 架构,采用分组查询注意力(GQA)优化推理成本。 支持多步学习率调度器,提升训练效率。 在预训练和对齐(监督微调与 DPO)方面进行了创新…

刺客信条 枭雄 画质设置以及【锁帧60帧】的办法

刺客信条 枭雄 锁帧60帧的办法 画质设置帧率锁60帧办法 画质设置 关爱老电脑和GPU&#xff0c;适当设置一下画质 我们设置画面的时候&#xff0c;可以看游戏右上角的显存占用&#xff0c;进而观察自己这样设置&#xff0c;GPU的显存够不够&#xff1a; 环境质量&#xff1a;超…

适用于复杂背景的YOLOv8改进:基于DCN的特征提取能力提升研究

文章目录 1. YOLOv8的性能瓶颈与改进需求1.1 YOLOv8的优势与局限性1.2 可变形卷积&#xff08;DCN&#xff09;的优势 2. DCN在YOLOv8中的应用2.1 DCN的演变与YOLOv8的结合2.2 将DCN嵌入YOLOv8的结构中2.2.1 DCNv1在YOLOv8中的应用2.2.2 DCNv2与DCNv3的优化 2.3 实验与性能对比…

cesium视频投影

先看效果 使用cesium做视频投影效果&#xff0c;而且还要跟随无人机移动而移动&#xff0c;我现在用定时器更新无人机的坐标来实现效果具体代码如下&#xff1a; 1、CesiumVideo3d.js(某个cesium技术群大佬分享的) // import ECEF from "./CoordinateTranslate"; le…

滚珠花键在使用时需注意什么?

滚珠花键是一种直线运动系统&#xff0c;当花键套利用其中的钢球在经过精密磨削的花键轴上直线运动时&#xff0c;可以传递扭矩。在使用滚珠花键时&#xff0c;需要注意以下几个重要的事项&#xff1a; 1、不要擅自拆卸滚珠花键的各部分&#xff0c;因为这样可能会导致异物进入…

AI助力下的PPT革命:DeepSeek 与Kimi的高效创作实践

清华大学出品《DeepSeek&#xff1a;从入门到精通》分享 在忙碌的职场中&#xff0c;制作一份高质量的PPT往往需要投入大量时间和精力&#xff0c;尤其是在临近截止日期时。今天&#xff0c;我们将探索如何借助 AI 工具 —— DeepSeek 和 Kimi —— 让 PPT 制作变得既快捷又高…

PcVue : 点亮马来西亚砂拉越偏远村庄

导读 背景简介 新项目的需求 实施亮点 成果 背景简介 2021年&#xff0c;砂拉越能源公司&#xff08;Sarawak Energy Berhad&#xff09;启动了一项意义非凡的项目-借助太阳能、微型水力发电机等可再生能源&#xff0c;为砂拉越州偏远村庄送去光明与动力。然而&#xff0c…

图论 之 迪斯科特拉算法求解最短路径

文章目录 题目743.网络延迟时间3341.到达最后一个房间的最少时间I 求解最短路径的问题&#xff0c;分为使用BFS和使用迪斯科特拉算法&#xff0c;这两种算法求解的范围是有区别的 BFS适合求解&#xff0c;边的权值都是1的图中的最短路径的问题 图论 之 BFS迪斯科特拉算法适合求…

在mfc中使用自定义三维向量类和计算多个三维向量的平均值

先添加一个普通类, Vector3.h, // Vector3.h: interface for the Vector3 class. // //#if !defined(AFX_VECTOR3_H__53D34D26_95FF_4377_BD54_57F4271918A4__INCLUDED_) #define AFX_VECTOR3_H__53D34D26_95FF_4377_BD54_57F4271918A4__INCLUDED_#if _MSC_VER > 1000 #p…

DM执行计划

DM执行计划 1. 引言 理解执行计划对于优化查询性能、诊断慢查询问题至关重要。本文将从基础概念入手&#xff0c;逐步深入探讨执行计划的各个组成部分&#xff0c;并通过设计用例来验证所学知识。 2. SQL 执行计划基础 SQL 执行计划是数据库引擎在执行 SQL 语句时生成的一个…