莽村李青都看得懂的Vue响应式原理

news2024/11/17 0:43:52

Vue响应式原理

  • 八股文序
  • 违背老祖宗的决定将Vue响应式原理公众于世
    • 响应式数据(Observe篇)
    • dom更新(Wacther篇)
    • 依赖收集

八股文序

开篇来一段大家都会背诵的八股文。

某面试官: 请你简要介绍一下Vue的响应式原理。

答:Vue利用发布订阅模式结合Object.defineProperty劫持对象的get和set方法来实现响应式。

某面试官追问:你知道什么是依赖吗?

答:。。。

某面试官再次追问:请你简述一下依赖收集的过程。

答:。。。

违背老祖宗的决定将Vue响应式原理公众于世

我相信大部分人对于vue的响应式的了解程度也和上面八股文篇描写的差不多。所以我做了一个违背老祖宗的决定那就是

将Vue的响应式原理公众于世!!!!





因为是介绍响应式原理,所以我们以Vue2的一个最最简单的实例来入门。
在这里插入图片描述
从图可知,即使我们什么原理都不知道,也应该新建一个Vue类。

	 class Vue {
            constructor({ el, data }) {
                this.el = document.querySelector(el)
                this.data = data
            }
        }

响应式数据(Observe篇)

下一步,把普通对象变成响应式对象。如何实现呢? 八股文已经给出了答案。
在这里插入图片描述
在Vue的构造器中增加一行 observe(data)

	  class Vue {
            constructor({ el, data }) {
                this.el = document.querySelector(el)
                this.data = data
                // 执行observe函数将普通对象转换成响应式对象
                observe(data)
            }
        }

那么很显然,observe函数就是将普通对象变成响应式对象的函数。这一块并不难,所以这里直接给出代码,注意看注释

	 // observe 函数,observe中文是观察,观察一个普通对象 使它变成响应式的
        const observe = (target) => {
            // 如果不是对象数据类型,只是普通类型那么变成响应式
            if (typeof target !== 'object') return
            // 新建一个Observe类 实现响应式的具体逻辑
            new Observe(target)
        }

        class Observe {
            constructor(data) {
                Object.entries(data).forEach(item => {
                    Object.defineProperty(data, item[0], {
                        get() {
                            // 劫持对象的get方法
                            console.log(`看来你想获取${item[0]}的值`)
                            return item[1]
                        },
                        set(newValue) {
                            // 劫持对象的set方法  
                            // 这里要注意的是如果新值是对象类型 不要忘记响应化
                            if (typeof newValue === 'object')
                                observe(newValue)
                            item[1] = newValue
                        }
                    })
                })
            }
        }

没错 将一个普通对象变成响应式对象就是这么简单,这一小节已经完成了。

dom更新(Wacther篇)

由于本文主要介绍的是响应化原理,那么一些虚拟dom渲染的过程就以图带过。我们看到Vue的构造函数中的el属性
在这里插入图片描述
我们可以从el属性的值获取到实际dom结构。
我以图的形式来描述dom和watcher的关系
在这里插入图片描述
由图中的对应关系可得。每一个响应式变量都会对应一个watcher。
那么以我们本文开始的🌰做进一步解释

 <div id="app">
        {{name}}
  </div>

新建一个watcher实例需要三个属性,
1、监听的响应式对象(很好,在上一小节中我们已经完成了)
2、监听的key (从图文关系可知,每一个key对应一个watcher,所以需要传入key)
3、回调函数,也就是监听的响应式对象的key值发生变化的时候,需要执行的回调,这个回调函数就是dom更新函数。以保证数据改变—>回调执行—>dom更新

根据目前的分析,一个初步的Wather类已经写好了

	class Watcher {
            constructor(target, key, callBack) {
                this.target = target
                this.key = key
                this.callBack = callBack
            }
            // 传入的callback就是dom的更新函数,updateDom就是执行这个函数
            upDateDom() {
                this.callBack()
            }
        }

那么现在问题来了?如何让watcher观察的key和响应式数据中的key一一对应上呢?
八股文早已给出了答案
在这里插入图片描述
新建一个Dep类(很关键哦!),Dep是dependence的缩写,依赖的意思。那么他们的对应关系依然用图说话
在这里插入图片描述
由图可知,dom中的插值表达式和watcher是一对一的关系,但是响应式数据和watcher是一对多的关系
我们可以这样理解,不止一个dom节点使用到了name属性,所以name更新要通知全部的watcher更新它们的dom所以是一对多的关系。

新建一个发布订阅模式。

	class Dep {
            constructor() {
                // 保存响应式数据的所有依赖,这里的依赖就是watcher实例
                this.depList = []
            }

            // 由于是一对多的关系,所以要有一个add方法
            addDep(watcher) {
                this.depList.push(watcher)
            }

            // 通知全部的watcher更新视图
            noticeWatcher() {
                // 上面已经说过了,每一个依赖都是一个watcher实例
                // noticeWatcher就说明watcher(订阅者)已经收到了通知
                // 需要更新dom
                this.depList.forEach(item => {
                    item.upDateDom()
                })
            }
        }

由上图可知,响应式变量的每一个key都需要保存使用到它的dom节点(我们称为依赖)
下一小节介绍依赖收集。

依赖收集

如果你坚持到了这一小节,那么你已经完成了 发布者(Dep类),订阅者(Watcher类),响应式数据(Observe)类。已经是梅西进禁区----只差临门一脚了。依赖收集的过程就是将响应式数据关联的依赖(也就是使用到该响应式数据的dom节点)给收集起来。
注意看,这个男人叫小帅,他开始写最关键的代码了。改写watcher类。

 class Watcher {
            constructor(target, key, callBack) {
                this.target = target
                this.key = key
                this.callBack = callBack
                this.getValue()
            }
            // 传入的callback就是dom的更新函数,updateDom就是执行这个函数
            upDateDom() {
                this.callBack()
            }

            getValue() {
            	// 执行getValue函数完成依赖收集
                this.target[this.key]
            }
        }

什么?? 执行getValue的函数就可以完成依赖收集????
以本文的html为例,新建一个watcher实例

	var app = new Vue({
            el: '#app',
            data: {
                name: 'wxs'
            }
        })

        new Watcher(app.data, 'name', () => {
            console.log('假设这是一个dom更新函数!!!')
        })

当我们新建一个vue实例时,说明app.data已经变成了响应式对象,所以监听者watcher观察的就是这个对象,key为‘name’。由于在watcher的构造函数中执行了函数getValuethis.target[this.key]那么响应式对象的get方法就被触发了。
不好理解的话看图
在这里插入图片描述
在watcher的getValue函数中插一行代码,构造器中加一行代码

 getValue() {
				 // 保存当前watcher实例
                Dep.target = this
                // 执行getValue函数完成依赖收集
                this.target[this.key]
            }

在这里插入图片描述

它保存的是当前正在新建的wather实例。不一定要保留在Dep.target中,Dep.aaa,window.aaa都可以。也许现在你还不知道为什么要保存它。但是我保证,你在10行之内就知道它是干什么的了。
由于刚刚触发了响应式对象的get方法,我们回到响应式类Observe中。
在这里插入图片描述
上一小节最后一句
在这里插入图片描述
那说明响应式对象的每一个key都要有一个数组来存它全部的依赖。改写Observe类。

	 class Observe {
            constructor(data) {
                Object.entries(data).forEach(item => {
                    // 新建依赖收集器
                    const dep = new Dep()
                    Object.defineProperty(data, item[0], {
                        get() {
                            // 劫持对象的get方法
                            // 新建watcher实例会执行到key的get方法 所以Dep.target就是key需要收集的依赖
                             if (Dep.target !== null) {
                                // 避免重复收集
                                dep.addDep(Dep.target)
                            }
                            return item[1]
                        },
                        set(newValue) {
                            // 劫持对象的set方法  
                            // 这里要注意的是如果新值是对象类型 不要忘记响应化
                            if (typeof newValue === 'object')
                                observe(newValue)
                            item[1] = newValue
                            // 通知依赖更新
                            dep.noticeWatcher()
                        }
                    })
                })
            }
        }

写到这里也许你开始不耐烦了。但是


已经结束啦!!!!


是的你没有看错,但你完成这一步。Vue的响应式已经全部完成啦!。当然,我们实现的是简易版本的。但是请你相信。当你认真看完并理解。你已经掌握了Vue响应式的核心了。

