Vue源码学习之nextTick

前言

最近在学习 ustbhuangyi 老师的 Vue源码全方位深入解析课程,以下内容算是课堂笔记了吧。

JS 的运行机制

为方便理解 nextTick,需要先理解 JS 的运行机制。

参考链接:

https://ustbhuangyi.github.io/vue-analysis/v2/reactive/next-tick.html#js-%E8%BF%90%E8%A1%8C%E6%9C%BA%E5%88%B6

JS 执行是单线程的,它是基于事件循环的。事件循环大致分为以下几个步骤:

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步。

我的理解

简单测试

<script>
  console.log(1)
  console.log(2)
  console.log(3)
  console.log(4)
  test()
  console.log(5)
  console.log(6)
  console.log(7)
  console.log(8)

  function test(){
    console.log("a")
    setTimeout(function(){console.log("b")},1000)
  }
</script>

 打印顺序

源码实现

nextTick源码实现的本质,是把要实现的函数收集起来,收集到 callbacks 数组中,然后在下一个 tick 中遍历数组,执行回调。

src/core/util/next-tick.js

const callbacks = []

......

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

})

}

}

测试

<template>

<div class="hello">

<h1 ref="msg">{ { msg }}</h1>

<div>

<button @click="changeText1">change1</button>

<button @click="changeText2">change2</button>

</div>

</div>

</template>

<script>

export default {

name: 'NextTick',

data () {

return {

msg: 'hello world'

}

},

methods: {

changeText1 () {

this.msg = 'hello vue'

// 同步输出dom结果

console.log('sync: msg = ' + this.$refs.msg.innerText)// 输出hello world

// 异步输出dom结果

// 以下两种方式写都可以

// this.$nextTick(()=>{

// console.log("nextTick: msg = "+this.$refs.msg.innerText)

// })

this.$nextTick().then(() => {

console.log('nextTick: msg = ' + this.$refs.msg.innerText)// 输出hello vue

})

},

changeText2 () {

// 执行顺序和书写顺序有关

this.$nextTick(() => {

console.log('nextTick: msg = ' + this.$refs.msg.innerText)// 输出hello world

})

this.msg = 'hello vue'

// 同步输出dom结果

console.log('sync: msg = ' + this.$refs.msg.innerText)// 输出hello world

// 异步输出dom结果

// 结论:数据的变化到dom的重新渲染是一个异步的过程

this.$nextTick().then(() => {

console.log('nextTick: msg = ' + this.$refs.msg.innerText)// 输出hello vue

})

}

}

}

</script>

总结

数据的变化到 DOM 的重新渲染是一个异步过程,发生在下一个 tick。

这就是我们平时在开发的过程中,比如从服务端接口去获取数据的时候,数据做了修改,如果我们的某些方法去依赖了数据修改后的 DOM 变化,我们就必须在 nextTick 后执行。

比如下面的伪代码:

getData(res).then(()=>{

  this.xxx = res.data

  this.$nextTick(() => {

    // 这里我们可以获取变化后的 DOM

  })

})

数据改变后触发渲染 watcher 的 update ,但是watchers 的flush 是在nextTick 后,所以重新渲染是异步的。

猜你喜欢

转载自blog.csdn.net/Irene1991/article/details/114097688