前言
有人说:是为了提高性能,对,根本上也是这么个道理 ;那到底是如何做的呢 ?
其实在vue
中,响应式数据是组件级的,也就是说,每一次的更新都是渲染整个组件,如果是同步的话,根据我们前边理解的响应式数据原理,一旦修改了data属性,便会触发对应的 watcher
,然后调用对应 watcher
下的 update
方法更新视图,那么结果显而易见,太频繁了 !假设:如下代码
// 省略多余模板语法
data () {
a:1,
b:2,
c:3
}
//如果我们按照同步的逻辑,修改data属性,this.a = 10; this.b = 20; this.c = 30;
//就会调用三次update渲染视图,岂不是很耗性能 ?而且体验也不好。
所以 vue 采用的是异步渲染
接下来,我们来了解一下,前边也有讲过响应式数据原理,不了解的童鞋可以回过头去看看 数据响应式 Go,这里我就接着数据更新方法update
开始;
queueWatcher
src/croe/observer/watcher.js 166 行
,这里的更新先不考虑计算属性和同步,我们顺着 queueWatcher
往下走,
update () {
/* istanbul ignore else */
if (this.lazy) { // 计算属性 依赖的数据发生变化了 会让计算属性的watcher的dirty变成true
this.dirty = true
} else if (this.sync) { // 同步 watcher
this.run()
} else {
// 将要更新的 watcher 放入队列
// 它们依赖同一个 dep 收集器 ,不了解的可以查看上边 数据响应式 链接
queueWatcher(this)
}
}
queueWatcher 逻辑解读
src/core/observer.scheduler.js 164行
,主要就是实现一个 watcher
队列 ,每一次的 update
都放入到队列中,然后进行统一异步处理 。 看代码:
export function queueWatcher (watcher: Watcher) {
// 过滤 watcher
const id = watcher.id
if (has[id] == null) {
has[id] = true
if (!flushing) {
// 将watcher放到队列中
queue.push(watcher)
} else {
// 通过对 id 的判断,这里的 id 是自加1,可查看 watcher.js 源码,
// 如果已经刷新了,则赋值当前的id , 如果id超过了,将运行如下代码
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// 如果不等了,则进行刷新
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {
// 该方法做了刷新前的 beforUpdate 方法调用,然后 watcher.run()
flushSchedulerQueue()
return
}
// 在下一次tick中刷新 watcher 队列 (借用nextTick)
//(包含同一watcher的多个data属性),
// 这里的nextTick 就是我们的常用 api => this.$nextTick()
nextTick(flushSchedulerQueue)
}
}
}
好了,通过源码简单的分析,明白为啥 vue
为啥采用异步更新了吧,原因很简单,因为vue
是组件级更新视图,每一次update
都要渲染整个组件,为了提高性能,采用了队列的形式,存储同一个watcher
的所有data属性变化,然后统一调用nextTick
方法进行更新渲染(有且只调用一次)。
nextTick 原理
问题来了,nextTick
方法是异步的 ,那么它又是如何实现的异步更新呢 ?来看张图
从图来看,调用了 nextTick
之后,将watcher
队列回调函数暂时存入了一个数组callbacks
中,然后才依次调用 timeFun()
方法执行,而真正让watcher
异步的关键就在这儿,我们通过代码来看一下:
src/core/util/next-tick.js 87 行
export function nextTick (cb?: Function, ctx?: Object) {
// flushSchedulerQueue 会使用 nextTick 保证当前视图渲染完成
let _resolve
callbacks.push(() => { // 暂存 watcher 队列
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) { // 状态改变后,调用 timerFun() 方法
pending = true
timerFunc() // 重点,重点,重点! 我们进去看一下
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
timerFunc 异步关键
timerFunc
方法 src/core/util/next-tick.js 33 行
很清晰,它对当前的环境进行了判断,如果支持promise
就用 promise
依次往下: MutationObserver , setImmediate , setTimeout
这四个分别都是异步解决方案,除了 setTimeout
是宏观任务以外,其它三个都是微观任务;
let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks) // then 里边执行 flushCallbacks
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks) // setImmediate 回调里边
}
} else {
timerFunc = () => {
setTimeout(flushCallbacks, 0) // setTimeout 回调里边
}
}
总结
nextTick 方法主要是使用了宏任务和微任务,定义了一个异步方法.多次调用 nextTick 会将方法存入 队列中,通过这个异步方法清空当前队列。 所以这个 nextTick 方法就是异步方法 。
而我们平常使用的api :vue.nextTick()
也是如此 .
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!