| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> JavaScript知识库 -> Vue源码实现之watcher拾遗 -> 正文阅读 |
|
[JavaScript知识库]Vue源码实现之watcher拾遗 |
目录 1. Watcher构造函数参数options和渲染watcher标志位 2. watcher收集的新老依赖deps和newDeps的作用 3. watcher中getter的目的就是去touch目标数据以触发依赖收集 4. Watcher设置依赖收集标志时为什么要pushTarget和popTarget 这篇文章是紧跟着《Vue源码分析基础之响应式原理》的一篇文章。如果刚开始接触vue源码的童鞋,推荐先看完上面一篇文章,这样理解起来可能会轻松点。 1. Watcher构造函数参数options和渲染watcher标志位vue源码的watcher实现和我们《Vue源码分析基础之响应式原理》分析的思路大致差不多,但是具体实现细节上会有不少的差异,所以下面会做一些补充性的分析。 这里我们先看下watcher类的构造函数
首先,我们来分析下构造函数的参数参数比我们《Vue源码分析基础之响应式原理》构建的watcher多了两个:
2. watcher收集的新老依赖deps和newDeps的作用这里跟着需要提一提的是deps和newDeps,因为每次数据更新时vue都会触发渲染watcher进而调用到render来重新构建虚拟dom,构建虚拟dom时需要解析模板中绑定的数据的变化,从而为模板中用到的这些data中的属性建立起watcher的依赖订阅。但是新的页面因为已经发生了变化,很有可能在之前我们模板中用到了某个data中的属性,但更新后的模板不再用这个属性了,那么我们就需要将渲染watcher从该属性的dep.subs中删除,否则就会造成修改该属性时依然触发页面重新渲染的bug。 比如下面的模板代码
假如当前show为true,那么在点击toggle的时候,show会被改成false,从而导致了依赖show变量的渲染watcher的update方法的运行来重新构建虚拟DOM和更新真实DOM,期间show自身的dep.subs原来是有watcher的订阅在里面的,这时我们就需要将其移除掉了。 而watcher上面的deps和newDeps要做的就是这些事情。
那么newDeps是什么时候产生的呢?当然是在依赖收集的时候了。 在watcher收集依赖的时候,会触发自身的get方法,关键的代码如下
get方法首先做的就是调用pushTarget来将watcher自身推入到targetStack里面,然后和以前分析的那样设置全局的Dep.target为watcher自身。这里为什么要用pushTarget,我们下面会另外起个小节来分析,这里我们就认为它仅仅是设置了Dep.target就行了。 跟着调用getter去触发依赖的收集,这样当某个被obeserved的data的属性被读取(touch)的时候,就会触发该属性自身对应的依赖对象dep的depend方法来进行依赖收集。
最终进入到watcher的addDep中
从中我们将上面defineReactive中对应属性的dep加入到了这个watcher的newDeps,同时将自己加入了该dep的subs中,实现了依赖的收集。 但这并没有完。这里还只是走完了get方法的getter调用,get方法后面还调用了一个cleanupDeps的方法,而该方法的作用,就是我们上面说的将以前依赖某个data属性的watcher,从该属性的dep.subs中移除,然后形成该watcher所依赖的所有属性的dep组成的最新的deps,并保存到watcher.deps中
3. watcher中getter的目的就是去touch目标数据以触发依赖收集跟着要看下的就是构造函数中对getter的操作。
我们知道watcher中的getter的作用是去取得被监控数据具体的属性,通常是在构造函数调用get方法时去调用下getter,从而读取下这个属性,因为此前对这个对象进行了observe(data),所以一旦我们读取了这个属性,那么get方法设置了Dep.target的标识位的情况下就会触发依赖收集。 这就是构造函数传入的expOrFn是"count.total"这种形式的数据的情况。 但是expOrFn也可能是个函数,比如我们刚才说的渲染watcher初始化时传入的就是updateComponent这个方法。 这个时候构造函数发现你传入的是个函数,它就会直接将这个方法赋值给getter。这样在跟着调用的get,然后触发getter时,就是直接调用到这个函数。比如updateComponent方法,开始去调用渲染函数从新生成虚拟DOM,期间touch到所有页面引用的data数据,触发依赖收集。 所以本质上来说,该getter的作用和上面的情况是一致的,也就是说,getter存在的目的就是为了去touch下目标监控数据,从而触发对这些数据的依赖的收集! 4. Watcher设置依赖收集标志时为什么要pushTarget和popTarget正如我们前面看到的,在vue的源代码中,开启依赖收集标志和关闭依赖收集标志并不是直接操作Dep.target的赋值,而是通过pushTarget和popTarget来做的
首先,代码字面意义没有什么不好理解的,就是pushTarget的时候将当前watcher推入到栈中,并将该watcher设置成Dep.target。popTarget的时候从栈顶pop一个watcher出来并将其设置到Dep.target。 问题是why? 其实这里主要是要解决依赖收集的嵌套的问题。 这里我能想到的一个应用场景是在计算属性watcher中,计算属性初始话时会调用initComputed方法。关键代码如下:
首先以用户自定义的computed函数作为getter创建watcher并放到vm的_computedWatchers数组下。但因为设置了lazy为tru,所以watcher构造时默认不会自动调用getter来立刻更新计算属性,而是等到如页面{{计算属性}}使用时再触发下面的computeGetter来触发更新,可以参考上面watcher的构造函数对lazy的判断部分:
跟着为我们定义的每个计算属性调用defineComputed方法
这个方法主要的目的是将用户自定义计算属性挂到vm上面,以便页面通过{{计算属性}}直接访问。然后将该计算属性的getter定义为computedGetter,这样一来,在页面等访问{{计算属性}}时该函数将调用_computedWatchers对应的watcher的getter来重新计算属性并触发所依赖属性的依赖收集。 该computedGetter会在什么时候被触发呢? 在组件渲染watcher执行updateComponent时。因为render渲染函数需要读取页面引用到的data属性和computed计算属性来生成虚拟dom。 此时Dep.target是该组件的渲染watcher,栈顶也是该渲染watcher。这个需要记一下,我们下面需要用。 跟着,我们看下计算属性被访问时触发的computedGetter具体是怎么实现的:
首先从vm中拿到我们上面创建的这个计算属性对应的watcher,然后执行evaludate
很明显,evaluate就是执行下watcher的get方法,进而触发我们自定义的计算属性方法,如果该方法使用了data属性,则会针对这个watcher开始对这些属性进行依赖收集。比如下面的自定义计算属性就依赖了data中的属性counter,所以counter的dep.subs会收集这个计算属性watcher到其囊中。
但是,这里要注意了,在执行get方法时,里面首先会做一个pushTarget。 记得此前Dep.target是谁吧?本组件的渲染watcher。这时你要对计算属性watcher做依赖收集(即看下我们自定义计算属性的方法体里面用了哪些data属性,将watcher自身加入到这些属性的dep.subs中),所以就会将计算属性watcher压栈,将Dep.target设置成自身,这样所依赖的属性的dep.subs收集到的才是计算属性watcher,好让该属性改变时自动触发计算属性的更新。 完了后,popTarget,计算属性watcher出栈,此时栈顶又变为组件的渲染watcher,同时设置为Dep.target。 为什么呢?因为createComputedGetter紧跟着执行的watcher.depend()需要用到本组件的渲染watcher。
注意这里的this是计算属性watcher,而this.deps指的是该计算属性watcher所有收集到的依赖,比如由data多个属性的dep所组成,同时,这时Dep.target是组件的渲染watcher而非计算属性watcher。 那么这个方法所实现的逻辑就是,让组件渲染watcher订阅所有该计算属性watcher收集到的依赖。 也就是说,一旦这个计算属性所依赖的某个data属性修改了,将首先会通知计算属性watcher来将计算属性进行更新,然后通知渲染watcher来重新渲染整个组件。 而这,也就是为什么需要pushTarget/popTarget以及targetStack的原因,如前面所说,主要为了解决依赖收集过程中的嵌套问题。即在收集渲染watcher得依赖过程中,发现需要先收集计算属性watcher的依赖,这时就要将渲染watcher先压栈,把栈顶和Dep.target留给计算属性watcher,等计算属性watcher收集完依赖后,再恢复现场,把组件渲染watcher置顶,并设置Dep.target,以完成渲染watcher后续的依赖收集动作。
|
|
JavaScript知识库 最新文章 |
ES6的相关知识点 |
react 函数式组件 & react其他一些总结 |
Vue基础超详细 |
前端JS也可以连点成线(Vue中运用 AntVG6) |
Vue事件处理的基本使用 |
Vue后台项目的记录 (一) |
前后端分离vue跨域,devServer配置proxy代理 |
TypeScript |
初识vuex |
vue项目安装包指令收集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 | -2025/1/11 17:47:00- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |