我眼中的vue nextTick原理

基本使用

this.$nextTick()将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。

<template>
  <section>
    <h1 ref="hello">{{ value }}</h1>
    <el-button type="danger" @click="get">点击</el-button>
  </section>
</template>
<script>
  export default {
    data() {
      return {
        value: 'Hello World ~'
      };
    },
    methods: {
      get() {
        this.value = '你好啊';
        console.log(this.$refs['hello'].innerText); // Hello World ~
        this.$nextTick(() => {
          console.log(this.$refs['hello'].innerText); // 你好啊'
        });
      }
    },
  }
</script>

在进行讲解之前我们先掌握js的事件循环,请看我的另一篇文章前端八股文之事件循环

我们先看一个简单的例子,查看一下事件循环的执行优先级

<body>
   <div id="app">test</div>
</body>
<script>
	var dom = document.querySelector('#app')
	dom.innerText = 'render'
	alert(`sync-${dom.innerText}`)
	
	new Promise((resolve) => {
	  alert(`promise-${dom.innerText}`)
	  resolve()
	}).then(() => {
	  alert(`then-${dom.innerText}`)
	})
	
	setTimeout(() => {
	  alert(`setTimeout-${dom.innerText}`)
	}, 0);
</script>

因为alert会阻断当前tick的执行,所以我们能很好的看到执行顺序;
1、script主体同步代码,弹出sync-render
2、script主体同步代码,弹出promise-render
3、微任务, 弹出then-render,此时页面仍为 <div id=“app”>test</div>
4、render UI, 页面渲染 <div id=“app”>render</div>
5、执行下一次tick,宏任务,弹出setTimeout-render

误区一
既然nexttick可以在页面渲染后再执行代码,那nexttick必然在渲染后进行的,可很多资料又说明渲染过程是放在一次tick后用进行的,甚至有些博主的说法自相矛盾
误区二
微任务优先于宏任务执行,其实不然,在一次tick内宏任务的优先级是高于微任务,宏任务包括了主体script的代码:例如上面例子的这两行代码
var dom = document.querySelector(’#app’);dom.innerText = ‘render’;

——————终极结论——————
dom结构的数据改变后,改变后的节点数据我们是可以通过代码拿到的,但渲染过程在整个tick结束后进行的,然后进行下一次tick,从宏任务开始

从vue双向绑定源码过程中看,改变data数据 – 触发watcher – 改变dom结构数据 – 重新渲染页面;
经过上面的解释我们可以这样理解双向绑定,改变data数据 – 触发watcher – 改变dom结构数据 – Vue.$nextTick的回调 – 重新渲染页面

那现在只剩一个问题:为什么Vue.$nextTick 会在重新渲染页面之前执行;由上面代码的实验,我们推测$nextTick必然一种可能是微任务了,最后附源码

是的,nextTick就是把回掉方法全部放在微任务中了,不过vue做了兼容处理,前前后后改了兼容的几种方法,由Promise做首选微任务,如果不支持选择微任务MutationObserver,不得已会选择setImmediate(IE、Node.js 环境) > MessageChannel > setTimeout;
注意如果最后由宏任务进行回调的执行,那双向绑定则变成改变 data数据 – 触发watcher – 改变dom结构数据 – 重新渲染页面 – Vue.$nextTick的回调因为宏任务在下一次tick执行,渲染行为发生在本次tick的结尾

nextTick主体源码,有删减
let isUsingMicroTask = false
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 timerFunc
if (typeof Promise !== 'undefined') {
  const p = Promise.resolve()
  timerFunc = () => { p.then(flushCallbacks) }
  isUsingMicroTask = true
} else if (typeof MutationObserver !== 'undefined' && (
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
//注意vue不是用MutationObserver检测dom是否改变,主要是利用这是微任务
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
 
  // timerFunc函数执行时会导致文本节点textNode的数据发生改变,因为MutationObserver对象在监听文本节点,
  //所以进而也就会触发flushCallbacks回调函数
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值