Vue响应式原理学习总结3:渲染watcher

2024-01-20 13:18

本文主要是介绍Vue响应式原理学习总结3:渲染watcher,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

终于到了渲染watcher,看完这篇文章的内容后,大家就可以实现一个响应式系统了,并且能够在页面上有所体现。

源码地址:gitee

系列文章:

1. 基本原理

2. 数组的处理

4. 最终章

Vue项目总结系列文章:

  1. 基础架构
  2. 登录与权限控制

持续更新中。。。

什么是渲染Watcher

vue中有多种watcher,我们之前实现的watcher类似于Vue.$watch,当依赖变化时执行回调函数。而渲染watcher不需要回调函数,渲染watcher接收一个渲染函数而不是依赖的表达式,当依赖发生变化时,自动执行渲染函数

new Watcher(app, renderFn)
复制代码

那么如何做到依赖变化时重新执行渲染函数呢,我们要先对Watcher的构造函数做一些改造

constructor(data, expOrFn, cb) {this.data = data// 修改if (typeof expOrFn === 'function') {this.getter = expOrFn} else {this.getter = parsePath(expOrFn)}this.cb = cbthis.value = this.get()
}// parsePath的改造,返回一个函数
function parsePath(path) {const segments = path.split('.')return function (obj) {for (let key of segments) {if (!obj) returnobj = obj[key]}return obj}
}

这样,this.getter就是一个取值函数了,get修改

get() {pushTarget(this)const data = this.dataconst value = this.getter.call(data, data) // 修改popTarget()return value
}

要想依赖变化时重新执行渲染函数,就要在派发更新阶段做一个更新,因此,update方法也要进行修改:

update() {// 重新执行get方法const value = this.get()// 渲染watcher的value是undefined,因为渲染函数没有返回值// 因此value和this.value都是undefined,不会进入if// 如果依赖是对象,要触发更新if (value !== this.value || isObject(value)) {const oldValue = this.valuethis.value = valuethis.cb.call(this.vm, value, oldValue)}
}function isObject(target) {return typeof target === 'object' && target !== null
}

大家可能会有疑问了,为什么不能直接用this.getter.call(this.data)来重新执行渲染函数呢,这就涉及到下文要提到的重新收集依赖了。但是在此之前,要先解决一个问题:依赖的重复收集

重复的依赖

看这样一个例子

<div>{{ name }} -- {{ name }}
</div>

如果我们渲染这个模板,那么渲染watcher就会依赖两次name。因为解析该模板时,会读取两次name的值,就会触发两次getter,此时Dep.target都是当前watcher,在depend方法中,

depend() {if (Dep.target) {dep.addSub(Dep.target)}
}

依赖会被收集两次,name变化时就会触发两次重新渲染。因此vue采用了以下方式

首先为每个dep添加一个id

let uid = 0constructor() {this.subs = []this.id = uid++ // 增加
}
复制代码
watcher`修改的地方比较多,首先为增加四个属性`deps, depIds, newDeps, newDepIds
this.deps = []             // 存放上次求值时存储自己的dep
this.depIds = new Set()    // 存放上次求值时存储自己的dep的id
this.newDeps = []          // 存放本次求值时存储自己的dep
this.newDepIds = new Set() // 存放本次求值时存储自己的dep的id

​ 每次取值完毕后,会交换depnewDep,并将newDep清空,下文会讲到

我们的思路是,当需要收集watcher时,由watcher来决定自己是否需要被dep收集。在上面的例子中,假设对name取值时,watcherdep1收集,第二次对name取值时,watcher发现自己已经被dep1收集过了,就不会重新收集一遍,代码如下

