注册
|
登录
发帖
热搜
活动
交友
discuz
论坛
BBS
翼度工具
翼度网址导航
开发工具
Linux命令速查
网页设计配色表
在线制作icon
颜色代码选取器
翼度科技
»
论坛
›
编程开发
›
JavaScript
›
查看内容
返回列表
发新帖
Vue之关于异步更新细节
郑敏华
郑敏华
当前离线
积分
21
7
主题
7
帖子
21
积分
新手上路
新手上路, 积分 21, 距离下一级还需 29 积分
新手上路, 积分 21, 距离下一级还需 29 积分
积分
21
发消息
显示全部楼层
前言
Vue官网对于异步更新的介绍如下:
Vue 在更新 DOM 时是异步执行的。
只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
如果同一个 watcher 被多次触发,只会被推入到队列中一次。
这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的
Vue使用Object.defineProperty对数据劫持后,当对对象进行set操作,就会触发视图更新。
更新逻辑
以下面实例来分析视图更新处理逻辑:
<div>{{ message }}</div>
<button @click="handleClick">更新</button>
new Vue({
data: {
message: ''
},
methods: {
handleClick() {
this.message = Date.now();
}
}
})
复制代码
当点击更新按钮后会对已劫持的属性message做赋值操作,此时会触发Object.defineProperty的set操作。
Object.defineProperty set操作
Object.defineProperty的set函数的设置,实际上最核心的逻辑就是触发视图更新,具体代码逻辑如下:
set: function reactiveSetter (newVal) {
// 其他逻辑
// 触发视图更新
dep.notify();
}
复制代码
每个属性都会对应一个Dep对象,当对属性进行赋值时就会调用Dep的notify实例方法,该实例方法的功能就是是通知视图需要更新。
Dep notify实例方法
notify实例方法的代码逻辑如下:
Dep.prototype.notify = function notify () {
// stabilize the subscriber list first
var subs = this.subs.slice();
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
复制代码
subs中存储是watcher对象,每个Vue实例都存在一个与视图更新关联的watcher对象,该对象的创建是在$mount阶段,具体看查看之前的文章
Vue实例创建整体流程
。
代表属性的Dep对象与watcher对象的关联是在render函数调用阶段具体属性获取时建立的即依赖收集
复制代码
notify方法会执行与当前属性关联的所有watcher对象的update方法,必然会存在一个视图更新相关的watcher。
watcher对象的按照分类实际上分为两类:
视图更新相关的,每一个Vue实例都存在一个此类的watcher对象
逻辑计算相关的,计算属性和watch监听所创建的watcher对象
Watcher update实例方法
update实例方法的代码逻辑具体如下:
Watcher.prototype.update = function update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
};
复制代码
lazy、sync都是Watcher的属性,分别表示:
lazy:表示懒处理,即延迟相关处理,用于处理计算属性
computedsync:表示同步执行,即触发属性更新就立即更新视图
从上面逻辑中可知,默认是queueWatcher处理即开启一个队列,并缓冲在同一事件循环中发生的所有数据变更,即视图是异步更新的。
这里需要注意的一点是:
queueWatcher中必然存在视图更新的watcher对象,不会存在计算属性computed对应的watcher(computed对应的watcher对象lazy属性默认为true),可能存在watch API对应的用户性质的watcher对象
queueWatcher执行逻辑
function queueWatcher (watcher) {
var id = watcher.id;
if (has[id] == null) {
has[id] = true;
if (!flushing) {
queue.push(watcher);
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
var i = queue.length - 1;
while (i > index && queue[i].id > watcher.id) {
i--;
}
queue.splice(i + 1, 0, watcher);
}
// queue the flush
if (!waiting) {
waiting = true;
nextTick(flushSchedulerQueue);
}
}
}
复制代码
实际上面逻辑主要分成3点:
对于同一个watcher对象,使用has对象结构+id为key来判断队列中是否已存在对应watcher对象,如果存在就不会将其添加到queue中
通过flushing标识区分当在清空队列过程中和正常情况下,如何向queue中添加watcher
通过waiting标识区分是否要执行nextTick即清空queue的动作
因为queue是全局变量,在此步骤之前就将watcher对象添加到queue,如果waiting为true就标识已经调用nextTick实现异步处理queue了,就不要再次调用nextTick
复制代码
从上面整体逻辑可知,queueWacther的逻辑主要就两点:
判断是否重复watcher,对于不重复的watcher将其添加到queue中
调用nextTick开启异步处理queue操作即flushSchedulerQueue函数执行
nextTick + flushSchedulerQueue
nextTick函数实际上跟$nextTick是相同的逻辑,主要的区别就是上下文的不同,即函数的this绑定值的不同。
使用macroTask API还是microTask API来执行flushSchedulerQueue
复制代码
而flushSchedulerQueue函数就是queue的具体处理逻辑,主要逻辑如下:
function flushSchedulerQueue () {
flushing = true;
var watcher, id;
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child)
// 2. A component's user watchers are run before its render watcher (because
// user watchers are created before the render watcher)
// 3. If a component is destroyed during a parent component's watcher run,
// its watchers can be skipped.
queue.sort(function (a, b) { return a.id - b.id; });
for (index = 0; index < queue.length; index++) {
watcher = queue[index];
id = watcher.id;
has[id] = null;
watcher.run();
}
var activatedQueue = activatedChildren.slice();
var updatedQueue = queue.slice();
resetSchedulerState();
// call component updated and activated hooks
callActivatedHooks(activatedQueue);
callUpdatedHooks(updatedQueue);
}
复制代码
flushSchedulerQueue函数的主要逻辑可以总结成如下几点:
对队列queue中watcher对象进行排序
遍历queue执行每个watcher对象的run方法
重置控制queue的相关状态,用于下一轮更新
执行组件的updated和activated生命周期
这里就不展开了,需要注意的是activated是针对于keep-alive下组件的特殊处理,updated生命周期是先子组件再父组件的,队列queue的watcher对象是按照父组件子组件顺序排列的,所以在源码中updated生命周期的触发是倒序遍历queue触发的。
首先说说watcher对象的run实例方法,该方法的主要逻辑就是执行watcher对象的getter属性和cb属性对应的函数。
上面说过watcher对象的按照分类实际上分为两类:
视图更新相关的,每一个Vue实例都存在一个此类的watcher对象
逻辑计算相关的,计算属性和watch监听所创建的watcher对象
watcher对象的getter属性和cb属性就是对应着上面各类watcher的实际处理逻辑,例如watch API对应的getter属性就是监听项,cb属性才是具体的处理逻辑。
为什么需要对queue中watcher对象进行排序?
实际上Vue源码中有相关说明,这主要涉及到嵌套组件Vue实例创建、render watch和用户watch创建的时机。
每个组件都是一个Vue实例,嵌套组件创建总是从父组件Vue实例开始创建的,在父组件patch阶段才创建子组件的Vue实例。
而这个顺序决定了watcher对象的id值大小问题:
父组件的所有watcher对象id < 子组件的所有watcher对象id
render watch实际上就是与视图更新相关的watcher对象,该对象是其对应的Vue实例创建的末期即挂载阶段才创建的,是晚于用户watch即计算属性computed和watch API创建的watcher对象,所以:
render watch的id < 所有用户watch的id的
子组件可能是更新触发源,如果父组件也需要更新视图,这样queue队列中子组件的watcher对象位置会在父组件的watcher对象之前,对queue中watcher对象进行排序就保证了:
视图更新时 父组件 总是先于 子组件开始更新操作,而每个组件对应的视图渲染的watcher最后再执行(即用户watcher对象对应的逻辑先执行)
总结
Vue异步更新的过程还是非常清晰的:
对属性赋值触发Dep对象notify方法执行
继而执行Watcher对象的update方法将对象保存到队列queue中
继而调用mircoTask API或macroTask API执行queue中任务
对队列中watcher进行排序,保证顺序执行的正确性,调用其对应run方法来实现视图更新和相关逻辑更新操作
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
来源:
https://www.jb51.net/javascript/322395ip9.htm
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
上一篇:
Vue elementui如何实现表格selection的默认勾选
下一篇:
Vue 组件之间的通信方式详解
发表于 2024-6-11 09:38:28
举报
回复
使用道具
分享
返回列表
发新帖
本版积分规则
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
|
立即注册
快速回复
快速回复
返回顶部
返回顶部
返回列表
返回列表