源码解读-vue是如何实现$nextTick的

前言:

本文需要一定的事件循环相关知识,想了解事件循环的小伙伴可以看
这里

本文要弄明白下面两件事:

  • $nextTick什么时候执行
  • vuenextTick$nextTick区别
1.查看源码中的$nextTick方法
Vue.prototype.$nextTick = function(fn) {
	return nextTick(fn, this)
};

可以看到$nextTick调用的也是nextTick方法,只不过$nextTick默认绑定了this上下文,也就是Vue实例对象

2.下面查看nextTick方法
function nextTick(cb, ctx) {
	var _resolve;
	callbacks.push(function() {
		if (cb) {
			try {
				cb.call(ctx);
			} catch (e) {
				handleError(e, ctx, 'nextTick');
			}
		} else if (_resolve) {
			_resolve(ctx);
		}
	});
	if (!pending) {
		pending = true;
		timerFunc();
	}
	// $flow-disable-line
	if (!cb && typeof Promise !== 'undefined') {
		return new Promise(function(resolve) {
			_resolve = resolve;
		})
	}
}
  • callbacks
    一个异步队列,传入的回调函数会被存储在这个数组内,等待时机执行
  • if (!cb && typeof Promise !== 'undefined')
    如果没有回调方法,并且当前环境支持Promise,那么nextTick返回的是一个Promise对象
3.查看timerFunc方法
if (typeof Promise !== 'undefined' && isNative(Promise)) {
	var p = Promise.resolve();
	timerFunc = function() {
		p.then(flushCallbacks);
		//ios的UIWebViews中,回调推送到微任务队列后不会立即刷新,通过添加空定时器来强制刷新微任务队列
		if (isIOS) {
			setTimeout(noop);
		}
	};
	isUsingMicroTask = true;
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
		isNative(MutationObserver) ||
		// PhantomJS and iOS 7.x
		MutationObserver.toString() === '[object MutationObserverConstructor]'
	)) {
	//如果支持MutationObserver
	var counter = 1;
	var observer = new MutationObserver(flushCallbacks);
	var textNode = document.createTextNode(String(counter));
	observer.observe(textNode, {
		characterData: true
	});
	timerFunc = function() {
		counter = (counter + 1) % 2;
		textNode.data = String(counter);
	};
	isUsingMicroTask = true;
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
	// but it is still a better choice than setTimeout.
	timerFunc = function() {
		setImmediate(flushCallbacks);
	};
} else {
	// Fallback to setTimeout.
	timerFunc = function() {
		setTimeout(flushCallbacks, 0);
	};
}

经过一系列的判断方法,用来判断当前执行环境到底支持哪种方法,可以看到最后timerFunc执行的都是flushCallbacks方法。

4.flushCallbacks方法
function flushCallbacks() {
	pending = false;
	var copies = callbacks.slice(0);
	callbacks.length = 0;
	for (var i = 0; i < copies.length; i++) {
		copies[i]();
	}
}

这个方法执行时,会将回调队列做一个浅拷贝,并且初始化这个队列,防止影响下次事件循环,接下来将浅拷贝后的数组进行循环并执行。

到这步,$nextTick方法就算执行完毕了。

总结:

nextTick$nextTick方法基本一致,$nextTick方法默认绑定vue实例为上下文。nextTick上下文需要传入,若不传入则默认绑定为window
$nextTick 方法执行时会判断当前执行环境是否支持Promise若支持则放入Promise.then()内,若不支持则判断是否支持MutationObserver,如果不支持的话则会判断是否支持setImmediate方法,否则的话会加入setTimeout中。

一次事件循环(event loop)的过程

宏任务 => 所有微任务 => ui渲染

其中 Promise.then以及MutationObserver为微任务,在当前事件循环执行。

setImmediatesetTimeout为宏任务,在下次事件循环执行。

下面代码为vue源码中的nextTick相关代码,我做了部分注释。

var isUsingMicroTask = false; //是否使用MutationObserver来触发回调函数执行,另作他用,可以在源码中搜索用到的地方,本文不做深究
var callbacks = []; //存储回调函数
var pending = false; //此次nextTick是否执行中的标记
var timerFunc; //触发方法

function noop(a, b, c) {} //空函数,ios用来强制刷新微任务队列
//执行回调队列
function flushCallbacks() {
	pending = false; //执行中标记置否
	var copies = callbacks.slice(0); //浅拷贝回调
	callbacks.length = 0; // 清空数组
	for (var i = 0; i < copies.length; i++) {
		copies[i](); //执行存储的函数
	}
}
//判断当前环境是否支持Promise
//Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,
//如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
if (typeof Promise !== 'undefined' && isNative(Promise)) {
	var p = Promise.resolve();
	timerFunc = function() {
		p.then(flushCallbacks); //将flushCallbacks放入微任务
		//ios的UIWebViews中,回调推送到微任务队列后不会立即刷新,通过添加空定时器来强制刷新微任务队列
		if (isIOS) {
			setTimeout(noop);
		}
	};
	isUsingMicroTask = true;
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
		isNative(MutationObserver) ||
		// PhantomJS and iOS 7.x
		MutationObserver.toString() === '[object MutationObserverConstructor]'
	)) {
	//如果支持MutationObserver
	var counter = 1;
	var observer = new MutationObserver(flushCallbacks);
	var textNode = document.createTextNode(String(counter));
	observer.observe(textNode, {
		characterData: true
	});
	timerFunc = function() {
		counter = (counter + 1) % 2;
		textNode.data = String(counter);
	};
	isUsingMicroTask = true;
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
	// but it is still a better choice than setTimeout.
	timerFunc = function() {
		setImmediate(flushCallbacks);
	};
} else {
	// Fallback to setTimeout.
	timerFunc = function() {
		setTimeout(flushCallbacks, 0);
	};
}

function nextTick(cb, ctx) {
	var _resolve;
	callbacks.push(function() { //将函数加入回调数组中,函数执行时会自动执行传入的回调函数
		if (cb) {
			try {
				cb.call(ctx); //更改this为传入的ctx,并执行,$nextTick为vue。nextTick为传入的上下文,如果没传则this为window
			} catch (e) {
				handleError(e, ctx, 'nextTick');
			}
		} else if (_resolve) {
			_resolve(ctx);
		}
	});
	if (!pending) {
		pending = true;
		timerFunc();  //执行函数
	}
	// $flow-disable-line
	if (!cb && typeof Promise !== 'undefined') {
		return new Promise(function(resolve) {
			_resolve = resolve;
		})
	}
}
Vue.prototype.$nextTick = function(fn) {
	return nextTick(fn, this)
};

如果想获取更多内容,可以扫描下方二维码,一起学习,一起进步。
左道前端

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值