// dep.depend
depend() {if (Dep.target) {Dep.target.addDep(this) // 让watcher来决定自己是否被dep收集}
}// watcher.addDep
addDep(dep) {const id = dep.id// 如果本次求值过程中,自己没有被dep收集过则进入ifif (!this.newDepIds.has(id)) {// watcher中记录收集自己的dpthis.newDepIds.add(id)this.newDeps.push(dep)if (!this.depIds.has(id)) {dep.addSub(this)}}
}

现在解释一下最后一个if,考虑重新渲染的情况:watcher依赖namename发生了变化,导致watcherget方法执行,会重新对name取值,进入addDep方法时,newDepIds是空的,因此会进入if,来到最后一个if,因为第一次取值时,dep已经收集过watcher了,所以不应该再添加一遍,这个if就是这个作用。

《Vue技术内幕》总结的很好:

  1. newDepsnewDepIds用来再一次取值过程中避免重复依赖,比如:{{ name }} -- {{ name }}
  2. depsdepIds用来再重新渲染的取值过程中避免重复依赖

再执行get方法最后会清空newDeps,newDepIds

cleanUpDeps() {// 交换depIds和newDepIdslet tmp = this.depIdsthis.depIds = this.newDepIdsthis.newDepIds = tmp// 清空newDepIdsthis.newDepIds.clear()// 交换deps和newDepstmp = this.depsthis.deps = this.newDepsthis.newDeps = tmp// 清空newDepsthis.newDeps.length = 0}

依赖的重新收集

我所理解的依赖重新收集包括两部分内容:收集新的依赖和删除无效依赖。其实收集新依赖再上面的代码中已经有所体现了,虽然前面的代码中对重复依赖做了很多判断,但是能够收集到依赖的基本前提是Dep.target存在,从Watcher的代码中可以看出,只有在get方法执行过程中,Dep.target是存在的,因此,我们在update方法中使用了get方法来重新触发渲染函数,而不是getter.call()。并且重新收集依赖是必要的,比如使用了v-if的情况,因此,现在的响应式系统比之前的固定依赖版本又有了很大进步。

至于删除无效依赖部分,可以在cleanUpDeps中添加如下代码

cleanUpDeps() {// 增加let i = this.deps.lengthwhile (i--) {const dep = this.deps[i]if (!this.newDepIds.has(dep.id)) {dep.removeSub(this)}}let tmp = this.depIds// ...
}

在求值结束(也就是依赖收集结束)后,如果本次求值过程中,发现有些dep在上次求值时收集了自己,但是这次求值时没有收集自己,说明该数据已经不需要自己了,将自己从dep中删除即可

// Dep.js
removeSub(sub) {remove(this.subs, sub)
}function remove(arr, item) {if (!arr.length) returnconst index = arr.indexOf(item)if (index > -1) {return arr.splice(index, 1)}
}

这样,我们的响应式系统就比较完整了

总结

其实所谓的渲染watcher和其他的watcher区别不大,只是依赖变化时自动执行渲染函数而已,上文中提到的重复依赖的处理,依赖重新收集是通用的。

下一篇文章将会做一个简单的模板编译器,让我们的响应式系统与页面渲染相结合,并且会实现v-model的双向绑定,请大家关注。

如果各位看官感觉文章还可以的话,就请点个赞吧!!!

这篇关于Vue响应式原理学习总结3:渲染watcher的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/626136

相关文章

详解Vue如何使用xlsx库导出Excel文件

《详解Vue如何使用xlsx库导出Excel文件》第三方库xlsx提供了强大的功能来处理Excel文件,它可以简化导出Excel文件这个过程,本文将为大家详细介绍一下它的具体使用,需要的小伙伴可以了解... 目录1. 安装依赖2. 创建vue组件3. 解释代码在Vue.js项目中导出Excel文件,使用第三

Kubernetes常用命令大全近期总结

《Kubernetes常用命令大全近期总结》Kubernetes是用于大规模部署和管理这些容器的开源软件-在希腊语中,这个词还有“舵手”或“飞行员”的意思,使用Kubernetes(有时被称为“... 目录前言Kubernetes 的工作原理为什么要使用 Kubernetes?Kubernetes常用命令总

Java实现Excel与HTML互转

《Java实现Excel与HTML互转》Excel是一种电子表格格式,而HTM则是一种用于创建网页的标记语言,虽然两者在用途上存在差异,但有时我们需要将数据从一种格式转换为另一种格式,下面我们就来看看... Excel是一种电子表格格式,广泛用于数据处理和分析,而HTM则是一种用于创建网页的标记语言。虽然两

Spring MVC如何设置响应

《SpringMVC如何设置响应》本文介绍了如何在Spring框架中设置响应,并通过不同的注解返回静态页面、HTML片段和JSON数据,此外,还讲解了如何设置响应的状态码和Header... 目录1. 返回静态页面1.1 Spring 默认扫描路径1.2 @RestController2. 返回 html2

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

vue解决子组件样式覆盖问题scoped deep

《vue解决子组件样式覆盖问题scopeddeep》文章主要介绍了在Vue项目中处理全局样式和局部样式的方法,包括使用scoped属性和深度选择器(/deep/)来覆盖子组件的样式,作者建议所有组件... 目录前言scoped分析deep分析使用总结所有组件必须加scoped父组件覆盖子组件使用deep前言

VUE动态绑定class类的三种常用方式及适用场景详解

《VUE动态绑定class类的三种常用方式及适用场景详解》文章介绍了在实际开发中动态绑定class的三种常见情况及其解决方案,包括根据不同的返回值渲染不同的class样式、给模块添加基础样式以及根据设... 目录前言1.动态选择class样式(对象添加:情景一)2.动态添加一个class样式(字符串添加:情

Python中实现进度条的多种方法总结

《Python中实现进度条的多种方法总结》在Python编程中,进度条是一个非常有用的功能,它能让用户直观地了解任务的进度,提升用户体验,本文将介绍几种在Python中实现进度条的常用方法,并通过代码... 目录一、简单的打印方式二、使用tqdm库三、使用alive-progress库四、使用progres

Redis主从复制的原理分析

《Redis主从复制的原理分析》Redis主从复制通过将数据镜像到多个从节点,实现高可用性和扩展性,主从复制包括初次全量同步和增量同步两个阶段,为优化复制性能,可以采用AOF持久化、调整复制超时时间、... 目录Redis主从复制的原理主从复制概述配置主从复制数据同步过程复制一致性与延迟故障转移机制监控与维

使用Python实现批量访问URL并解析XML响应功能

《使用Python实现批量访问URL并解析XML响应功能》在现代Web开发和数据抓取中,批量访问URL并解析响应内容是一个常见的需求,本文将详细介绍如何使用Python实现批量访问URL并解析XML响... 目录引言1. 背景与需求2. 工具方法实现2.1 单URL访问与解析代码实现代码说明2.2 示例调用