本文主要介绍了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>
<p id="app">
<template>
<p ref="list">
{{name}}
</p>
</template>
</p>
<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>
<p id="app">
</p>
<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.nexttick 的实现方法浅析
node.js中的process.nexttick使用实例
node.js中的定时器nexttick()和setimmediate()区别分析_node.js
以上就是vue中nexttick函数源码详解的详细内容。