Vue.js原理分析之nextTick实现详解
作者:minijun2333 发布时间:2024-05-13 09:38:08
前言
tips:第一次发技术文章,篇幅比较简短,主要采取文字和关键代码表现的形式,希望帮助到大家。(若有不正确还请多多指正)
nextTick作用和用法
用法:nextTick接收一个回调函数作为参数,它的作用是将回调延迟到下一次DOM更新之后执行,如果没有提供回调函数参数且在支持Promise的环境中,nextTick将返回一个Promise。
适用场景:开发过程中,开发者需要在更新完数据之后,需要对新DOM做一些操作,其实我们当时无法对新DOM进行操作,因为这时候还没有重新渲染,这时候nextTick就派上了用场。
nextTick实现原理
下面我们介绍下nextTick工作原理:
首先我们应该了解到更新完数据(状态)之后,DOM更新这个动作并不是同步进行的,而是异步的。Vue.js中有一个队列,每当需要渲染时,会将Watcher推送到这个队列中,等下一次事件循环中再让Watcher触发渲染流程。这里我们可能会有两个疑问:
**1.为什么更新DOM是异步的?**
我们知道从Vue2.0开始使用虚拟DOM进行渲染,变化侦测只发送到组件级别,组件内部则通过虚拟DOM的diff(比对)而进行局部渲染,而在同一次事件循环中组件假如收到两份通知,组件是否会进行两次渲染呢?事实上一次事件循环组件会在所有状态修改完毕之后只进行一次渲染操作。
**2.什么是事件循环?**
javascript是单线程脚本语言,它具有非阻塞特性,之所以非阻塞是由于在处理异步代码时,主线程会挂起这个任务,当异步任务处理完毕之后会根据一定的规则去执行异步任务的回调,异步任务分宏任务(macrotast)和微任务(microtast),它们会被分配到不同的队列中,当执行栈所有任务执行完毕之后,会先检查微任务队列中是否有事件存在,优先执行微任务队列事件对应的回调,直至为空。然后再执行宏任务队列中事件的回调。无限重复这个过程,形成一个无限循环就叫做事件循环。
常见微任务包括:Promise 、MutationObserver、Object.observer、process.nextTick等
常见宏任务包括:setTimeout、setInterval、setImmediate、MessageChannel、requestAnimation、UI交互事件等
微任务如何注册?
nextTick会将回调添加到异步任务队列中延迟执行,在执行回调前,反复调用nextTick,Vue并不会反复添加到任务队列中,只会向任务队列添加一个任务,多次使用nextTick只会将回调添加到回调列表缓存起来,当任务触发时,会清空回调列表并依次执行所有回调 ,具体代码如下:
const callbacks = []
let pending = false
function flushCallbacks(){ //执行回调
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0 //清空回调队列
for(let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let microTimerFunc
const p = Promise.resolve()
microTimerFunc = () => { //注册微任务
p.then(flushCallbacks)
}
export function nextTick(cb,ctx){
callbacks.push(()=>{
if(cb){
cb.call(ctx)
}
})
if(!pending){
pending = true //将pending设置为true,保证任务在依次事件循环中不会重复添加
microTimerFunc()
}
}
由于微任务优先级太高,可能在某些场景下需要使用到宏任务,所以Vue提供了可以强制使用宏任务的方法withMacroTask。具体实现如下:
const callbacks = []
let pending = false
function flushCallbacks(){ //执行回调
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0 //清空回调队列
for(let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let microTimerFunc
//新增代码
let macroTimerFunc = function(){
...
}
let useMacroTask = false
const p = Promise.resolve()
microTimerFunc = () => { //注册微任务
p.then(flushCallbacks)
}
//新增代码
export function withMacroTask(fn){
return fn._withTask || fn._withTask = function()=>{
useMacroTask = true
const res = fn.apply(null,arguments)
useMacroTask = false
return res
}
}
export function nextTick(cb,ctx){
callbacks.push(()=>{
if(cb){
cb.call(ctx)
}
})
if(!pending){
pending = true //将pending设置为true,保证任务在依次事件循环中不会重复添加
//修改代码
if(useMacroTask){
macroTimerFunc()
}else{
microTimerFunc()
}
}
}
上面提供了一个withMacroTask方法强制使用宏任务,通过useMacroTask变量进行控制是否使用注册宏任务执行,withMacroTask实现很简单,先将useMacroTask变量设置为true,然后执行回调,回调执行之后再改回false。
宏任务是如何注册?
注册宏任务优先使用setImmediate,但是存在兼容性问题,只能在IE中使用,所以使用MessageChannel作为备选方案,若以上都不支持则最后会使用setTimeout。具体实现如下:
if(typeof setImmediate !== 'undefined' && isNative(setImmediate)){
macroTimerFunc = ()=>{
setImmediate(flushCallbacks)
}
} else if(
typeof MessageChannel !== 'undefined' &&
(isNative(MessageChannel) || MessageChannel.toString() === '[Object MessageChannelConstructor]')
){
const channel = new MessageChannel()
const port = channel.port2
channel.port1.onmessage = flushCallbacks
macroTimerFunc = ()=>{
port.postMessage(1)
}
} else {
macroTimerFunc = ()=>{
setTimout(flushCallbacks,0)
}
}
microTimerFunc的实现方法是通过Promise.then,但是并不是所有浏览器都支持Promise,当不支持的时候采取降级为宏任务方式
if(typeof Promise !== 'undefined' && isNative(Promise)){
const p = Promise.resolve()
microTimerFunc = ()=>{
p.then(flushCallbacks)
}
} else {
microTimerFunc = macroTimerFunc
}
若未提供回调且环境支持Promise情况下,nextTick会返回一个Promise,具体实现如下:
export function nextTick(cb, ctx) {
let _resolve
callbacks.push(()=>{
if(cb){
cb.call(ctx)
}else{
_resolve(ctx)
}
})
if(!pending){
pending = true
if(useMacroTask){
macroTimerFunc()
}else{
microTimerFunc()
}
}
if(typeof Promise !== 'undefined' && isNative(Promise)){
return new Promise(resolve=>{
_resolve = resolve
})
}
}
以上是nextTick运行原理的设计,完整代码如下:
const callbacks = []
let pending = false
function flushCallbacks(){ //执行回调
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0 //清空回调队列
for(let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let microTimerFunc
let macroTimerFunc
let useMacroTask = false
//注册宏任务
if(typeof setImmediate !== 'undefined' && isNative(setImmediate)){
macroTimerFunc = ()=>{
setImmediate(flushCallbacks)
}
} else if(
typeof MessageChannel !== 'undefined' &&
(isNative(MessageChannel) || MessageChannel.toString() === '[Object MessageChannelConstructor]')
){
const channel = new MessageChannel()
const port = channel.port2
channel.port1.onmessage = flushCallbacks
macroTimerFunc = ()=>{
port.postMessage(1)
}
} else {
macroTimerFunc = ()=>{
setTimout(flushCallbacks,0)
}
}
//微任务注册
if(typeof Promise !== 'undefined' && isNative(Promise)){
const p = Promise.resolve()
microTimerFunc = ()=>{
p.then(flushCallbacks)
}
} else {//降级处理
microTimerFunc = macroTimerFunc
}
export function withMacroTask(fn){
return fn._withTask || fn._withTask = function()=>{
useMacroTask = true
const res = fn.apply(null,arguments)
useMacroTask = false
return res
}
}
export function nextTick(cb,ctx){
let _resolve
callbacks.push(()=>{
if(cb){
cb.call(ctx)
}else{
_resolve(ctx)
}
})
if(!pending){
pending = true //将pending设置为true,保证任务在依次事件循环中不会重复添加
//修改代码
if(useMacroTask){
macroTimerFunc()
}else{
microTimerFunc()
}
}
if(typeof Promise !== 'undefined' && isNative(Promise)){
return new Promise(resolve=>{
_resolve = resolve
})
}
}
以上便是对nextTick的实现原理的全部介绍。
参考资料
Vue.js深入浅出
来源:https://juejin.im/post/6869220072074936334


猜你喜欢
- 前言:如何做到,控制多设备并行执行测试用例呢。思路篇我们去想下,我们可以获取参数的信息,和设备的信息,那么我们也可以针对每台设备开启不一样的
- 一、python3的安装建议安装python3,python2在未来将不再维护。python官方下载地址https://www.python
- 功能:间隔5毫秒,快速点击屏幕某区域,循环45000000次from ctypes import *import timetime.slee
- 简单的说,GUI编程就是给程序加上图形化界面.python的脚本开发简单,有时候只需几行代码就能实现丰富的功能,而且python本身是跨平台
- 本文向大家介绍一个javascript实现的动画。点击开始按钮div会往右移动,点击停止后,div停止移动,再点击则继续移动。请看下面代码:
- 可以去官网下载,我百度网盘也有都一样链接: https://pan.baidu.com/s/1fhEJu_9Zas364bvlEimRLA
- 网站上的Banner条,是网站用来作为盈利或者是发布一些重要的信息的工具。但是它又不能作为网页的主要内容,因为它的主要目的是吸引人的注意力,
- 本文主要介绍了pandas导出数据到文件的四种方式,分享给大家,主要也是给自己留个笔记,具体如下:import pandas as pdim
- 思路:先随机排序然后再分组就好了。1、创建表:CREATE TABLE `xdx_test` ( `id` int(11) NOT NULL
- XML 是严格又自由的标记语言。我们都习惯于它的自由特性,自己想怎么定义都行,设计上非常自由,从不会因为它的标记特性约束到设计灵感的发挥。对
- 当单台MYSQL服务器无法满足当前网站流量时的优化方案。需要搭建mysql集群技术。一、功能:当向主服务器插入|修改|删除数据时,数据会自动
- 题:取表table中100条-200条之间数据 方法1:临时表 select top 200 * into #aa from table o
- python中向上取整可以用ceil函数,ceil函数是在math模块下的一个函数。向上取整需要用到 math 模块中的 ceil() 方法
- 要实现标题的功能,总共分四步:1.创建html错误页2.配置settings3.编写视图4.配置url我的开发环境:django1.10.3
- 对于一个给定的字符串,逆序输出,这个任务对于python来说是一种很简单的操作,毕竟强大的列表和字符串处理的一些列函数足以应付这些问题 了,
- 本文实例讲述了python通过colorama模块在控制台输出彩色文字的方法。分享给大家供大家参考。具体分析如下:colorama是一个py
- 本文实例为大家分享了微信小程序跳一跳自动运行脚本,供大家参考,具体内容如下1、压缩包带了adb等必须工具,配置一下环境变量即可2、Pytho
- 以下内容为转帖: 代码 <script type="text/javascript"> function g
- 本文实例讲述了python根据路径导入模块的方法,分享给大家供大家参考。具体方法如下:常规做法如下:import sys sys.path.
- WebService客户端接口调用及身份验证问题最近由于业务需求,需要实现python Webservice的服务以及接口调用。服务端代码可