1. v-show 和 v-if 的区别?
- v-if 指令用于条件渲染,它会根据表达式的值的真假来决定是否渲染元素。如果表达式的值为 false,则该元素不会被渲染并且也不会保留在 DOM 中。
- v-show 指令用于条件展示,它不会从 DOM 中删除元素,只是简单地使用 CSS 属性 display 控制元素的显示和隐藏。
- v-if 更适用于在运行时动态添加/删除元素,而 v-show 更适用于频繁切换显示和隐藏。
2. 为何在 v-for 使用 key?
- 当 Vue 使用 v-for 指令更新被迭代的元素时,会通过 diff 算法检测哪些元素已经被添加、删除或移动,以便只更新需要更新的元素,而不是重新渲染整个列表。为了实现这个检测机制,Vue 使用每个元素的 key 作为跟踪它们的标识。
- 在为元素指定 key 时,key 的值应该是一个字符串或数值类型。它的值应该是唯一的,且不会随着元素的重新排序而改变。如果没有为元素指定唯一的 key,Vue 会使用元素的索引作为默认 key,但是元素的索引可能会发生变化,因此使用索引作为 key 会导致潜在的渲染错误。
3. 描述Vue组件的生命周期
- 在 Vue 中,组件生命周期指的是组件从创建到销毁的整个过程,包含了多个阶段和钩子函数。钩子函数在组件的不同阶段做出相应的处理,实现对组件的控制。Vue 组件的生命周期包含四个阶段:创建阶段、更新阶段、销毁阶段、激活\停用阶段。
- 创建阶段的主要工作是创建组件实例并将组件实例渲染到页面中。该阶段会调用四个钩子函数,即 beforeCreate、created、beforeMount、mounted。beforeCreate 函数在组件实例初始化之后,数据观测和事件配置之前被调用,此时组件实例尚未创建,不能访问 data 和 methods 等数据和方法。created 函数在组件实例创建完后被立即调用,此时组件实例已经创建,可以访问 data 和 methods 等数据和方法,但此时组件尚未被挂载到 DOM 中。beforeMount 函数在组件挂载之前被调用,此时组件已经完成了模板编译,但尚未将其渲染到页面中。mounted 函数在组件挂载完成后被调用,此时组件已经被渲染到页面中,可以进行需要访问 DOM 的操作。
- 更新阶段调用 beforeUpdate 和 updated 钩子函数。beforeUpdate 函数在组件更新之前被调用,此时组件的数据已经发生了变化,但是 DOM 尚未重新渲染,这个阶段可以进行组件数据更新前的处理工作,例如计算属性的更新、深度监听数据的变化等。updated 函数在组件更新之后被调用,此时组件的 DOM 已经重新渲染,数据观测(data observer)和 event/watcher 事件已经重新开始,可以进行一些需要访问更新后 DOM 元素的操作。
- 销毁阶段调用 beforeDestroy 和 destroyed 钩子函数。beforeDestroy 函数在组件实例销毁之前被调用,此时组件实例仍然可以访问,该阶段可以进行一些组件销毁前的处理工作,比如清除定时器、取消事件监听等。destroyed 函数在组件销毁之后被调用,此时组件实例已经被销毁,无法再访问组件实例中的任何数据和方法。
- 激活/停用阶段调用 activated 和 deactivated 钩子函数。activated 函数在 keep-alive 组件被激活时调用。deactivated 函数在 keep-alive 组件被停用时调用。
4. Vue父子组件生命周期执行顺序
在父组件中调用子组件时,组件的生命周期执行顺序如下:
- 父组件开始创建阶段,首先会调用父组件的 beforeCreate 和 created 函数。
- 父组件进入模板编译阶段,此时会先调用父组件的 beforeMount 函数,然后会递归遍历子组件的模板,创建子组件实例,并调用子组件的 beforeCreate 和 created 函数。
- 当子组件实例创建完成后,父组件会调用子组件的 beforeMount 函数,将子组件渲染到父组件的模板中,渲染完成会先调用子组件的 mounted 函数。当子组件全部渲染完成后,会调用父组件的 mounted 函数。此时父组件和子组件已经挂载完成。
- 当子组件的数据发生变化时,首先调用父组件的 beforeUpdate 函数,然后依次调用子组件的 beforeUpdate 和 updated 函数,当子组件全部更新完成后,会调用父组件的 updated 函数。当父组件的数据发生变化时,如果这些数据被传递给子组件作为 props 属性,那么会调用子组件的 beforeUpdate 和 updated 函数;如果父组件中的数据改变没有通过 props 属性传递给子组件,那么不会调用子组件的 beforeUpdate 和 updated 函数,只会调用父组件的 beforeUpdate 和 updated 函数。
- 当父组件被销毁时,子组件也会随之销毁,首先调用父组件的 beforeDestory 函数,然后依次调用子组件的 beforeDestory 和 destoryed 函数,当子组件全部销毁后,会调用父组件的 destoryed 函数。
5. Vue组件如何通讯?
- 父到子传值:父到子的传值可以通过在子组件中添加 props 配置项来完成,props 配置项的值可以是一个数组,数组中的元素用于接收父组件传递过来的数据,在父组件中通过子组件的标签属性来传递这些数据。我们可以把父到子传值理解成函数的封装和调用,props 数组中的元素是函数的形参,在使用子组件的时候,传入父组件的数据作为实参。
- 子到父传值:子到父的传值可以通过自定义事件完成。在子组件中,通过 $emit 方法触发自定义事件,$emit 方法的第一个参数是自定义事件的名称,之后的参数则是要传递的数据。在父组件中,使用 v-on 指令来监听这个自定义事件,v-on 指令的参数是自定义事件的名称,值是一个函数,这个函数会在自定义事件被触发时被执行,函数的参数就是子组件传递过来的数据。
- Vuex:Vuex 是 Vue.js 的官方状态管理库,用于管理应用程序中的共享状态,可以解决应用程序中状态管理复杂性的问题。Vuex 中有四个核心概念:1. state,用于存放数据,通过
this.$store.state.xxx
来访问。2. getters,相当于组件的计算属性,里面的函数自带一个 state 参数,通过this.$store.getters.xxx
来访问。3. mutations,在 mutations 中定义方法修改 state 中的数据,定义的方法必须的同步的,可以通过this.$store.commit("xxx")
来调用。4. actions,用于处理异步操作,但是 actions 不能修改数据,需要在 actions 定义的函数中提交 mutations,因为只有 mutations 中定义的方法能够修改数据,通过this.$store.dispatch("xxx")
来调用。
6. 描述Vue组件渲染和更新的过程
Vue组件渲染和更新的过程简单地概况为以下几个步骤:
- 初始化组件实例:在渲染一个组件之前,Vue会创建一个组件实例,并将组件的选项对象进行合并、处理,最终形成一个组件实例的配置对象。
- 渲染组件:Vue将组件实例的配置对象转化为一个渲染函数,并执行该渲染函数,生成一个虚拟DOM树。此时,Vue会对虚拟DOM树进行初次渲染,将组件显示在页面上。
- 监听数据变化:当组件实例中的响应式数据发生变化时,Vue中的渲染watcher会立即检测到这些变化,并重新计算组件的渲染函数,生成一个新的虚拟DOM树。
- 对比新旧虚拟DOM树:Vue会将新生成的虚拟DOM树和上一次渲染时生成的虚拟DOM树进行比较,找出需要更新的部分。
- 更新组件:Vue只会更新旧虚拟DOM树中有差异的部分,使组件达到更新的效果。
7. 双向数据绑定v-model的实现原理
- v-model 是 Vue.js 中常用的指令,通过该指令可以将表单元素的值与 Vue 实例的属性进行双向绑定。当用户输入表单元素的值时,Vue 会自动更新实例的属性,反之亦然。
- 使用 v-model 时,只需要在需要绑定的表单元素或组件上加上 v-model 指令,并把它的值设置为需要绑定的属性即可,代码如下:
`<input type="text" v-model="message">`
- v-model 指令实际上是一个语法糖,它相当于同时使用了一个名为 value 的属性 和 一个名为 input 的事件。上面的代码等价于:
`<input
type="text"
:value="message"
@input="message = $event.target.value"
>`
其中,:value="message"
是将组件的 value
属性与组件实例的 message
属性绑定起来;@input="message = $event.target.value"
则是将组件的 input 事件与一个函数绑定起来,这个函数将当前表单元素的值赋给组件实例的 message
属性。
8. 如何理解MVVM模式?
- 传统的组件渲染是静态渲染,数据更新需要操作DOM。Vue框架采用 MVVM(Model-View-ViewModel) 模式来管理应用程序的数据模型(Model)和视图界面(View)之间的交互,即数据驱动视图,达到减少DOM操作的目的。
- MVVM的基本思想是将 Model 和 View 进行解耦(Decoupling),也就是将 Model 和 View 分离开来,使得它们之间的依赖关系降到最小。然后通过 ViewModel 来协调它们之间的通信。当 Model 变化时,我们不需要手动更新 View,而是可以通过 Vue 的响应式机制,让 Vue 自动更新 View。同样,当用户与 View 交互时,我们也不需要手动修改 Model,而是可以通过 Vue 的指令和事件机制,让 Vue 自动更新 Model。这种解耦机制使得我们可以更加专注于业务逻辑的实现,而不需要过多地关注 View 和 Model 之间的细节。
- Model:Vue应用程序中的数据模型,通常是一个JavaScript对象或数组。这些数据模型被存储在Vue实例的 data 属性中。
- View:Vue应用程序中的视图界面,通常由 HTML模板 和 Vue指令 组成。Vue的模板语法允许开发人员在HTML中绑定数据以及表达式,从而实现视图的动态更新。
- ViewModel:ViewModel 是Vue应用程序的核心,它是一个Vue实例,它充当 Model 和 View 之间的桥梁。ViewModel 负责业务逻辑以及管理数据,它可以将 Model数据 绑定到 View 上,同时也可以响应 View 上的事件和用户交互。ViewModel 通过 Vue的响应式机制监听 Model数据 的变化,并自动更新 View。
9. 计算属性computed有何特点?
在Vue实例中,计算属性 computed 是一种用于实时计算 Vue 组件属性值的便捷方式。它可以缓存计算结果,只有在计算属性依赖的响应式数据发生改变时才重新计算;当依赖的数据没有改变时,计算属性不再计算,直接返回缓存的结果,提高了组件渲染的性能。
10. 为何组件中的data必须是一个函数?
在Vue中,每个组件实例都拥有自己的作用域和状态。如果 data 选项是一个简单的对象,那么该组件的实例都会共享这个对象,这样就会出现一个实例更改数据后导致另一个实例中的数据被改变的问题。将 data 定义为一个函数,每个组件实例在创建时都会调用该函数来返回一个对象,从而确保每个实例都拥有自己的数据。同时,Vue 在内部会对这个函数进行封装,确保它只被调用一次,从而避免不必要的性能损失。
11. Ajax请求应该放在哪个生命周期?
- Ajax(Asynchronous JavaScript and XML) 是一种网络请求方式,可以通过 JavaScript 和 XML 实现异步传输数据。Ajax 可以在不重新加载整个页面的情况下,通过异步请求服务器上的数据,并将数据局部更新到页面上,从而实现局部刷新的效果,提高用户的交互体验。
- 一般 Ajax 请求会在页面加载完成后,由用户触发某个事件时执行,也就是 mounted 生命周期后执行。这种方式可以避免在页面加载时对服务器进行不必要的请求,提高页面加载速度和用户体验。
12. 如何将父组件所有props传给子组件?
在调用子组件时,绑定 $props
属性。
<child-component v-bind="$props"></child-component>
13. 如何自己实现v-model?
对于一个自定义组件,如果要实现 v-model 双向绑定,需要通过 自定义v-model 的方式实现。自定义v-model 需要在自定义组件中使用 model选项 来配置 value属性 和 input事件的名称。并在组件模板中使用v-bind 将 value属性 绑定到 input元素的value属性上,并通过 v-on 监听 input事件,当input事件触发时将表单元素的值通过 $emit
方法发送出去。这样就可以使用v-model语法糖来实现自定义组件的双向绑定了。
14. 多个组件有相同逻辑,如何抽离?
- 在Vue中,mixin(混入)是一种将多个组件之间共享的逻辑抽象出来并进行重用的机制。通过定义一个mixin对象,可以将其应用于多个组件中,从而为这些组件提供相同的属性、方法、生命周期钩子等。
- mixin 是一个普通的 JavaScript 对象,可以包含各种属性和方法,例如data、computed、methods、created等。在组件中,通过定义 mixins 选项来应用一个或多个 mixin。当组件引用了一个或多个 mixin 时,mixin 中定义的属性和方法会被混合到该组件中,这些属性和方法与组件自生定义的属性和方法会合并成一个新的对象。如果 mixin 中定义的属性或方法与组件自身定义的属性或方法名称冲突,则以组件自身的属性或方法为准。
- 使用 mixin 可以将组件中相同的逻辑代码进行抽象和复用,避免了代码重复和冗余。但是,mixin 的使用也存在一些问题,比如,多个 mixin 可能会造成命名冲突。变量来源不明确,不利于代码阅读。mixin 和组件可能会出现多对多的关系,复杂度高。
15. 何时使用异步组件?
异步组件与常规组件的不同之处在于,异步组件只有在需要的时候才会被加载,而不是在应用程序初始化时就被全部加载。异步组件的应用场景是:当某个组件的体积比较大时,例如Echarts文件,如果应用初始化时就加载,会非常慢,严重影响性能。此时可以将该组件设置为异步组件,在需要使用该组件时,再加载该组件。异步组件通过 import()
函数引入。
16. 何时需要使用keep-alive?
- keep-alive是Vue内置的一个组件,可以将其用于需要缓存的动态组件,避免每次重新渲染时都要执行组件的 created、mounted、destoryed等钩子函数,从而提高应用程序的性能。
- keep-alive组件可以包裹动态组件,使其被缓存。被缓存的组件在组件切换时并不会被销毁,而是被保留在内存中,下次需要渲染时直接从缓存中读取组件实例,避免了组件的重新创建和重新渲染。
17. 何时需要使用beforeDestory?
beforeDestroy 函数在组件实例销毁之前被调用,此时组件实例仍然可以访问,该阶段可以进行一些组件销毁前的处理工作,比如清除定时器、取消事件监听 $off
、解绑自定义事件 event.$off
等。
18. 什么是作用域插槽?
- 作用域插槽(scoped slot)是Vue中的一种高级插槽技术。父组件希望使用子组件中的数据作为插槽中的内容,可以使用作用域插槽。
- 在父组件中,使用
<template>
标签来声明一个作用域插槽,并在其中使用v-slot
指令来指定作用域变量的名称。子组件可以使用<template>
标签来包含作用域插槽<slot>
。 - 下面是一个使用作用域插槽的示例:
<!--父组件-->
<template>
<div>
<child-component>
<template v-slot="slotProps">
{{slotProps.slotData.text}}
</template>
</child-component>
</div>
</template>
<script>
import child-component from './ChildComponent'
export default{
components: {
child-component
},
data(){
return{}
}
}
<script>
<!--子组件-->
<template>
<div>
<slot :slotData="message"></slot>
</div>
</template>
<script>
export default{
data(){
return {
message: {text: '子组件数据'}
}
}
}
</script>
19. Vuex中的actions和mutations有什么区别?
- mutations 中的函数用于处理 state 数据,函数执行的操作必须是同步的,这是为了保证对 state 的修改是可追溯和可控的。修改 state 数据的唯一方案是提交(commit) mutations。
- actions 中的函数可以包含任意异步操作,例如 Ajax请求、定时器任务等。当异步操作完成后,actions 会通过 commit 方法触发相关的 mutations 来修改 state。
20. Vue-router有哪些路由模式?
Vue-router 中提供了两种路由模式,一种是hash模式,另一种是history模式。
- hash模式是默认的路由模式。hash模式的URL中存在
#
,例如http://www.baidu.com/#/abc
,#
后面的内容就是路由地址。hash模式的优点是兼容性好,它不需要后端支持,只要在前端将URL中#
后面的内容进行解析,就能获取到路由信息。当URL的hash部分(#
后面的内容)发生变化时,会触发window.onhashchange
事件,该事件可以用来监听浏览器的返回\前进按钮、手动修改URL的hash部分等操作。 - history模式的URL不会像hash模式那样带有
#
,而是一个正常的URL,例如:http://www.baidu.com/path/to/page
。history模式是通过利用 HTML5 History API 实现的,可以在不刷新整个页面的情况下,动态改变当前URL地址和页面内容。可以通过window.onpopstate
来监听路由的变化。history模式需要后端的支持,因为它需要在服务器端进行配置。如果用户直接访问了一个history模式下的URL,而此时后端又没有相应的配置,就会出现404错误。Vue-router 提供了一个特殊的 fallback 选项,用于定义在服务器返回404错误页面时,应该返回什么样的页面。常见的做法是将 fallback 设为单页面应用的主页,以保证用户可以正确访问到页面。 - 在选择路由模式的时候,如果是 to B 的系统,推荐使用 hash模式,该模式的路由简单易用,兼容性好,对URL规范不敏感。如果是 to C 的系统,推荐使用 history 模式,该模式需要后端支持。总之需要考虑成本和收益,尽量选择简单的路由模式。
21. 如何配置Vue-router异步加载?
- Vue-rouer 的异步加载可以通过路由懒加载的方式实现,路由懒加载是指将路由对应的组件分开打包,只在需要的时候动态加载,而不是在页面初始化时全部加载。路由懒加载可以有效地减少初始页面加载的时间。
- 在 Vue.js 中,通过以下代码实现路由懒加载:
export default new VueRouter({
routes: [
{
path: '/foo',
component: ()=>import('./Foo.vue')
},
{
path: '/bar',
component: ()=>import('./Bar.vue')
}
]
})
22. 用vnode描述一个DOM结构
- 给定一个DOM结构片段:
<div id="div" class="container">
<p>abc</p>
<ul style="font-size: 20px">
<li>abc</li>
</ul>
</div>
- 使用vnode模拟该DOM结构:
{
tag: 'div',
props: {
id: 'div1',
className: 'container'
},
children: [
{
tag: 'p',
children: 'abc'
},
{
tag: 'ul',
props: {style: 'font-size: 20px'},
children: [
{
tag: 'li',
children: 'abc'
}
]
}
]
}
23. data的监听是如何实现的?
在Vue中,data的监听是通过Vue响应式机制完成的。Vue的响应式机制使用 ES5 中的 Object.defineProperty()
方法来实现对数据的监听。在Vue实例初始化时,会对 data 属性中的每个属性进行监听,将属性转化为 getter 和 setter 函数。当数据发生变化时, setter 函数会被触发,Vue的响应式机制就会重新渲染组件,更新视图界面。
24. Vue如何监听数组的变化?
- Vue的响应式机制通过在对象上使用
Object.defineProperty()
方法来实现属性的劫持,从而监听对象的变化。然而,由于Object.defineProperty()
只能劫持对象的属性,而不能劫持数组的变化,因此Vue对数组的监听需要做特殊处理。 - Vue对数组的监听通过重写数组的原型方法来实现,常见的数组原型方法有 push、pop、shift、unshift 等。例如,在调用 push() 方法时,Vue会在数组末尾添加新元素,并触发数组的 length 属性的更新,从而触发更新视图。
25. 请描述Vue响应式原理
-
Vue框架采用了MVVM模式来管理应用程序的Model和View之间的交互,即数据驱动视图,减少了DOM操作。当Model发生变化时,通过Vue的响应式机制,自动更新View。Vue响应式原理是基于数据劫持实现的,其核心是使用
Object.defineProperty()
方法对数据对象的属性进行监听,当数据发生变化时,触发 getter 和 setter 方法,从而实现数据的响应式更新。 -
Vue响应式机制主要包含以下几个部分:
名称 | 作用 |
---|---|
Observer:数据观察者 | 用于监听数据对象的属性变化,当属性发生变化时,通知订阅者进行更新 |
Watcher:数据订阅者 | 用于订阅 Observer 发送的属性变化通知,当属性发生变化时,调用更新函数进行界面更新 |
Dep:消息订阅与发布中心 | 用于存储订阅者和发送通知,当数据发生变化时,通知所有订阅者进行更新 |
Compiler:模板编译器 | 用于解析模板,生成抽象语法树(AST),实现模板与数据的关联 |
Directive:指令 | 用于将模板中的元素与数据绑定起来,实现数据的双向绑定,例如 v-if v-for v-bind 等 |
- Vue响应式的实现流程如下:
- 在 Vue 实例化时,通过递归遍历 data 属性中的所有属性,为每个属性设置 getter 和 setter 方法,并在 getter 方法中为该属性收集依赖(即将该属性相关的 Watcher 添加到 Dep 中)。
- 当数据发生变化时,会触发 setter 方法,setter 方法会通知 Dep 中所有订阅该属性的 Watcher 进行更新。
- Watcher 接收到更新通知后触发更新函数,根据被修改的属性的值,重新计算视图中需要更新的部分,并将这部分更新到DOM上。
26. diff算法的时间复杂度
Vue中的diff算法是指在更新虚拟DOM时,对新旧虚拟DOM进行比较,并尽可能地减少DOM操作的算法。使用diff算法比较两棵树的时间复杂度为 O ( n 3 ) O(n^3) O(n3),如果一棵树有1000个节点,就要执行十亿次计算,那么算法是不可用的。Vue对diff算法进行了优化,使得算法的时间复杂度降到了 O ( n ) O(n) O(n)。
Vue的diff算法的优化策略如下:
-
只比较同一层级,不跨级比较。
-
tag 和 key 都相同,则认为是相同节点,递归地进行子节点的比较。
-
tag 不相同,则直接删掉重建,不再深度比较。
27. 简述diff算法的过程
diff算法是用来比较新旧DOM树并完成DOM更新的算法。diff算法的核心包括四个函数,即h函数、patch函数、patchVnode函数、updateChildren函数。
- h函数用于在diff算法初始化时,新建vnode节点。h函数是通过函数重载的方式定义的,即传入函数的参数个数、参数类型不同时,函数会执行不同的内容。h函数的返回结果是执行vnode函数,vnode函数返回一个JS对象,也就是虚拟DOM。
- patch函数用于比较新旧虚拟DOM数据的根节点是否相同。patch函数接收两个参数,第一个参数可以是 vnode 也可以是 普通DOM元素,代表旧的 vnode;第二个参数是 vnode,代表新的 vnode。当第一个参数是DOM元素时,patch 函数会先创建一个空的 vnode,然后将这个空的 vnode 关联到该DOM元素上。如果传入的两个参数都是 vnode,且两个 vnode 相同(也就是 vnode 的 sel 和 key 一样),此时会调用 patchVnode 函数,用于比较这两个 vnode 的子集(text 和 children)。如果传入的两个 vnode 不相同,那么会删除旧的 vnode,然后根据新的 vnode,重建这个删除掉的 vnode。
- patchVnode函数是用于比较两个 vnode 并更新 DOM 的核心函数,它会比较新旧 vnode 节点的 text 和 children,执行 addVnodes 和 removeVnodes 函数更新DOM元素。
- updateChildren 函数是在 patchVnode 函数执行时,如果新旧 vnode 节点都存在 children 的情况执行的函数。该函数使用双端比较策略,对两个 vnode 的 children 进行对比。如果新旧节点的 children 存在 sel 和 key 相等的情况,那么就会在 updataChildren 函数中,递归调用 patchVnode 函数,比较子节点的 text 和 children。
- 总之,diff算法的过程,就是 patch -> patchVnode ->updateChildren ->patchVnode -> updateChildren -> patchVnode … 的循环递归过程。
28. Vue为何是异步渲染,$nextTick有什么用?
- Vue组件是异步渲染的,因为在组件的渲染过程中,涉及到数据的响应式更新、计算属性、侦听器等多个步骤,这些步骤可能会涉及到异步操作,例如异步获取数据,从而导致组件渲染时存在延迟。
- 在Vue中,
$nextTick
是一个实例方法,它用于在下次DOM更新循环结束之后执行延迟回调。当我们修改了Vue实例中的数据后,Vue异步执行DOM更新,更新完成之后我们可能需要进行一些DOM操作,但此时DOM还没有被更新,这时可以使用$nextTick
来确保DOM已经更新完毕,从而进行DOM操作。$nextTick
方法是异步执行的,因此回调函数不会立即执行,而是会在DOM更新后执行。这意味着,在回调函数中,我们可以访问到最新的DOM状态。
29. Vue有哪些性能优化方式?
- 合理使用 v-show 和 v-if
- 利用 computed 的缓存特性,合理利用计算属性。
- 使用 v-for 时添加 key 属性,并且避免 v-if 和 v-for 的同时使用。
- 在 beforeDestory 期间,及时销毁自定义事件,DOM事件等,避免内存泄漏。
- 合理使用异步组件,keep-alive,路由懒加载。
- data 层级不要太深。