js及vue组件执行顺序

536 阅读3分钟

在最近的标签项目开发中,遇到了标签构建的数据回显,有时会出现不能及时返回信息的问题,其中就涉及了js执行顺序,vue组件执行顺序问题。

js运行环境

  • 简单提一嘴前言,所谓"js是单线程的"只是指js的主运行线程只有一个,而不是整个运行环境都是单线程。
  • 提到js的运行环境,就要请出大名鼎鼎的V8引擎(Chrome浏览器和Node.js用的就是它,当然也有其他引擎苹果的JavaScriptCore,Mozilla Firefox的SpiderMonkey等),是它让js在浏览器上编译运行,而且他是多线程多进程的。

异步与同步

如果js同步的逻辑代码执行中,需要向后端发出网络请求或者一些定时延迟执行的方法,那么必然会经历网络延迟或执行延迟,为了避免这种阻塞情况发生,就会有异步执行,那么js如何知道谁先谁后执行呢?

事件循环机制(Event Loop)

其实就是js管理事件执行的一个流程准则,我们这次重点来说说浏览器的Event Loop。 异步与同步线程为了方便沟通信息,有一个共有的空间叫做事件队列,当点击,悬停等事件被触发时,会将触发的回调事件放进事件队列,主线程每次干完手上的活儿就来看看这个队列有没有新活儿,有的话就取出来执行。

  • 如下图

image.png

宏任务和微任务

  • 而事件队列里面会解决异步与同步实行顺序的问题,可以分两类:宏任务和微任务。
  • 微任务拥有更高的优先级,当事件循环遍历队列时,先检查微任务队列,如果里面有任务,就全部拿来执行,执行完之后再执行一个宏任务。执行每个宏任务之前都要检查下微任务队列是否有任务,如果有,优先执行微任务队列。

image.png

常见宏任务
  1. setTimeout/setInterval
  2. setImmediate(Node.js)
  3. postMessage
常见微任务
  1. Promise
  2. process.nextTick(Node.js)
  • 总体流程图如下

image.png

  • 当然我们了解原理,在实际的开发中是不够的,在vue的框架中,除了js的实行顺序,同样也需要考虑到组件及API中的生命周期

那么vue中computed、watch及其生命周期谁先执行,父子组件传值更改时,是什么顺序呢?

父组件Home

<template>
  <div class="home">
    <Childcom :secondNum="secondNum"/>
    <button @click="changParent">改变父组件</button>
  </div>
</template>

<script>
// @ is an alias to /src
import Childcom from '../components/Childcom.vue'

export default {
  name: 'Home',
  components: {
    Childcom
  },
  data() {
    return {
      secondNum: 10,
      sss: 0
    }
  },
  created() {
    this.logcreated()
  },
  beforeMount() {
    this.logbeforeMount()
  },
  mounted() {
    this.logmounted()
  },
  beforeUpdate() {
    console.log("beforeUpdate", this.firstNum, this.secondNum);
  },
  updated() {
    console.log("updated", this.firstNum, this.secondNum);
  },
  watch: {
    secondNum(val) {
      console.log('父watch', val);
      this.sss = 10
    },
  },
  methods: {
    changParent() {
      this.secondNum = 20
    },
    logmounted() {
      console.log('父mounted', this.secondNum);
    },
    logcreated() {
      console.log('父created', this.secondNum);
    },
    logbeforeMount() {
      console.log('父logbeforeMount', this.secondNum);
    },
    logbeforeUpdate() {
      console.log('父beforeUpdate', this.secondNum);
    },
    logupdated() {
      console.log('父updated', this.secondNum);
    }
  }
}
</script>

子组件Childcom

<template>
  <div>
    <h4>父组件传值secondNum:{{ secondNum }}</h4>
    <h4>自己的值firstNum:{{ firstNum }}</h4>
    <h4>computed之thirdNum:{{ thirdNum }}</h4>
    <button @click="btnClick">改变firstNum</button>
  </div>
</template>

<script>
export default {
  props: ["secondNum"], //父组件传过来的值,默认为0
  data() {
    return {
      firstNum: 0,
    };
  },
  computed: {
    thirdNum() {
      console.log("computed", this.firstNum, this.secondNum);
      return this.firstNum + this.secondNum + "元";
    },
  },
  watch: {
    firstNum() {
      console.log("watchfirstNum", this.firstNum, this.secondNum);
    },
    thirdNum: {
      handler () {
        console.log("watchthirdNum", this.firstNum, this.secondNum);
      }
    },
  },
  created() {
    console.log("created", this.firstNum, this.secondNum);
  },
  beforeMount() {
    console.log("beforeMount", this.firstNum, this.secondNum);
  },
  mounted() {
    console.log("mounted", this.firstNum, this.secondNum);
  },
  beforeUpdate() {
    console.log("beforeUpdate", this.firstNum, this.secondNum);
  },
  updated() {
    console.log("updated", this.firstNum, this.secondNum);
  },
  methods: {
    btnClick() {
      this.firstNum = "firstNum" + Math.random() * 999;
      console.log("methods", this.firstNum, this.secondNum);
    },
  },
};
</script>

<style>
</style>

我们执行一下看看效果

1. 当初次加载时

image.png

执行顺序为:父组件created --> 父组件beforeMounted --> 子组件computed --> 子组件created --> 子组件beforeMounted --> 子组件mounted --> 父组件mounted

2. 当父组件值变动时

image.png

执行顺序为:父组件watch --> 父组件beforeUpdate --> 子组件computed --> 子组件watch --> 子组件beforeUpdate --> 子组件updated --> 父组件updated

3. 当子组件值变动时

image.png

执行顺序为:子组件watch --> 子组件computed --> thirdNum子组件watch --> 子组件beforeUpdate --> 子组件updated