详解Vue中的nextTick
Vue中的nextTick
涉及到Vue中DOM的异步更新,感觉很有意思,特意了解了一下。其中关于nextTick
的源码涉及到不少知识,很多不太理解,暂且根据自己的一些感悟介绍下nextTick
。
一、示例
先来一个示例了解下关于Vue中的DOM更新以及nextTick
的作用。
//模板
<div class="app">
<div ref="msgDiv">{{msg}}</div>
<div v-if="msg1">Message got outside $nextTick: {{msg1}}</div>
<div v-if="msg2">Message got inside $nextTick: {{msg2}}</div>
<div v-if="msg3">Message got outside $nextTick: {{msg3}}</div>
<button @click="changeMsg">
Change the Message
</button>
</div> //Vue实例
new Vue({
el: '.app',
data: {
msg: 'Hello Vue.',
msg1: '',
msg2: '',
msg3: ''
},
methods: {
changeMsg() {
this.msg = "Hello world."
this.msg1 = this.$refs.msgDiv.innerHTML
this.$nextTick(() => {
this.msg2 = this.$refs.msgDiv.innerHTML
})
this.msg3 = this.$refs.msgDiv.innerHTML
}
}
})
点击后:
从图中可以得知:msg1和msg3显示的内容还是变换之前的,而msg2显示的内容是变换之后的。其根本原因是因为Vue中DOM更新是异步的(详细解释在后面)。
二、应用场景
下面了解下nextTick
的主要应用的场景及原因:
1、在Vue生命周期的created()
钩子函数进行的DOM操作一定要放在Vue.nextTick()
的回调函数中。
因为在created()
钩子函数执行的时候 DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()
的回调函数中。与之对应的就是mounted()
钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题 。
2、在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()
的回调函数中。
具体原因在Vue的官方文档中详细解释:
Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的 Promise.then
和MessageChannel
,如果执行环境不支持,会采用 setTimeout(fn, 0)
代替。
例如,当你设置vm.someData = 'new value'
,该组件不会立即重新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。多数情况我们不需要关心这个过程,但是如果你想在 DOM 状态更新后做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们确实要这么做。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用Vue.nextTick(callback)
。这样回调函数在 DOM 更新完成后就会调用。
三、nextTick
源码浅析
作用:Vue.nextTick
用于延迟执行一段代码,它接受2个参数(回调函数和执行回调函数的上下文环境),如果没有提供回调函数,那么将返回promise
对象。
源码
export const nextTick = (function () {
const callbacks = []
let pending = false
let timerFunc function nextTickHandler () {
pending = false;
/*
之所以要slice复制一份出来是因为有的cb执行过程中又会往callbacks中加入内容,比如$nextTick的回调函数里又有$nextTick,
那么这些应该放入到下一个轮次的nextTick去执行,所以拷贝一份,遍历完成即可,防止一直循环下去。
*/
const copies = callbacks.slice()
callbacks.length =
for (let i = ; i < copies.length; i++) {
copies[i]()
}
} // the nextTick behavior leverages the microtask queue, which can be accessed
// via either native Promise.then or MutationObserver.
// MutationObserver has wider support, however it is seriously bugged in
// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
// completely stops working after triggering a few times... so, if native
// Promise is available, we will use it:
/* istanbul ignore if */
/*
nextTick行为利用了microtask队列, 先使用 Promise.resolve().then(nextTickHandler)来将异步回调
放入到microtask中,Promise 和 MutationObserver都可以使用,但是 MutationObserver 在IOS9.3以上的
WebView中有bug,因此如果满足第一项的话就可以执行,如果没有原生Promise就用 MutationObserver。
*/
if (typeof Promise !== 'undefined' && isNative(Promise)) {
var p = Promise.resolve()
var logError = err => { console.error(err) }
timerFunc = () => {
p.then(nextTickHandler).catch(logError)
// in problematic UIWebViews, Promise.then doesn't completely break, but
// it can get stuck in a weird state where callbacks are pushed into the
// microtask queue but the queue isn't being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
if (isIOS) setTimeout(noop)
}
} else if (typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// use MutationObserver where native Promise is not available,
// e.g. PhantomJS IE11, iOS7, Android 4.4
/*
创建一个MutationObserver,observe监听到DOM改动之后执行的回调 nextTickHandler
*/
var counter =
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter));
// 使用MutationObserver的接口,监听文本节点的字符内容
observer.observe(textNode, {
characterData: true
});
/*
每次执行timerFunc函数都会让文本节点的内容在0/1之间切换,切换之后将新赋值到那个我们MutationObserver监听的文本节点上去。
*/
timerFunc = () => {
counter = (counter + ) %
textNode.data = String(counter)
}
} else {
// fallback to setTimeout
/*
如果上面的两种都不支持的话,我们就使用setTimeout来执行
*/
timerFunc = () => {
setTimeout(nextTickHandler, )
}
} return function queueNextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
});
/* 如果pending为true,表明本轮事件循环中已经执行过 timerFunc(nextTickHandler, 0) */
if (!pending) {
pending = true
timerFunc()
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise((resolve, reject) => {
_resolve = resolve
})
}
}
})()
首先,先了解nextTick
中定义的三个重要变量。
callbacks:
用来存储所有需要执行的回调函数
pending:
用来标志是否正在执行回调函数
timerFunc:
用来触发执行回调函数
var callbacks = []; // 缓存函数的数组
var pending = false; // 是否正在执行
var timerFunc; // 保存着要执行的函数
接下来,了解nextTickHandler()
函数。
function nextTickHandler () {
pending = false;
// 拷贝出函数数组副本
var copies = callbacks.slice();
// 把函数数组清空
callbacks.length = ;
// 依次执行函数
for (var i = ; i < copies.length; i++) {
copies[i]();
}
}
这个函数就是$nextTick内实际调用的函数。
接下来,是vue分了三种情况来延迟调用以上这个函数,因为$nextTick目的就是把传进来的函数延迟到dom更新后再使用,所以这里依次优雅降序的使用js的方法来做到这一点。
1、先判断是否原生支持promise,如果支持,则利用promise来触发执行回调函数;
if (typeof Promise !== 'undefined' && isNative(Promise)) {
var p = Promise.resolve();
var logError = function (err) { console.error(err); };
timerFunc = function () {
p.then(nextTickHandler).catch(logError);
if (isIOS) { setTimeout(noop); }
};
}
如果浏览器支持Promise,那么就用Promise.then的方式来延迟函数调用,Promise.then方法可以将函数延迟到当前函数调用栈最末端,也就是函数调用栈最后调用该函数。从而做到延迟。
2、否则,如果支持MutationObserver,则实例化一个观察者对象,观察文本节点发生变化时,触发执行所有回调函数。
else if (typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) { var counter = ;
var observer = new MutationObserver(nextTickHandler);
var textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true
});
timerFunc = function () {
counter = (counter + ) % ;
textNode.data = String(counter);
};
}
MutationObserver是h5新加的一个功能,其功能是监听dom节点的变动,在所有dom变动完成后,执行回调函数。
具体有以下几点变动的监听
- childList:子元素的变动
- attributes:属性的变动
- characterData:节点内容或节点文本的变动
- subtree:所有下属节点(包括子节点和子节点的子节点)的变动
可以看出,以上代码是创建了一个文本节点,来改变文本节点的内容来触发的变动,因为我们在数据模型更新后,将会引起dom节点重新渲染,所以,我们加了这样一个变动监听,用一个文本节点的变动触发监听,等所有dom渲染完后,执行函数,达到我们延迟的效果。
3、如果都不支持,则利用setTimeout设置延时为0
else {
timerFunc = function () {
setTimeout(nextTickHandler, );
};
}
利用setTimeout的延迟原理,setTimeout(func, 0)会将func函数延迟到下一次函数调用栈的开始,也就是当前函数执行完毕后再执行该函数,因此完成了延迟功能。
最后是queueNextTick
函数。因为nextTick
是一个即时函数,所以queueNextTick
函数是返回的函数,接受用户传入的参数,用来往callbacks里存入回调函数。
return function queueNextTick (cb, ctx) {
var _resolve;
callbacks.push(function () {
if (cb) { cb.call(ctx); }
if (_resolve) { _resolve(ctx); }
});
// 如果没有函数队列在执行才执行
if (!pending) {
pending = true;
timerFunc();
}
// promise化
if (!cb && typeof Promise !== 'undefined') {
console.log('进来了')
return new Promise(function (resolve) {
_resolve = resolve;
})
}
}
这个return的函数就是我们实际使用的闭包函数,每一次添加函数,都会想callbacks这个函数数组入栈。然后监听当前是否正在执行,如果没有,执行函数。这个很好理解。下面一个if是promise化。
this.$nextTick(function () { })
// promise化
this.$nextTick().then(function () { }.bind(this))
以上代码中第二种写法我们不常见把,直接调用$nextTick函数然后用promise格式去书写代码,不过这个then里面需要手动绑定this,vue内部没有给做处理。
这就是一个this.$nextTick的实现,其中利用了优雅降序的巧妙手法,使代码尽可能优化。而且还提供了promise的写法,虽然我们不经常用,但是有总比没有好。
详解Vue中的nextTick的更多相关文章
- 详解Vue中的computed和watch
作者:小土豆 博客园:https://www.cnblogs.com/HouJiao/ 掘金:https://juejin.cn/user/2436173500265335 1. 前言 作为一名Vue ...
- 详解Vue中watch的高级用法
我们通过实例代码给大家分享了Vue中watch的高级用法,对此知识点有需要的朋友可以跟着学习下. 假设有如下代码: <div> <p>FullName: {{fullName} ...
- 详解Vue中的虚拟DOM
摘要: 什么是虚拟DOM? 作者:浪里行舟 Fundebug经授权转载,版权归原作者所有. 前言 Vue.js 2.0引入Virtual DOM,比Vue.js 1.0的初始渲染速度提升了2-4倍,并 ...
- 详解Vue中的插槽
作者: 小土豆 博客园:https://www.cnblogs.com/HouJiao/ 掘金:https://juejin.im/user/2436173500265335 什么是插槽 在日常的项目 ...
- 详解JavaScript中的原型
前言 原型.原型链应该是被大多数前端er说烂的词,但是应该还有很多人不能完整的解释这两个内容,当然也包括我自己. 最早一篇原型链文章写于2019年07月,那个时候也是费了老大劲才理解到了七八成,到现在 ...
- Vue中之nextTick函数源码分析
Vue中之nextTick函数源码分析 1. 什么是Vue.nextTick()?官方文档解释如下:在下次DOM更新循环结束之后执行的延迟回调.在修改数据之后立即使用这个方法,获取更新后的DOM. 2 ...
- 详解Vue.js 技术
本文主要从8个章节详解vue技术揭秘,小编觉得挺有用的,分享给大家. 为了把 Vue.js 的源码讲明白,课程设计成由浅入深,分为核心.编译.扩展.生态四个方面去讲,并拆成了八个章节,如下: 准备工作 ...
- 详解vue的数据binding原理
自从angular火了以后,各种mv*框架喷涌而出,angular虽然比较火,但是他的坑还是蛮多的,还有许多性能问题被人们吐槽.比如坑爹的脏检查机制,数据binding是受人喜爱的,脏检查就有点…性能 ...
- 详解Vue 方法与事件处理器
本篇文章主要介绍了详解Vue 方法与事件处理器 ,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 方法与事件处理器 方法处理器 可以用 v-on 指令监听 DOM 事件 ...
随机推荐
- (android高仿系列)今日头条 --新闻阅读器 (转载)
非常不错,原文地址:http://blog.csdn.net/vipzjyno1/article/details/26514543
- eclipse闪退解决(转)
最近帮同事解决一个eclipse闪退解决的问题,从网上找了N多方法皆无效,最后用一个园友的博客上的方法解决了,特转载一下. 解决办法: 删除文件 [workspace]/.metadata/.plug ...
- oracle创建透明网关出现的问题
解决方案:创建HS_TRANSACTION_LOG表 DROP TABLE HS_TRANSACTION_LOG go CREATE TABLE HS_TRANSACTION_LOG( GLOBAL_ ...
- FireDAC 下的 Sqlite [4] - 创建数据库
建立数据库的代码: {建立内存数据库的一般代码:} begin FDConnection1.DriverName := 'SQLite'; //同 FDConnection1.Params.Add(' ...
- CentOS 7解压安装PHP7.1.21
下载php yum install -y wget wget http://cn2.php.net/distributions/php-7.1.21.tar.gz 解压 tar -zxvf php-7 ...
- 在windows server 2008 R2 64bit上面配置PI OPC Server的DCOM
今天想配置PI OPC SERVER的DCOM设置,但是发现在“运行dcomcnfg->组件服务-计算机-我的电脑-DCOM设置”中找不到PI OSI DA Server.如下图所示 这是以前从 ...
- C#远程调用技术WebService修炼手册
一.课程介绍 一位伟大的讲师曾经说过一句话:事物存在即合理!意思就是说:任何存在的事物都有其存在的原因,存在的一切事物都可以找到其存在的理由,我们应当把焦点放在因果关联的本质上.所以在本次分享课开课之 ...
- 在 DELPHI 中 procedure 型变量与 method 型变量的区别
Procedure型变量: 在DELPHI中,函数.过程的地址可以赋给一个特殊类型的变量,变量可用如下方式声明: var p : procedure(num:integer); //过程 或: var ...
- 认识TWICImage类
Graphics 单元新增了 TWICImage 类, 该类的功能源于新增的 Wincodec.pas 单元. WIC 支持的图像有 BMP.PNG.ICO.JPEG.GIF.TIFF.HDP(HDP ...
- android iOS 编码问题害死人!
android 与后端服务器进行通信时,默认使用的编码格式是asi. 而iOS与后端通信时,获取的数据到iOS端默认被utf-8进行编码.所以,我们常常出现android能够从服务器端获取到数据,但是 ...