探索Vue.js底层源码——nextTick实现及原理

引言


Vue 中 nextTick 的实现主要是基于微任务队列,它是通过原生的 Promise.then 或 MutationObserver 实现,需要注意的是 nextTick 更多地会选择 Promise.then 进行触发,因为在 IOS(>= 9.3.3)的时候,MutationObserver 在触发几次后会失效。

我想大家对 Promise.then 都有认知,最常见的一段代码:

	setTimeout(() => console.log(4)) // 宏任务
	new Promise(resolve => {
		resolve()
		console.log(1) // 这一部分的代码是同步的
	}).then(() => {
		console.log(3) // 这一部分代码是异步的即微任务
	})
	console.log(2)
	// 最终输出 1 2 3 4

但是 MutationObserver 可能有有点陌生,那么我们简单地认识一下 MutationObserver

MutationObserver


MutationObserver 是一个构造器,它接收一个 callback 参数,是用于处理节点变化的回调函数,回调中返回两个参数:mutations:节点变化记录列表、observer:构造 MutationObserver 对象

var observer = new MutationObserver(function(mutations, observer) {})

方法:

  1. observer 用于设置观察目标,它接收两个参数,target:观察目标,options:通过对对象成员来设置观察选项
  2. disconnect 阻止观察者观察任何改变
  3. takeRecords 清空记录队列并返回里面的内容
    需要注意的事 observer 方法中 options 具有几个参数:
  4. childList 设置为 true,则表示观察添加或删除目标节点的变化
  5. attributes 设置为 true,则表示观察目标属性的改变
  6. characterData 设置为 true,则表示观察目标数据的改变
  7. subtree 设置为 true,则表示目标以及目标的后代的改变都会观察
  8. attributeOldValue 默认为 true,表示记录改变当前目标属性值
  9. characterDataOldValue
  10. attributeFilter

JS 运行机制


JS 执行是单线程的,它是基于事件循环的。事件循环步骤:

  1. 所有同步任务都在主线程上执行,即执行栈 execution context stack
  2. 主线程之外,还存在一个任务队列(也有人称之为消息队列)。只要异步任务有了运行结果,就在任务队列中放置一个事件
  3. 一旦执行栈中的所有同步任务执行完毕,系统,就会读取任务队列,看看里面哪些事件。哪些对应的异步任务,则结束等待状态,进入执行栈中,开始执行。
  4. 一直重复上述三个步骤

JS中的任务


JS 中规定任务为两类,一类是宏任务 macro task,一类是微任务 micro task,并且每个宏任务结束后,都要清空所有微任务。
宏任务和微任务之间的执行顺序:

// 遍历宏任务队列
for (macroTask of macroTaskQueue) {
	// 处理当前的宏任务
	hadleMacroTask()

	// 处理所有的微任务x
	for (microTask of microTaskQueue) {
		handleMicroTask
	}
}

在浏览器环境中,常见的 macro task 有 setTimeout、MessageChannel、postMessage、setImmediate;常见的 micro task 有 MutationObserver 和 Promise.then

nextTick 方法


nextTick 方法定义在 src/core/util/next-titck.js

export function nextTick (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()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

上面代码中很值得学习的就是调用 cb 回调函数的时候,用了 try catch 包裹,防止出错崩掉。

总结


nextTick 的实现在 Vue 中采用了多种方式,具体是用哪种方式实现,取决于自己开发环境,大多数情况是用 Promise.then 实现,还有就是 nextTick 并不是一个一个单独执行的。首先,它会把代码中所有的 nextTick 收集起来,放到数组 callbacks(相当于队列) 中,然后通过对应环境下的微任务方法执行 flushCallbacks 方法,即遍历 calllhacks 数组中的每一个方法,然后清空 callbacks 数组。

发布了140 篇原创文章 · 获赞 16 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_42049445/article/details/103451909