出其不意的挂上全部代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <div id="app">
        {{name}}
    </div>


    <script>
        class Dep {
            constructor() {
                // 保存响应式数据的所有依赖,这里的依赖就是watcher实例
                this.depList = []
            }

            // 由于是一对多的关系,所以要有一个add方法
            addDep(watcher) {
                this.depList.push(watcher)
            }

            // 通知全部的watcher更新视图
            noticeWatcher() {
                // 上面已经说过了,每一个依赖都是一个watcher实例
                // noticeWatcher就说明watcher(订阅者)已经收到了通知
                // 需要更新dom
                this.depList.forEach(item => {
                    item.upDateDom()
                })
            }
        }

        // observe 函数,observe中文是观察,观察一个普通对象 使它变成响应式的
        const observe = (target) => {
            // 如果不是对象数据类型,只是普通类型那么变成响应式
            if (typeof target !== 'object') return

            // 新建一个Observe类 实现响应式的具体逻辑
            new Observe(target)
        }

        class Observe {
            constructor(data) {
                Object.entries(data).forEach(item => {
                    // 新建依赖收集器
                    const dep = new Dep()
                    Object.defineProperty(data, item[0], {
                        get() {
                            // 劫持对象的get方法
                            // 新建watcher实例会执行到key的get方法 所以Dep.target就是key需要收集的依赖
                            if (Dep.target !== null) {
                                // 避免重复收集
                                dep.addDep(Dep.target)
                            }

                            return item[1]
                        },
                        set(newValue) {
                            // 劫持对象的set方法  
                            // 这里要注意的是如果新值是对象类型 不要忘记响应化
                            if (typeof newValue === 'object')
                                observe(newValue)
                            item[1] = newValue
                            // 通知依赖更新
                            dep.noticeWatcher()
                        }
                    })
                })
            }
        }

        class Vue {
            constructor({ el, data }) {
                this.el = document.querySelector(el)
                this.data = data
                // 执行observe函数将普通对象转换成响应式对象
                observe(data)
            }
        }

        class Watcher {
            constructor(target, key, callBack) {
                this.target = target
                this.key = key
                this.callBack = callBack
                this.getValue()
                // 依赖收集完成之后重置,避免重复收集
                Dep.target = null
            }
            // 传入的callback就是dom的更新函数,updateDom就是执行这个函数
            upDateDom() {
                this.callBack()
            }

            getValue() {
                // 保存当前watcher实例
                Dep.target = this
                // 执行getValue函数完成依赖收集
                this.target[this.key]
            }
        }

        var app = new Vue({
            el: '#app',
            data: {
                name: 'wxs'
            }
        })

        new Watcher(app.data, 'name', () => {
            console.log('假设这是一个dom更新函数!!!')
        })

    </script>
</body>

</html>

算上html结构以及换行才125行代码。学会它,超过隔壁莽村李青!

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

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

相关文章

leaflet 读取上传的wkt文件,转换为geojson文件(示例代码056)

第056个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中上传WKT文件,解析wtk文件并转换为geojson,并在地图上显示图片。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式本示例所用的a.wkt示例源代码(共139行)…

AirServer在哪下载?如何免费使用教程

苹果手机投屏到电脑mac是怎么弄&#xff1f;你知道多少&#xff1f;相信大家对苹果手机投屏到电脑mac能在电脑上操作不是很了解&#xff0c;下面就让coco玛奇朵带大家一起了解一下教程。AIrServer是一款ios投屏到mac的专用软件&#xff0c;可将iOS上的音频&#xff0c;视频&…

make的使用及Makefile万能模板

make的使用及Makefile万能模板前言为什么用makemake的使用Makefile万能模板前言 gcc 的编译&#xff0c;是将源码生成可执行程序。 例如&#xff1a; gcc hello.c -o hello源码到可执行程序需要四步处理 硬件——》机器语言——》汇编语言——》 高级语言 1&#xff09;预处理…

【openGauss实战8】Schema的图文解读

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

Session与Cookie的区别(三)

中场休息 让我们先从比喻回到网络世界里&#xff0c;HTTP 是无状态的&#xff0c;所以每一个 Request 都是不相关的&#xff0c;就像是对小明来说每一位客人都是新的客人一样&#xff0c;他根本不知道谁是谁。 既然你没办法把他们关联&#xff0c;就代表状态这件事情也不存在。…

微搭低代码从入门到精通08-轮播容器

我们上一篇讲解了基础布局组件&#xff0c;讲解了普通容器和文本组件的用法&#xff0c;本篇我们继续介绍布局组件。 小程序中经常会有个功能是轮播图展示的功能&#xff0c;多张图片可以顺序进行切换。我们学习使用轮播容器的时候&#xff0c;先考虑切换的图片从哪来&#xf…

视频连载09 - 这个为生信学习和生信作图打造的开源R教程真香!!!

点击阅读原文跳转完整教案。1 思考题2 R基础2.1 R安装2.2 Rstudio基础2.2.1 Rstudio版本2.2.2 Rstudio安装2.2.3 Rstudio 使用2.3 R基本语法2.3.1 获取帮助文档&#xff0c;查看命令或函数的使用方法、事例或适用范围2.3.2 R中的变量及其初始化2.3.3 变量类型和转换2.3.4 R中矩…

Python-项目实战--贪吃蛇小游戏(1)

1.贪吃蛇游戏规则贪吃蛇游戏规则如下:1.1开始和结束贪吃蛇初始出现在游戏窗口的左上角位置,体长共有3节游戏过程中&#xff0c;一旦蛇头撞到了窗口的边缘或者身体的其他部位,游戏结束游戏过程中&#xff0c;点击游戏窗口的关闭按钮&#xff0c;或者按下ESC键可以直接退出游戏一…

