在最近的标签项目开发中,遇到了标签构建的数据回显,有时会出现不能及时返回信息的问题,其中就涉及了js执行顺序,vue组件执行顺序问题。
js运行环境
- 简单提一嘴前言,所谓"js是单线程的"只是指js的主运行线程只有一个,而不是整个运行环境都是单线程。
- 提到js的运行环境,就要请出大名鼎鼎的V8引擎(Chrome浏览器和Node.js用的就是它,当然也有其他引擎苹果的JavaScriptCore,Mozilla Firefox的SpiderMonkey等),是它让js在浏览器上编译运行,而且他是多线程多进程的。
异步与同步
如果js同步的逻辑代码执行中,需要向后端发出网络请求或者一些定时延迟执行的方法,那么必然会经历网络延迟或执行延迟,为了避免这种阻塞情况发生,就会有异步执行,那么js如何知道谁先谁后执行呢?
事件循环机制(Event Loop)
其实就是js管理事件执行的一个流程准则,我们这次重点来说说浏览器的Event Loop。 异步与同步线程为了方便沟通信息,有一个共有的空间叫做事件队列,当点击,悬停等事件被触发时,会将触发的回调事件放进事件队列,主线程每次干完手上的活儿就来看看这个队列有没有新活儿,有的话就取出来执行。
- 如下图
宏任务和微任务
- 而事件队列里面会解决异步与同步实行顺序的问题,可以分两类:宏任务和微任务。
- 微任务拥有更高的优先级,当事件循环遍历队列时,先检查微任务队列,如果里面有任务,就全部拿来执行,执行完之后再执行一个宏任务。执行每个宏任务之前都要检查下微任务队列是否有任务,如果有,优先执行微任务队列。
常见宏任务
- setTimeout/setInterval
- setImmediate(Node.js)
- postMessage
常见微任务
- Promise
- process.nextTick(Node.js)
- 总体流程图如下
-
当然我们了解原理,在实际的开发中是不够的,在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. 当初次加载时
执行顺序为:父组件created --> 父组件beforeMounted --> 子组件computed --> 子组件created --> 子组件beforeMounted --> 子组件mounted --> 父组件mounted
2. 当父组件值变动时
执行顺序为:父组件watch --> 父组件beforeUpdate --> 子组件computed --> 子组件watch --> 子组件beforeUpdate --> 子组件updated --> 父组件updated
3. 当子组件值变动时
执行顺序为:子组件watch --> 子组件computed --> thirdNum子组件watch --> 子组件beforeUpdate --> 子组件updated