Vue中之nextTick函数源码分析详解
1. 什么是vue.nexttick()?
官方文档解释如下:
在下次dom更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的dom。
2. 为什么要使用nexttick?
<!doctype html> <html> <head> <title>演示vue</title> <script src="https://tugenhua0707.github.io/vue/vue1/vue.js"></script> </head> <body> <div id="app"> <template> <div ref="list"> {{name}} </div> </template> </div> <script> new vue({ el: '#app', data: { name: 'aa' }, mounted() { this.updatedata(); }, methods: { updatedata() { var self = this; this.name = 'bb'; console.log(this.$el.textcontent); // aa this.$nexttick(function(){ console.log(self.$el.textcontent); // bb }); } } }); </script> </body> </html>
如上代码 在页面视图上显示bb,但是当我在控制台打印的时候,获取的文本内容还是 aa,但是使用 nexttick后,获取的文本内容就是最新的内容bb了,因此在这种情况下,我们可以使用nexttick函数了。
上面的代码为什么改变this.name = 'bb';后,再使用console.log(this.$el.textcontent);打印的值还是aa呢?那是因为设置name的值后,dom还没有更新到,所以获取值还是之前的值,但是我们放到nexttick函数里面的时候,代码会在dom更新后执行,因此dom更新后,再去获取元素的值就可以获取到最新值了。
理解dom更新:在vue中,当我们修改了data中的某一个值后,并不会立即反应到该el中,vue将对更改的数据放到watcher的一个异步队列中,只有在当前任务空闲时才会执行watcher队列任务,这就有一个延迟时间,因此放到 nexttick函数后就可以获取该el的最新值了。如果我们把上面的nexttick改成settimeout也是可以的。
3. vue源码详解之nexttick(源码在 vue/src/core/util/env.js)
在理解nexttick源码之前,我们先来理解下 html5中新增的 mutationobserver的api,它的作用是用来监听dom变动的接口,它能监听一个dom对象发生的子节点删除,属性修改,文本内容修改等等。
nexttick源码如下:
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(0) callbacks.length = 0 for (let i = 0; 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 = 1 var observer = new mutationobserver(nexttickhandler) var textnode = document.createtextnode(string(counter)); // 使用mutationobserver的接口,监听文本节点的字符内容 observer.observe(textnode, { characterdata: true }); /* 每次执行timerfunc函数都会让文本节点的内容在0/1之间切换,切换之后将新赋值到那个我们mutationobserver监听的文本节点上去。 */ timerfunc = () => { counter = (counter + 1) % 2 textnode.data = string(counter) } } else { // fallback to settimeout /* 如果上面的两种都不支持的话,我们就使用settimeout来执行 */ timerfunc = () => { settimeout(nexttickhandler, 0) } } 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 是一个闭包函数,代码立即执行,在理解整体代码之前,我们先来看个类似的demo,如下代码:
<!doctype html> <html> <head> <title>演示vue</title> </head> <body> <div id="app"> </div> <script> var nexttick = (function(){ return function queuenexttick(cb, ctx) { if (cb) { try { cb.call(ctx) } catch (e) { console.log('出错了'); } } } })(); // 方法调用 nexttick(function(){ console.log(2); // 打印2 }) </script> </body> </html>
demo代码和上面的代码很类似。
我们也可以再来抽离使用nexttick做demo代码如下:
var nexttick2 = (function(){ const callbacks = []; let pending = false; let timerfunc; function nexttickhandler () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } } if (typeof promise !== 'undefined') { var p = promise.resolve() var logerror = err => { console.error(err) } timerfunc = () => { p.then(nexttickhandler).catch(logerror) } } else if (typeof mutationobserver !== 'undefined' || // 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 var counter = 1 var observer = new mutationobserver(nexttickhandler) var textnode = document.createtextnode(string(counter)) observer.observe(textnode, { characterdata: true }) timerfunc = () => { counter = (counter + 1) % 2 textnode.data = string(counter) } } else { // fallback to settimeout /* istanbul ignore next */ timerfunc = () => { settimeout(nexttickhandler, 0) } } return function queuenexttick (cb, ctx) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleerror(e, ctx, 'nexttick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true timerfunc() } if (!cb && typeof promise !== 'undefined') { return new promise((resolve, reject) => { _resolve = resolve }) } } })(); nexttick2(function(){ console.log(2222); });
如上代码是nexttick源码的抽离,为了更好的理解nexttick,做了如上的demo。
我们再来理解一下整体的代码的含义;
先定义数组 callbacks = [];来存放所有需要执行的回调函数,定义let pending = false;判断本轮事件是否执行过 timerfunc(nexttickhandler, 0)这个函数,为true说明执行过 timefunc函数,接着定义nexttickhandler函数,该函数的作用是依次遍历数组callbacks保存的函数,依次执行;
请看源代码如下:
function nexttickhandler () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } }
然后就是三个判断了,代码如下:
if (typeof promise !== 'undefined' && isnative(promise)) { var p = promise.resolve(); var logerror = err => { console.error(err) } timerfunc = () => { p.then(nexttickhandler).catch(logerror); } else if (typeof mutationobserver !== 'undefined' && ( isnative(mutationobserver) || // phantomjs and ios 7.x mutationobserver.tostring() === '[object mutationobserverconstructor]' )){ var counter = 1 var observer = new mutationobserver(nexttickhandler) var textnode = document.createtextnode(string(counter)) observer.observe(textnode, { characterdata: true }) timerfunc = () => { counter = (counter + 1) % 2 textnode.data = string(counter) } } else { timerfunc = () => { settimeout(nexttickhandler, 0) } }
首先判断是否支持promise对象,如果支持的话,定义了timefunc()函数,为了下一步调用做准备,然后继续判断是否支持该对象 mutationobserver,如果支持的话,创建一个文本节点,监听该节点数据是否发生改变,如果发生改变的话,调用timerfunc函数,counter值会在0/1切换,如果值改变了的话,把该数据值赋值到data属性上面去,那么data属性发生改变了,就会重新渲染页面(因为vue是通过object.defineproperty来监听属性值是否发生改变),如果上面两种情况都不满足的话,那么直接使用settimeout来执行nexttickhandler函数了;
最后nexttick代码返回一个函数,代码如下:
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) } }) if (!pending) { pending = true timerfunc() } if (!cb && typeof promise !== 'undefined') { return new promise((resolve, reject) => { _resolve = resolve }) } }
代码的含义是:传入的cb是否是函数,ctx参数是否是一个对象,如果cb是一个函数的话,使用cb.call(ctx), 如果timerfunc没有执行过的话,那么pending为false,因此执行 timerfunc()函数。基本的思路就是这样的。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读
-
详解Vue源码学习之callHook钩子函数
-
Vue源码分析之Vue实例初始化详解
-
Vue源码分析之虚拟DOM详解
-
Vue.js源码分析之自定义指令详解
-
jQuery源码分析之jQuery中的循环技巧详解
-
Vue中之nextTick函数源码分析详解
-
详解Vue源码学习之callHook钩子函数
-
CI框架源码解读之URI.php中_fetch_uri_string()函数用法分析_PHP
-
CI框架源码解读之URI.php中_fetch_uri_string()函数用法分析,ciuristring_PHP教程
-
CI框架源码解读之URI.php中_fetch_uri_string()函数用法分析,ciuristring_PHP教程