1

nextTick的源码分析

var callbacks = [];
var pending = false;
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();
    }
    if (!cb && typeof Promise !== 'undefined') {
        return new Promise(function(resolve) {
            _resolve = resolve;
        })
    }
}

可以看到在 nextTick 函数中把通过参数 cb 传入的函数,做一下包装然后 push 到 callbacks 数组中。
然后用变量 pending 来保证执行一个事件循环中只执行一次 timerFunc()。
最后执行 if (!cb && typeof Promise !== 'undefined'),判断参数 cb 不存在且浏览器支持 Promise,则返回一个 Promise 类实例化对象。例如 nextTick().then(() => {}),当 _resolve 函数执行,就会执行 then 的逻辑中。
来看一下 timerFunc 函数的定义,先只看用 Promise 创建一个异步执行的 timerFunc 函数

var timerFunc;
if (typeof Promise !== 'undefined' && isNative(Promise)) {
    var p = Promise.resolve();
    timerFunc = function() {
        p.then(flushCallbacks);
        if (isIOS) {
            setTimeout(noop);
        }
    };
}

在其中发现 timerFunc 函数就是用各种异步执行的方法调用 flushCallbacks 函数。
来看一下flushCallbacks 函数

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

执行 pending = false 使下个事件循环中能nextTick 函数中调用 timerFunc 函数。
执行 var copies = callbacks.slice(0);callbacks.length = 0; 把要异步执行的函数集合 callbacks 克隆到常量 copies,然后把 callbacks 清空。
然后遍历 copies 执行每一项函数。回到 nextTick 中是把通过参数 cb 传入的函数包装后 push 到 callbacks 集合中。来看一下怎么包装的。

function() {
    if (cb) {
        try {
            cb.call(ctx);
        } catch (e) {
            handleError(e, ctx, 'nextTick');
        }
    } else if (_resolve) {
        _resolve(ctx);
    }
}

逻辑很简单。若参数 cb 有值。在 try 语句中执行 cb.call(ctx) ,参数 ctx 是传入函数的参数。 如果执行失败执行 handleError(e, ctx, 'nextTick')。
若参数 cb 没有值。执行 _resolve(ctx),因为在nextTick 函数中如何参数 cb 没有值,会返回一个 Promise 类实例化对象,那么执行 _resolve(ctx),就会执行 then 的逻辑中。

总结

到这里 nextTice 函数的主线逻辑就很清楚了。定义一个变量 callbacks,把通过参数 cb 传入的函数用一个函数包装一下,在这个中会执行传入的函数,及处理执行失败和参数 cb 不存在的场景,然后 添加到 callbacks。调用 timerFunc 函数,在其中遍历 callbacks 执行每个函数,因为 timerFunc 是一个异步执行的函数,且定义一个变量 pending来保证一个事件循环中只调用一次 timerFunc 函数。这样就实现了 nextTice 函数异步执行传入的函数的作用了。

update生命周期

beforeUpdate:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。 你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
updated:无论是组件本身的数据变更,还是从父组件接收到的 props 或者从vuex里面拿到的数据有变更,都会触发虚拟 DOM 重新渲染(页面dom已经渲染完成)和打补丁。并在之后调用 updated。

父子组件Update生命周期和nextTick的执行顺序

父的beforeUpdate->子的beforeUpdate->子的updated->父updated->nextTick回调

//父组件

<template>
  <div>  
    <space>{{msg}}</space>
    <button @click="add">增加</button>
  </div>
  
</template>
<script>
import space from "./space"
export default {
  components:{
    space
  },
  data(){
    return {
      msg:1
    }
  },
  beforeUpdate(){
    console.log("父beforeUpdate")
  },
  updated(){
    console.log("父updated")
  },
  methods:{
    add(){
      this.msg++;
      this.$nextTick(()=>{
        console.log("next")
      })
    }
  }
  
}
</script>
//子组件
<template>
  <div>
    <slot></slot>
  </div>
</template>
<script>
export default {
  beforeUpdate(){
    console.log("子beforeUpdate")
  },
  updated(){
    console.log("子updated")
  },
}
</script>
//打印结果
父beforeUpdate
子beforeUpdate
子updated
父updated
next

这也很好理解,beforeUpdate,updated虽然也是在微任务中执行,但是它所在的微任务先创建,所以先执行。
如果改成下面这样,结果就不一样了

add(){
  this.$nextTick(()=>{
    console.log("next")
  })//next的微任务先创建,所以先执行
  this.msg++;
}
//打印结果
next
父beforeUpdate
子beforeUpdate
子updated
父updated

star
64 声望3 粉丝

小菜鸟成长记录