Java高级-常用类-String、Date、Compare、Other

本篇讲解java常用类 String类 String:字符串&#xff0c;使用一对""引起来表示。 String类被声明为final的&#xff0c;不可被继承。 String实现了Serializable接口&#xff1a;表示字符串是支持序列化的。 ​ 实现了Comparable接口&#xff1a;表示String可以比较…

微搭低代码从入门到精通09-数据容器

我们已经用了两篇的篇幅介绍了微搭的布局组件&#xff0c;包括普通容器、文本、图片、轮播容器。 微搭中还有粗粒度的组件&#xff0c;今天介绍的数据容器就是粗粒度的组件。所谓粗粒度的组件&#xff0c;一般包括基础组件、样式还有默认的事件。数据容器一共包含三种分别是数…

vscode sftp从linux服务器下载文件至本地:No such file or dictionary【已解决】

在服务器跑完程序需要下载数据的时候报错&#xff1a; [warn] ENOENT: no such file or directory, open /home/LIST_2080Ti/.ssh/config load /home/LIST_2080Ti/.ssh/config failed 完整报错内容如下&#xff1a; [02-10 08:38:47] [info] config at /home/LIST_2080Ti {&q…

Arm-Linux子系统的互相Notify

前言&#xff1a; Linux下面不同的子系统一个个的组成了整个系统的运行环节&#xff0c;为了让这些子系统能够互相通讯&#xff0c;有一种叫做&#xff1a;notify chain(通知链)的东西。本篇看下。 概括 所谓通知链&#xff0c;有通知&#xff0c;就有执行的地方。比如A子系统通…

无题

&#xff08;1&#xff09;风国产化替代&#xff1f;全球化&#xff1f;新一代数字化技术升级&#xff1f;云化&#xff08;公有云化&#xff09;&#xff1f;业务线上化&#xff1f;产业互联整合&#xff1f;私有云原生技术可以支撑&#xff1a;国产化替代-新一代数字化技术升…

WPS底层逻辑串讲

文章目录wps页面基本介绍演示文稿功能讲解框架介绍具体功能讲解&#xff1a;1. 另存为2. 输出图片3. 文件打包4. 演示文稿打印5. 文档加密两种方式 ❤文件打开密码 &#xff1a;文档加密--->密码加密--》输入密码即可 ❤文档编辑密码&#xff1a;输出为PDF--->设置即可6.…

【老卫拆书】009期:Vue+Node肩挑全栈!《Node.js+Express+MongoDB+Vue.js全栈开发实战》开箱

今天刚拿到一本新书&#xff0c;叫做《Node.jsExpressMongoDBVue.js全栈开发实战》&#xff0c;做个开箱。 外观 先从外观上讲&#xff0c;这本是全新的未开封的&#xff0c;膜还在。 这本书介绍从技术原理到整合开发实战&#xff0c;以丰富的项目展现全栈开发的一个技巧。 …

ChatGPT如何注册,如何使用(个人版)文末送账号

您好&#xff0c;我是码农飞哥&#xff08;wei158556&#xff09;&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精…

ES8——Generator函数的使用

babel工具插件下载&#xff1a;npm i --save babel-polyfill 引入&#xff1a;polyfill.js进行转码&#xff08;es8->es5&#xff09; 介绍 Generator函数用于生成迭代器 function * (){} yeild: 作用同return类似 {const obj function* () {yield "a";yield 12…

分享! opensource.builders——为您喜爱的应用程序查找开源替代方案

今天和大家分享一个非常有意思的网站, 我们可以从这个网站中找到一些常用软件的开源替代方案, 比如说之前很火Notion, 我们可以找到它的开源替代方案——Appflowy, 并且在 github 上学习部署它的本地版本.好啦, 话不多说, 上地址:网址地址: https://opensource.builders/github…

数据库(三):多版本并发控制MVCC,行锁的衍生版本,记录锁,间隙锁, Next-Key锁(邻键锁)

文章目录前言一、MVCC以及MVCC的缺点1.1 MVCC可以为数据库解决什么问题1.2 MVCC的基本思想1.3 版本号1.4 Undo日志1.5 ReadView1.6 快照读和当前读1.6.1 快照读1.6.2 当前读二、记录锁三、间隙锁四、邻键锁总结前言 一、MVCC以及MVCC的缺点 MVCC&#xff0c;即多版本并发控制…

Linux clock子系统及驱动实例

文章目录基本概念CLK子系统时钟API的使用clock驱动实例1、时钟树2、设备树3、驱动实现fixed_clk固定时钟实现factor_clk分频时钟实现gate_clk门控时钟实现基本概念 晶振&#xff1a;晶源振荡器 PLL&#xff1a;Phase lock loop&#xff0c;锁相环。用于提升频率 OSC&#xff1a…