一、vue2中父子组件嵌套时的生命周期执行顺序是怎样的
在Vue2中,组件的生命周期钩子函数包括:
- beforeCreate:在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用
- created:实例已经创建完成之后被调用
- beforeMount:在挂载开始之前被调用:相关的 render 函数首次被调用
- mounted:el 被新创建的 vm.$el 替换,并挂载到实例上后调用该钩子函数
- beforeUpdate:数据更新时调用,在数据更新之前和重新渲染之前被调用
- updated:由于数据改变导致的虚拟 DOM 重新渲染和打补丁时调用
- beforeDestroy:在实例被销毁之前调用。此时实例仍然完全可用
- destroyed:Vue 实例被销毁后调用
生命周期函数执行的顺序如下:
- beforeCreate
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- beforeDestroy
- destroyed
举例说明:
javascript">new Vue({
el: '#app',
data: {
message: 'Hello, Vue!'
},
beforeCreate: function() {
console.log('beforeCreate');
},
created: function() {
console.log('created');
},
beforeMount: function() {
console.log('beforeMount');
},
mounted: function() {
console.log('mounted');
},
beforeUpdate: function() {
console.log('beforeUpdate');
},
updated: function() {
console.log('updated');
},
beforeDestroy: function() {
console.log('beforeDestroy');
},
destroyed: function() {
console.log('destroyed');
}
});
以上代码创建了一个Vue实例,当页面加载时会依次输出如下内容:
beforeCreate
created
beforeMount
mounted
1. 挂载阶段
父子组件的生命周期执行顺序是先执行父组件的生命周期钩子函数,然后再执行子组件的生命周期钩子函数
只看created和mounted钩子,执行顺序是
父组件created
子组件created
子组件mounted
父组件mounted
前四个生命周期的执行顺序是:
- 父组件beforeCreate
- 父组件created
- 父组件beforeMount
- 子组件beforeCreate
- 子组件created
- 子组件beforeMount
- 子组件mounted
- 父组件mounted
举例说明:
//父组件
<template>
<div>
<child-***ponent></child-***ponent>
</div>
</template>
<script>
import Child***ponent from './Child***ponent';
export default {
***ponents: {
Child***ponent
},
beforeCreate() {
console.log('父组件beforeCreate');
},
created() {
console.log('父组件created');
},
beforeMount() {
console.log('父组件beforeMount');
},
mounted() {
console.log('父组件mounted');
}
}
</script>
//子组件
<template>
<div>
Child ***ponent
</div>
</template>
<script>
export default {
beforeCreate() {
console.log('子组件beforeCreate');
},
created() {
console.log('子组件created');
},
beforeMount() {
console.log('子组件beforeMount');
},
mounted() {
console.log('子组件mounted');
}
}
</script>
当上述代码中的父组件和子组件被渲染到页面时,控制台输出的日志顺序将是:
父组件beforeCreate
父组件created
父组件beforeMount
子组件beforeCreate
子组件created
子组件beforeMount
子组件mounted
父组件mounted
2. 更新阶段
在Vue2中,父组件和子组件的生命周期钩子在更新阶段执行的顺序是:
- 父组件beforeUpdate钩子
- 子组件beforeUpdate钩子
- 子组件updated钩子
- 父组件updated钩子
举例说明:
假设有一个父组件Parent和一个子组件Child,在Parent组件中包含了Child组件。当父组件的数据发生变化时,更新触发事件会导致更新阶段的生命周期钩子执行顺序如下:
<template>
<div>
<Child :msg="message"></Child>
<button @click="updateMessage">更新消息</button>
</div>
</template>
<script>
import Child from './Child.vue';
export default {
***ponents: {
Child
},
data() {
return {
message: 'Hello'
};
},
methods: {
updateMessage() {
this.message = 'World';
}
},
beforeUpdate() {
console.log('父组件beforeUpdate');
},
updated() {
console.log('父组件updated');
}
};
</script>
Child组件的代码如下:
<template>
<div>
<p>{{ msg }}</p>
</div>
</template>
<script>
export default {
props: ['msg'],
beforeUpdate() {
console.log('子组件beforeUpdate');
},
updated() {
console.log('子组件updated');
}
};
</script>
当点击按钮更新消息时,控制台输出的结果为:
父组件beforeUpdate
子组件beforeUpdate
子组件updated
父组件updated
可以看到,在更新阶段,父组件的beforeUpdate钩子会在子组件的beforeUpdate钩子之前执行,而子组件的updated钩子会在父组件的updated钩子之前执行。这是因为Vue在更新过程中是从父组件开始,然后递归更新子组件。
3. 销毁阶段
在Vue2中,父子组件的生命周期钩子函数在销毁前和销毁阶段执行顺序如下:
- Parent beforeDestroy
- Child beforeDestroy
- Child destroyed
- Parent destroyed
例如,假设有一个父组件Parent和一个子组件Child,它们的生命周期钩子函数如下:
Parent组件:
export default {
// 父组件的生命周期钩子函数
beforeDestroy() {
console.log('Parent beforeDestroy');
},
destroyed() {
console.log('Parent destroyed');
}
}
Child组件:
export default {
// 子组件的生命周期钩子函数
beforeDestroy() {
console.log('Child beforeDestroy');
},
destroyed() {
console.log('Child destroyed');
}
}
当父组件Parent销毁时,执行顺序如下:
- Parent beforeDestroy
- Child beforeDestroy
- Child destroyed
- Parent destroyed
可以看到,在父组件销毁前,会先执行子组件的beforeDestroy钩子函数,然后再执行子组件的destroyed钩子函数。最后再执行父组件的destroyed钩子函数。
二、vue组件通讯方式有哪些
1. 父子组件通讯方式
在Vue2中,父子组件之间的通讯方式有以下几种:
(1). Props 和 Events:父组件通过props向子组件传递数据,子组件通过事件向父组件传递数据。
示例:
父组件:
<template>
<child-***ponent :message="parentMessage" @notify="handleNotify"></child-***ponent>
</template>
<script>
export default {
data() {
return {
parentMessage: 'Hello from parent'
}
},
methods: {
handleNotify(message) {
console.log(message)
}
}
}
</script>
子组件:
<template>
<div @click="sendMessage">{{message}}</div>
</template>
<script>
export default {
props: ['message'],
methods: {
sendMessage() {
this.$emit('notify', 'Message from child')
}
}
}
</script>
(2). $parent
和 $children
:通过访问父组件的$children
属性或子组件的$parent
属性来实现通讯。
示例:
父组件:
<template>
<child-***ponent></child-***ponent>
</template>
<script>
export default {
methods: {
sendMessage() {
this.$children[0].$emit('notify', 'Message from parent')
}
}
}
</script>
子组件:
<template>
<div @click="sendMessage">{{message}}</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello from child'
}
},
methods: {
sendMessage() {
this.$parent.$emit('notify', 'Message from child')
}
}
}
</script>
(3). $refs
:使用ref属性获取子组件的实例,从而实现父组件和子组件之间的通讯。
示例:
父组件:
<template>
<child-***ponent ref="child"></child-***ponent>
</template>
<script>
export default {
methods: {
sendMessage() {
this.$refs.child.sendMessage('Message from parent')
}
}
}
</script>
子组件:
<template>
<div @click="sendMessage">{{message}}</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello from child'
}
},
methods: {
sendMessage(message) {
console.log(message)
}
}
}
</script>
2. 兄弟组件通讯方式
事件总线event bus:利用Vue实例作为事件总线,通过$on
和$emit
方法进行事件的监听和触发,实现兄弟组件之间的通讯。示例代码如下所示:
// eventBus.js
import Vue from 'vue';
export const EventBus = new Vue();
// ***ponentA.vue
<script>
import { EventBus } from './eventBus';
export default {
methods: {
sendMessage() {
EventBus.$emit('message', 'Hello from ***ponent A');
}
}
};
</script>
// ***ponentB.vue
<script>
import { EventBus } from './eventBus';
export default {
data() {
return {
message: ''
};
},
mounted() {
EventBus.$on('message', (message) => {
this.message = message;
});
}
};
</script>
这里,***ponentA
发送一个名为message
的事件,并传递一个消息。***ponentB
监听这个事件,然后更新自身的message
属性。
3. 祖孙组件通讯
依赖注入provide / inject
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。
可以使用provide和inject选项来实现依赖注入。provide选项用于向子组件提供依赖项,inject选项用于在子组件中注入依赖项。
例如,以下是一个简单的示例,在父组件中使用provide提供一个变量,然后在子组件中使用inject注入该变量:
// Parent***ponent.vue
export default {
provide: {
message: 'Hello from parent'
}
}
// Child***ponent.vue
export default {
inject: ['message'],
created() {
console.log(this.message); // 输出 'Hello from parent'
}
}
在这个示例中,父组件使用provide选项提供了一个名为message的变量,而子组件通过inject选项注入了这个变量,并在created钩子函数中打印了该变量的值。
需要注意的是,provide和inject选项不会对子组件进行响应式处理,也不支持嵌套对象的属性访问。因此,在使用依赖注入时,建议将数据存在在原始数据中,而不是在嵌套对象中。
4. vuex
Vuex是一个专门为Vue.js应用程序开发的状态管理模式,它集中存储管理应用的所有组件的状态,并以功能强大和易于维护的方式进行统一管理和更新。
vuex的五大核心概念
-
State:状态存储在Vuex的中央存储中,用于存储组件之间共享的数据。
-
Getter:用于从state中获取数据,类似于计算属性,可以对数据进行进一步的处理。
-
Mutation:用于修改state中的数据,必须是同步函数。
-
Action:用于处理异步操作,可以包含任意异步操作,并且可以通过***mit来调用mutation来修改state中的数据。
-
Module:用于将store分割成模块,每个模块拥有自己的state、getters、mutations、actions。Vuex允许将store分割成模块,更好地管理代码结构。
一些Vuex的主要功能和用途包括:
-
状态存储:Vuex可以让你在一个集中的地方管理所有组件的状态。通过store对象,可以存储所有需要共享和管理的状态,确保应用中的每个组件都可以访问和更新这些状态。
-
状态共享:Vuex使得在不同组件之间共享状态变得更加容易。通过store对象中存储的状态,可以在需要的时候在任何组件中获取和修改状态,而不需要通过props或事件来传递状态。
-
状态响应:Vuex提供了一种响应式的状态管理机制,它能够追踪状态的变化并实时更新应用的视图。当状态发生变化时,依赖于该状态的组件会自动重新渲染,从而保持状态和视图的同步。
状态共享,举例
要使用Vuex实现在组件之间通信,首先需要在Vue应用中安装并配置Vuex。然后在store中定义state、mutations、actions和getters等属性,以及要共享的数据。
举例说明如下:
假设有两个组件***ponentA和***ponentB,它们之间需要共享一个变量count。首先在store中定义一个state并初始化count的值:
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
}
},
actions: {
increment({ ***mit }) {
***mit('increment');
}
},
getters: {
getCount(state) {
return state.count;
}
});
export default store;
然后在***ponentA和***ponentB中分别使用mapState和mapActions来获得state和actions,并进行相应的操作:
// ***ponentA.vue
<template>
<div>
<p>***ponentA Count: {{ count }}</p>
<button @click="increment">Increment Count</button>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
export default {
***puted: {
...mapState(['count'])
},
methods: {
...mapActions(['increment'])
}
}
</script>
// ***ponentB.vue
<template>
<div>
<p>***ponentB Count: {{ count }}</p>
<button @click="increment">Increment Count</button>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
export default {
***puted: {
...mapState(['count'])
},
methods: {
...mapActions(['increment'])
}
}
</script>
这样就实现了在***ponentA和***ponentB之间共享count变量,并通过点击按钮来增加count的功能。当一个组件中修改了count值,另一个组件中的count值也会同步更新。
三、自定义v-model
在Vue2中,自定义组件的v-model用于实现父子组件之间的双向绑定。通过使用v-model,可以简化在父组件中传递数据和监听子组件事件的操作。
在自定义组件中,可以使用配置项model来自定义v-model的属性名称和事件名称。下面是一个简单的示例:
// My***ponent.vue
<template>
<input :value="value" @input="$emit('update:value', $event.target.value)">
</template>
<script>
export default {
props: ['value'],
model: {
prop: 'value',
event: 'update:value'
}
}
</script>
在上面的示例中,配置项model中prop指定了v-model绑定的属性名称为value,event指定了v-model绑定的事件名称为update:value。这样在使用My***ponent组件时,可以直接使用v-model来实现双向绑定:
// Parent***ponent.vue
<template>
<My***ponent v-model="message"></My***ponent>
</template>
<script>
import My***ponent from './My***ponent.vue'
export default {
data() {
return {
message: 'Hello, Vue!'
}
},
***ponents: {
My***ponent
}
}
</script>
在上面的示例中,父组件Parent***ponent中使用了My***ponent组件,并通过v-model绑定了message变量,实现了父子组件之间的双向绑定。当在子组件中输入内容时,父组件中的message变量会自动更新。
四、$nextTick
$nextTick
是Vue实例的一个方法,用于在DOM更新之后执行代码,确保代码在DOM更新之后执行。
在Vue.js中,如果需要在更新DOM之后操作DOM元素,可以使用$nextTick方法来执行相应的代码。这样可以确保操作是在DOM更新完成之后进行的,避免出现DOM操作不及时导致的bug。
举个例子,假设有一个Vue组件中有一个按钮,点击按钮后改变按钮的背景颜色。但是由于Vue的更新并不是立即的,而是异步的,所以直接在按钮点击事件中修改样式可能无法生效
。此时可以使用$nextTick
方法来确保按钮背景颜色的修改在DOM更新之后执行:
methods: {
changeColor() {
this.buttonColor = 'blue';
this.$nextTick(() => {
document.getElementById('myButton').style.backgroundColor = 'blue';
});
}
}
五、插槽
插槽是一种特殊的语法,使父组件可以向子组件传递内容。插槽具有以下几个重要的知识点:
- 默认插槽:父组件可以在子组件中通过
<slot>
标签传递内容,而这些内容会被插槽内部的默认内容所替换。
示例:
// Parent.vue
<template>
<child>
<p>内容会插入到默认插槽中</p>
</child>
</template>
// Child.vue
<template>
<div>
<slot></slot>
</div>
</template>
- 具名插槽:父组件可以在子组件中使用具名插槽,以便给插槽传递多个不同的内容。
示例:
// Parent.vue
<template>
<child>
<!-- 简写 #header -->
<template v-slot:header>
<h2>头部内容</h2>
</template>
<template v-slot:footer>
<p>底部内容</p>
</template>
</child>
</template>
// Child.vue
<template>
<div>
<slot name="header"></slot>
<slot name="footer"></slot>
</div>
</template>
- 作用域插槽:父组件可以在插槽内部传递数据给子组件,子组件可以获取这些数据并渲染到插槽中。
示例:
<!-- Parent***ponent.vue -->
<template>
<Child***ponent>
<template v-slot:default="props">
<p>{{ props.message }}</p>
</template>
</Child***ponent>
</template>
<script>
import Child***ponent from './Child***ponent.vue';
export default {
***ponents: {
Child***ponent
},
data() {
return {
message: 'Hello from Parent***ponent'
}
}
}
</script>
<!-- Child***ponent.vue -->
<template>
<div>
<slot :message="message"></slot>
</div>
</template>
<script>
export default {
props: {
message: String
}
}
</script>
在上面的例子中,Parent***ponent
通过在Child***ponent
中使用作用域插槽传递了message
数据。Child***ponent
接收到message
数据后,在插槽中使用slot-scope
接收props
对象,然后可以在插槽中使用props
对象的message
属性。
六、动态、异步组件
1. 动态组件
动态组件在Vue2中用于根据不同的条件或状态动态地渲染不同的组件。这样可以根据需要动态加载不同的组件,提高了代码的灵活性和可维护性。
举例来说,假设我们有一个网页,其中有一个切换按钮,根据用户的点击来动态地展示不同的组件内容。我们可以使用动态组件来实现这个功能。具体的代码示例如下:
<template>
<div>
<button @click="current***ponent = '***ponentA'">显示组件A</button>
<button @click="current***ponent = '***ponentB'">显示组件B</button>
<***ponent :is="current***ponent"></***ponent>
</div>
</template>
<script>
import ***ponentA from './***ponentA.vue';
import ***ponentB from './***ponentB.vue';
export default {
data() {
return {
current***ponent: '***ponentA'
}
},
***ponents: {
***ponentA,
***ponentB
}
}
</script>
在上面的代码中,我们定义了两个按钮,分别用来显示组件A和组件B。当用户点击按钮时,current***ponent
的值会发生变化,从而动态加载不同的组件。这样可以根据用户的交互动态地展示不同的内容。
2. 异步组件
在Vue中,异步组件是一种特殊的组件,其内容是在组件被渲染时才进行加载。这意味着当页面加载时,异步组件的代码不会立即被加载,而是在需要的时候再进行加载,从而提高页面加载速度和性能。
异步组件的使用可以通过Vue的import()
函数或者Webpack的import
语法来实现。当需要使用异步组件时,可以将组件定义为一个方法,在需要时进行加载。
举例来说,下面是一个简单的异步组件的示例:
// 异步组件定义
const Async***ponent = () => ({
***ponent: import('./Async***ponent.vue')
});
// 在组件中使用异步组件
new Vue({
el: '#app',
***ponents: {
'async-***ponent': Async***ponent
},
template: `
<div>
<async-***ponent></async-***ponent>
</div>
`
});
在这个示例中,Async***ponent
是一个异步组件,其内容在需要显示时才会被加载。通过使用异步组件,可以有效地提高页面加载速度,特别是在页面含有大量组件的情况下。
七、keep-alive
在Vue中,keep-alive
是一个抽象组件,用于缓存不活动的组件实例,以便在组件切换时保留其状态或避免重新渲染。这在使用动态组件或路由时特别有用,能提升应用的性能和用户体验。
keep-alive
组件接收两个属性include
和exclude
,用于指定哪些组件需要被缓存或排除缓存。当被包裹的组件切换时,keep-alive
会检查其属性,并根据条件来保留或丢弃缓存实例。
下面是一个示例,演示如何在Vue中使用keep-alive
组件:
<template>
<div>
<keep-alive>
<router-view :key="$route.fullPath"></router-view>
</keep-alive>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
在上面的示例中,keep-alive
组件包裹了router-view
,通过设置<router-view :key="$route.fullPath"></router-view>
的key
属性为动态的路由路径,实现每个路由页面的缓存和切换。这样在路由之间切换时,之前访问过的路由页面会被保留在缓存中,避免重复渲染和重新加载。
总之,keep-alive
是一个非常有用的组件,在需要保留组件状态或避免重新渲染时,可以用来提升应用的性能和用户体验。
当一个组件被包裹后,组件将会缓存并保留其状态,避免在组件销毁后重新渲染。这意味着,即使组件被切换隐藏,组件的生命周期钩子函数也不会再执行,只有第一次加载时才执行。下面通过一个例子来说明组件被包裹后的生命周期。
假设我们有一个名为***ponentA的组件,它被包裹。当该组件首次加载时,会依次执行以下生命周期钩子函数:
- beforeCreate
- created
- beforeMount
- mounted
当我们切换到其他组件,***ponentA会被缓存并保留其状态。这时,再次回到***ponentA时,只有以下生命周期钩子函数会执行:
- activated
- deactivated
如果***ponentA被销毁,再次切换回来时,会重新执行以下生命周期钩子函数:
- beforeCreate
- created
- beforeMount
- mounted
通过包裹可以避免频繁的渲染和数据请求,提高页面性能和用户体验。
八、mixin
1. 基本使用
在Vue中,mixin是一种可重用的代码片段,允许在多个组件中共享相同的逻辑。使用mixin可以将组件中通用的属性、方法、生命周期钩子等提取出来,减少重复代码,提高代码的可维护性和重用性。
举例说明,假设我们有一个名为loadingMixin
的mixin,用来处理组件的加载状态:
const loadingMixin = {
data() {
return {
isLoading: false
}
},
methods: {
showLoading() {
this.isLoading = true
},
hideLoading() {
this.isLoading = false
}
}
}
然后我们可以在多个组件中使用这个mixin:
Vue.***ponent('***ponent1', {
mixins: [loadingMixin],
methods: {
fetchData() {
this.showLoading()
// fetch data
this.hideLoading()
}
}
})
Vue.***ponent('***ponent2', {
mixins: [loadingMixin],
methods: {
fetchData() {
this.showLoading()
// fetch data
this.hideLoading()
}
}
})
通过使用mixin,我们可以避免在每个组件中重复编写加载状态的代码,提高了代码的复用性和可维护性。
2. 存在问题
Vue中的mixin存在一些问题,其中包括:
-
命名冲突:当两个mixin中有相同的选项时,会发生命名冲突,导致无法准确地确定最终结果。
-
不可预期的属性覆盖:当不同的mixin中定义了同名的属性或方法时,可能会覆盖掉原组件中的同名属性或方法,导致意外的行为。
-
难以追踪问题:由于mixin可以在多个组件中被引用,当出现问题时,可能需要逐一检查各个组件和mixin,增加了调试的复杂性。
举例说明:
假设有一个用户信息的mixin和一个订单信息的mixin,在不同的组件中都引用了这两个mixin。如果用户信息的mixin中定义了一个名为userInfo的属性,而订单信息的mixin中也定义了一个同名的属性,那么在组件中使用时可能会遇到属性覆盖的问题,导致无法获取到正确的用户信息或订单信息。这种情况下就会出现不可预期的行为,增加了调试和维护的难度。因此,在使用mixin时需要格外小心,避免出现命名冲突和属性覆盖的问题。
九、谈谈你对MVVM的理解
大家可以看看这篇谈谈你对mvc和mvvm的理解
十、vue是如何实现响应式的
可以看看这篇vue响应式原理
十一、 vue2在使用Object.defineProperty实现数据响应式的时候有哪些缺点
使用Object.defineProperty实现数据响应式在某些方面会有一些缺点,主要包括:
- 对象只能监听已经存在的属性,无法监听新增属性。
- 对象监听的属性是针对对象的指定属性,无法监听整个对象的变化。
- 需要在初始化属性时就进行数据响应式的处理,不能动态添加响应式属性。
- 对象的嵌套属性需要单独进行监听处理,如果对象嵌套层级较深会比较麻烦。
- 不支持数组的监听,需要额外处理数组的操作。
- 性能较差,每个属性都需要添加getter和setter,会影响整体性能。
因此,虽然Object.defineProperty可以实现简单的数据响应式,但在实际开发中可能会遇到一些限制和性能问题。Vue3采用Proxy来实现数据响应式,解决了Object.defineProperty的一些缺点。
十二、谈谈你对虚拟dom的理解
可以看看这篇虚拟dom笔记
优化时间diff算法的时间复杂度到O(n)
- 只比较同一层级,不跨级比较
- tag不相同,则直接删掉重建,不再深度比较
- tag和key,两者都相同,则认为是相同节点,不再深度比较
十三、使用虚拟dom描述一个dom树
假设我们有以下的DOM树结构:
<div id="app">
<h1>Hello, Virtual DOM!</h1>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
对应的虚拟DOM树结构可以描述为:
{
type: 'div',
props: {
id: 'app',
children: [
{
type: 'h1',
props: {
children: 'Hello, Virtual DOM!'
}
},
{
type: 'ul',
props: {
children: [
{
type: 'li',
props: {
children: 'Item 1'
}
},
{
type: 'li',
props: {
children: 'Item 2'
}
},
{
type: 'li',
props: {
children: 'Item 3'
}
}
]
}
}
]
}
}
在这个虚拟DOM树中,每个节点都有type
字段表示节点类型,props
字段存储节点的属性和子节点信息。可以看到整个DOM树的结构被用对象表示出来,便于在页面渲染时快速进行比对和更新。