1、谈一谈 nextTick 的原理
Vue 中的 nextTick 方法实现了类似于 JavaScript 中的 nextTick 的异步任务调度机制,它用于在 DOM 更新之后
执行回调函数。
当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。
nextTick()
可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise。
示例:
<script setup>
import { ref, nextTick } from 'vue'
const count = ref(0)
async function increment() {
count.value++
// DOM 还未更新
console.log(document.getElementById('counter').textContent) // 0
// 暂停代码执行,等待Promise
await nextTick()
// DOM 此时已经更新
console.log(document.getElementById('counter').textContent) // 1
}
</script>
<template>
<button id="counter" @click="increment">{{ count }}</button>
</template>
通过使用 nextTick,Vue 可以在 DOM 更新之后执行回调函数,这对于在更新后操作 DOM、获取正确的计算属性值等场景非常有用。
⭐️需要注意的是,Vue 在不同环境下使用不同的异步任务调度方式,优先使用 Promise,然后是 MutationObserver,最后才是 setTimeout。这样做是为了尽可能地利用最高效和可靠的异步机制来实现 nextTick 的功能。
2、Vue 组件 data 为什么必须是函数?
Vue 组件中的 data 选项为什么必须是一个函数,而不是一个普通的对象呢?
这是因为 Vue 组件在创建多个实例时,如果直接使用一个对象作为 data 选项的值,那么所有实例将共享同一个 data 对象,这会导致状态的混乱和难以维护。
通过将 data 选项定义为一个函数,Vue 能够为每个组件实例返回一个全新
的数据对象,确保每个实例都拥有独立的数据状态
。这样做的好处是,每个组件实例都可以独立地修改和管理自己的数据,而不会相互影响。
下面是一个示例代码,演示了为什么 data 必须是一个函数:
// 错误的写法,data 是一个对象
Vue.component('counter', {
data: {
count: 0
},
template: '<div>{{ count }}</div>'
});
// 正确的写法,data 是一个函数
Vue.component('counter', {
data() {
return {
count: 0
};
},
template: '<div>{{ count }}</div>'
});
在以上示例中,第一个组件的 data 选项是一个对象,而第二个组件的 data 选项是一个函数。
如果我们使用第一个组件创建多个实例,它们将共享同一个 count 属性,即使在不同的实例中修改 count 的值,最终都会相互影响。
而如果我们使用第二个组件创建多个实例,每个实例都会拥有自己独立的 count 属性,它们之间互不影响。
因此,为了保证组件实例之间的数据独立性和可维护性,Vue 要求我们将 data 选项定义为一个函数,每次创建组件实例时都会调用该函数返回一个新的数据对象。
这样,每个组件实例都可以管理自己的数据,避免了数据共享和状态混乱的问题。
3、vm.$set() 实现原理是什么?
vm.$set()
是 Vue.js 中用于在组件实例中添加响应式属性的方法。它的实现原理是通过调用 Vue 实例的内部方法 $set
来实现。
Vue.js 使用了一种叫做"响应式系统
"的机制来追踪数据的变化,并在数据发生改变时自动更新相关的视图。在创建 Vue 组件时,Vue 会对组件的数据对象进行劫持,使其成为响应式的。
当我们使用 vm.$set()
方法向组件实例中添加新的属性时,它会做以下几个步骤:
- 首先,
$set
方法会检查要添加的属性是否已经存在于组件实例的数据对象中。 - 如果属性已经存在,则直接更新属性的值,并触发相应的重新渲染。
- 如果属性不存在,则调用 Vue 内部的方法
defineReactive
来将新属性添加到组件实例的数据对象中,并将其设置为响应式的。 - 接下来,
$set
方法会触发依赖收集,将新属性添加到依赖列表中,以便在属性值发生变化时能够通知相关的视图进行更新。
通过使用 vm.$set()
方法,我们可以在运行时动态地添加响应式属性,使其能够与 Vue 的响应式系统进行交互,并实现自动更新视图的效果。
// 示例代码,展示了 vm.$set() 的使用
// 假设 vm 是 Vue 组件实例
vm.$set(vm.dataObject, 'newProperty', 'new value');
上述代码将在 vm
组件实例的 dataObject
对象中添加一个名为 newProperty
的属性,并将其值设置为 'new value'
。这个属性将成为响应式的,当其值发生变化时,相关的视图将自动更新。
4、什么是MVVM?
MVVM(Model-View-ViewModel)是一种软件架构模式,用于开发用户界面(UI)的应用程序。它将应用程序的逻辑分为三个主要组件:模型(Model)、视图(View)和视图模型(ViewModel)。
模型
(Model)代表应用程序中的数据和业务逻辑。它是应用程序的数据源,负责处理数据的获取、存储和操作。视图
(View)是用户界面的可见部分。它负责将数据呈现给用户,并接收用户的输入。视图模型
(ViewModel)是连接模型和视图的中间层。它从模型中获取数据,并根据视图的需求对数据进行处理和转换,然后将处理后的数据提供给视图进行显示。视图模型还负责处理视图中的用户输入,并将其转换为模型可以理解的操作。
简单理解:
M - Model——模型、数据
V - View——视图、UI
VM - ViewModel——用于实现双向数据绑定,即视图的改变,会自动反映到 ViewModel 上从而引起 Model 数据的变化,而 Model 数据的变化也会自动反映到 ViewModel 上从而引起 View 视图的重新渲染。
⭐️Vue 是一种实现了 MVVM 思想的框架
5、响应式数据原理(Vue2.x & Vue3.x)
响应式数据是 Vue 的核心特性之一,它使得当数据状态发生改变时,相关的组件自动进行了重新渲染,从而使视图和模型保持同步。
在 Vue 2.x 中,Vue 会将 data 中定义的属性添加到一个名为 Observer
的观察者实例中,当属性被访问时,会自动触发它的 get() 方法,将当前的 Watcher 实例添加进该属性的dep
实例中(也称作订阅
)。当属性被修改时,会触发 set() 方法,set() 中会通知订阅该属性的所有 Watcher 实例以及子组件的 Watcher 实例进行更新。
而在 Vue 3.x 中,则使用了Proxy
对象来进行响应式实现。当对象添加getter
和setter
,使用时就会被代理,对所有数据访问和修改进行拦截,从而实现自动更新。与 Observer 不同的是,Proxy 是由 JavaScript 引擎内置
的功能,而不是通过定义 setter 和 getter 实现。
总的来说,Vue 2.x 和 Vue 3.x 对于响应式数据的实现原理是有所不同的,但都是通过监听数据的变化,从而实现组件的自动重新渲染。
6、Proxy 与 Object.defineProperty 的优劣对比?
Proxy 与 Object.defineProperty 在实现响应式数据方面有很大的不同。下面是它们的优缺点对比:
-
Proxy的
优点
:-
Proxy 可以
直接监听对象
而不是监听对象的属性,因此可以捕获更多操作,如对象的新增、删除等处理。 -
Proxy 可以代理
多层嵌套
的对象,不需要对每个内层对象分别设置代理。 -
Proxy 可以
监听数组
内元素的变化,而 Object.defineProperty 只能通过修改数组原型方法才能实现。 -
Proxy 拥有更多的拦截方法,比如 has、ownKeys、defineProperty、deleteProperty 等。
-
-
Proxy的
缺点
:-
Proxy 不兼容低版本浏览器,需要 polyfill 支持。
-
Proxy 不支持 Object 的一些方法,如 Object.keys、Object.values 和 Object.entries 等。
-
-
Object.defineProperty的
优点
:-
兼容性好
,支持绝大多数的浏览器。 -
适合小型项目,比如单页应用。
-
可以控制属性的读写行为。
-
-
Object.defineProperty的
缺点
:-
只能监听数据的属性变化,而且只能监听已经存在的属性,对于新增或删除的属性无能为力。
-
对于对象上多层嵌套的属性,
需要递归遍历
每一个属性并设置 getter 和 setter,比较麻烦。 -
只能监听数组的变化方法,对数组内部元素的变化需要使用额外的处理方式,比如 Vue 中的 $set。
-
综上所述,使用 Proxy 进行数据劫持能够更加自然和高效地实现响应式数据,但缺点是兼容性问题。而 Object.defineProperty 兼容性好,但无法监听对象新增、删除等操作,对于大型复杂项目的数据劫持会有很多的繁琐操作。
7、Vue中组件通信的方式有哪些?
- Props和事件:通过使用props将数据从父组件传递给子组件,子组件可以通过事件将数据传递回父组件。这是一种单向数据流的方式,适用于父子组件之间的通信。
- 自定义事件:父组件可以使用
$emit
方法触发自定义事件,并在子组件中使用$on
方法监听这些事件。这样可以在兄弟组件之间进行通信。 - 事件总线:可以创建一个空的Vue实例作为事件总线,用于在任何组件之间进行通信。可以通过在组件中触发和监听事件来实现通信。
- Vuex:Vuex是Vue的官方状态管理库,用于管理应用程序的状态。它提供了一个全局的状态存储,可以在不同组件之间共享数据。通过在组件中使用
this.$store
访问存储的数据,以及使用mutations和actions来修改数据,可以实现组件之间的通信。 - $refs:通过在组件上使用
ref
属性,可以在父组件中直接访问子组件实例。这样可以通过子组件的方法和属性进行通信。 - Provide 和 Inject: 使用
provide
和inject
选项可以在祖先组件和后代组件之间建立依赖注入关系,从而共享数据。
8、虚拟DOM
虚拟DOM(Virtual DOM)是一种用于优化前端框架性能的技术,最初由React引入,后来也在其他前端框架中得到广泛应用,包括Vue.js。虚拟DOM是一个抽象的内存数据结构,它代表了真实DOM树的层次结构。
以下是虚拟DOM的工作原理:
-
虚拟DOM树: 当页面渲染时,框架会首先创建一个虚拟DOM树,这个树的结构和内容与实际的DOM树一一对应。但虚拟DOM树只存在于内存中。
-
状态变化检测: 当应用状态发生变化(如用户交互或数据更新)时,框架会重新生成一个新的虚拟DOM树,表示新的UI状态。
-
虚拟DOM比较: 接下来,框架会将新旧虚拟DOM树进行比较,找出两者之间的差异,即哪些部分需要更新。
-
差异更新: 框架只会操作实际DOM树中需要更新的部分,而不是整个DOM树。这可以大大减少DOM操作的成本。
-
实际DOM更新: 最后,框架将差异应用到实际的DOM树中,使界面与新的状态保持一致。
虚拟DOM的优势包括:
-
性能优化: 通过减少实际DOM操作的次数,虚拟DOM可以提高页面渲染性能,减少重绘和重排的开销。
-
跨平台: 虚拟DOM不依赖于浏览器特定的API,因此可以在不同平台上使用,如浏览器、服务器和移动应用。
-
方便的抽象: 虚拟DOM提供了一种抽象层,使开发人员更容易管理和更新UI状态。
⭐️需要注意的是,虚拟DOM并不是适用于所有情况的解决方案。在大多数小型应用中,直接操作实际DOM可能更加高效,因为虚拟DOM本身也会引入一些开销。虚拟DOM的主要价值体现在大规模和复杂的应用中,特别是需要频繁UI更新的情况下,它可以显著提高性能。
9、v-model双向数据绑定原理
v-model
是Vue.js中用于实现双向数据绑定的指令。它的原理涉及两个主要概念:数据绑定
和事件监听
。
以下是 v-model
的原理:
-
数据绑定: 当你在Vue.js中使用
v-model
指令时,它会在表单元素(如<input>
、<textarea>
和<select>
)上创建一个双向数据绑定。这意味着,表单元素的值将与Vue组件的数据属性双向同步。 -
值的初始化: 当组件首次加载时,
v-model
会将表单元素的值初始化为Vue组件数据属性的值。这确保了表单元素和组件数据的初始值一致。 -
数据到视图的同步: 当Vue组件数据属性的值发生变化时,
v-model
会自动将新的值同步到绑定的表单元素。这使得当你修改组件数据时,表单元素会自动更新以反映新值。 -
视图到数据的同步: 当用户在表单元素中输入内容时,
v-model
会监听元素上的输入事件(如input
或change
),并将输入的值自动更新到Vue组件的数据属性。这允许用户在表单元素中输入内容,而组件的数据也会跟着更新。
简而言之,v-model
建立了一个双向通道,使数据和视图之间的同步变得非常容易。这消除了手动更新数据和视图之间的差异的需要,简化了Vue.js应用程序的开发过程。
以下是一个示例,演示了 v-model
的工作原理:
<template>
<div>
<input v-model="message" type="text">
<p>{{ message }}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: "Hello, Vue.js"
};
}
};
</script>
在这个示例中,v-model
绑定了 <input>
元素和 message
数据属性,从而实现了双向数据绑定。当用户在输入框中输入内容时,message
的值会自动更新,反之亦然。
10、为什么v-for的key尽量不要使用index?
v-for
中的 key
属性用于帮助Vue.js跟踪节点的标识,以便更高效地更新DOM。尽量避免使用索引作为 key
的原因主要有以下几点:
-
不稳定的索引: 数组的索引是基于位置的,当你向数组中插入或删除元素时,索引会发生变化。这意味着如果你将索引作为
key
,在数据变化时,可能会导致节点的key
发生变化,从而触发不必要的DOM更新或重新渲染,这可能会导致性能问题。 -
可能导致渲染问题: 如果使用索引作为
key
,Vue.js 可能会错误地假设两个具有相同索引的元素具有相同的身份。这可能导致渲染问题,特别是在列表中有重复的值时,Vue.js 可能会混淆元素的身份。 -
难以维护和调试: 使用索引作为
key
可能会使代码更难以维护和调试,因为在数据变化时,你需要非常小心地处理key
变化可能引发的问题。
代替使用索引作为 key
,建议使用具有稳定唯一标识的属性或字段作为 key
。这可以是一个具有唯一ID的对象属性,或者是数据集合中的其他稳定属性。这样可以确保在数据变化时,每个元素都能够保持相同的 key
,从而更好地实现DOM元素的重用和更新。
总之,避免使用索引作为 key
可以帮助避免一系列潜在的问题,使你的Vue.js应用更加可靠和高效。
11、vue能监听到数组变化的方法有哪些?为什么这些方法能监听到呢?
Vue.js 提供了多种方式来监听数组的变化,使数组的变动可以触发响应式更新。这些方法包括:
-
使用数组变异方法(Mutation Methods): 这些方法包括
push()
、pop()
、shift()
、unshift()
、splice()
、sort()
和reverse()
,它们能够监听数组的变化是因为 Vue.js 在内部重写了这些方法,添加了响应式的逻辑。当你使用这些方法修改数组时,Vue.js能够检测到变化并触发相应的视图更新。 -
使用非变异方法(Non-Mutation Methods): 这些方法包括
filter()
、concat()
、slice()
,它们并不直接修改原始数组,而是返回一个新的数组,也能够触发响应式更新。Vue.js通过拦截这些方法的调用,使其返回的新数组具有响应式特性。 -
使用
vm.$set
或Vue.set
: 这些方法允许你在数组中的指定位置添加新元素,并触发响应式更新。它们通过将新元素变成响应式对象来实现。 -
使用
vm.$forceUpdate
: 这个方法可以强制重新渲染组件,包括数组的变化。尽管这种方式不是自动的响应式,但它可以用于手动触发更新。 -
使用
array.length
直接赋值: 直接修改数组的length
属性也能触发响应式更新。
这些方法能够监听数组的变化,是因为 Vue.js 在内部使用了双向数据绑定和响应式数据系统。当数组发生变化时,Vue.js能够检测到这些变化,然后触发相关组件的重新渲染,确保视图与数据的同步。
⭐️需要注意的是,如果你使用第三方库或纯JavaScript的方式修改数组,Vue.js可能无法自动检测到变化。在这种情况下,你可以使用 vm.$set
或 Vue.set
来通知 Vue.js 响应式系统,或者使用 vm.$forceUpdate
手动触发更新。但最好的做法是尽量使用Vue.js提供的数组方法来修改数组,以确保响应式更新的正确性。
12、v-if和v-for嵌套使用问题
(以下内容来自Vue官网文档)
与Vue2相反的是,在Vue3中,当同时使用时,v-if
比 v-for
优先级更高。所以并不推荐在一个元素上同时使用这两个指令,
当它们同时存在于一个节点上时,v-if
比 v-for
的优先级更高。这意味着 v-if
的条件将无法访问到 v-for
作用域内定义的变量别名:
<!--
这会抛出一个错误,因为属性 todo 此时
没有在该实例上定义
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
</li>
在外新包装一层 `<template>` 再在其上使用 `v-for` 可以解决这个问题 (这也更加明显易读):
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
13、Vue的父组件和子组件生命周期钩子执行顺序是什么?
Vue的父组件和子组件生命周期钩子的执行顺序如下:
- 父组件的
beforeCreate
钩子 - 父组件的
created
钩子 - 父组件的
beforeMount
钩子 - 子组件的
beforeCreate
钩子 - 子组件的
created
钩子 - 子组件的
beforeMount
钩子 - 子组件的
mounted
钩子 - 父组件的
mounted
钩子
在父组件和子组件的生命周期钩子执行过程中,父组件的钩子先于子组件的钩子执行。
14、v-show 和 v-if 有哪些区别?
v-show 和 v-if 是 Vue.js 中用于条件渲染的指令,它们有以下区别:
- v-show:v-show 是通过控制元素的
display
样式来决定是否显示元素。当条件为真时,元素会被显示;当条件为假时,元素会被隐藏,但仍然占据
着 DOM 空间。 - v-if:v-if 是通过
添加或移除
元素来决定是否显示元素。当条件为真时,元素会被添加到 DOM 中;当条件为假时,元素会被从 DOM 中移除。v-if
是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。v-if
也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。 - 渲染开销:
v-show
的渲染开销
较大,因为元素始终存在于 DOM 中,只是通过修改 display 样式来控制显示与隐藏;而v-if
的渲染开销
较小,因为元素的创建和销毁是动态的。 - 切换开销:
v-show
的切换开销
较小,因为元素始终存在于 DOM 中,只是修改 display 样式;而v-if
的切换开销
较大,因为元素的创建和销毁会触发额外的性能开销。
综上所述,如果需要频繁切换元素的显示与隐藏,且元素的数量较少,可以使用 v-show;如果需要根据条件动态地添加或移除元素,或者元素的数量较多,可以使用 v-if。
15、vue-router的 hash 模式和 history 模式是如何实现的?
vue-router 的 hash 模式和 history 模式是通过浏览器的 API 实现的。
-
hash 模式:使用浏览器的
window.location.hash
属性来实现。在 hash 模式下,URL 中的路径后面会有一个#
符号,例如:http://example.com/#/home
。当路由发生变化时,会修改window.location.hash
的值,然后通过监听hashchange
事件来响应路由的变化。 -
history 模式:使用浏览器的
history.pushState
和history.replaceState
方法来实现。在 history 模式下,URL 中的路径没有#
符号,例如:http://example.com/home
。当路由发生变化时,会调用history.pushState
或history.replaceState
方法来修改 URL,并通过监听popstate
事件来响应路由的变化。
⭐️需要注意的是,使用 history 模式需要服务器的支持,因为在 history 模式下,刷新页面或直接访问某个路由时,需要服务器返回对应的页面,否则会出现 404 错误。
16、Object.defineProperty有哪些缺点?
-
无法监听数组的变化
:Object.defineProperty 只能监听对象的属性变化,无法监听数组的变化。因此,当数组的 length 属性发生变化或直接修改数组的索引或通过数组的方法(如push、pop等)修改数组时,无法触发属性的getter和setter。 -
无法监听新增属性和删除属性
:使用 Object.defineProperty 定义的属性是静态的,无法监听新增属性和删除属性的变化。 -
无法深度监听
:Object.defineProperty 只能监听对象的属性变化,无法监听对象内部属性的变化。如果对象的属性值是对象,需要递归遍历对象的属性才能实现深度监听。 -
重复代码
:使用 Object.defineProperty 需要编写大量的重复代码来定义每个属性的 get 和 set 方法,增加了代码量和维护成本。
17、你都做过Vue哪些性能优化?
Vue.js 提供了一些性能优化的最佳实践和工具,以确保你的应用在大规模数据和复杂组件情况下能够高效运行。以下是一些Vue.js性能优化的常见方法:
-
使用响应式数据: Vue.js的响应式系统能够有效地追踪数据的变化,从而只更新必要的DOM元素。确保你的数据是响应式的,这将减少不必要的DOM操作。
-
使用
v-bind:key
: 在使用v-for
迭代渲染元素时,务必提供唯一的key
属性,以帮助Vue.js更好地管理DOM元素,减少不必要的重新渲染。 -
懒加载组件: 使用Vue.js的异步组件加载功能,以减小初始加载时的包大小。这样只有在需要时才会下载和渲染组件。
-
使用
v-if
和v-else
代替v-show
: 如果某个元素在大多数情况下是隐藏的,使用v-if
和v-else
而不是v-show
,因为v-show
只是使用CSS来隐藏元素,而v-if
会在条件不满足时完全移除元素。 -
避免过多的计算属性: 计算属性是惰性的,但过多的计算属性可能会导致性能问题。确保只计算必要的数据,并避免复杂的计算属性。
-
使用
v-cloak
: 如果你的模板在加载时短暂地显示出未编译的Vue.js模板标记,你可以使用v-cloak
来防止这种情况。 -
使用
<keep-alive>
: 如果你有频繁切换的组件,可以使用<keep-alive>
来缓存这些组件,以避免不必要的销毁和重新创建。 -
异步更新: 使用
this.$nextTick
来在DOM更新后执行操作,以确保在正确的时机访问DOM。 -
使用Vue Devtools: Vue Devtools是一个强大的工具,可以帮助你分析和优化你的Vue.js应用,包括组件性能和数据流程的可视化分析。
这些是一些常见的Vue.js性能优化方法,但实际的优化策略可能因应用的具体情况而有所不同。在进行性能优化时,最重要的是使用工具来分析你的应用并识别性能瓶颈,然后有针对性地采取措施来解决问题。
18、Vue修饰符有哪些?
-
事件修饰符
-
.stop
:阻止事件冒泡 -
.prevent
:阻止默认事件的触发 -
.self
:只有事件在目标元素本身触发时才会触发事件处理函数,不包括子元素 -
.capture
:使用事件捕获模式,即从外部元素到目标元素的事件触发顺序 -
.once
:事件只触发一次,之后会自动解绑… …
-
-
按键修饰符
.enter
:监听回车键(Enter)。.tab
:监听 Tab 键。.delete
:监听删除键或退格键。.esc
:监听 Esc 键。.space
:监听空格键。.up
:监听上箭头键。.down
:监听下箭头键。.left
:监听左箭头键。.right
:监听右箭头键。.ctrl
:监听 Ctrl 键。.alt
:监听 Alt 键。.shift
:监听 Shift 键。.meta
:监听 Meta 键(Windows 键或 Command 键)。- 鼠标按键修饰符
.left
:左键.right
:右键.middle
:中键
-
.exact 修饰符
.exact
修饰符允许控制触发一个事件所需的确定组合的系统按键修饰符。
-
v-model 修饰符
.lazy
:监听change事件.trim
:自动去除用户输入内容中两端的空格.number
:输入自动转换为数字(通过parseFloat()处理)自定义修饰符
19、你有写过自定义指令吗?自定义指令的生命周期(钩子函数)有哪些?
一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。下面是一个自定义指令的例子,当一个 input 元素被 Vue 插入到 DOM 中后,它会被自动聚焦:
const focus = {
mounted: (el) => el.focus()
}
export default {
directives: {
// 在模板中启用 v-focus
focus
}
}
<input v-focus />
自定义指令的生命周期:
const myDirective = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode, prevVnode) {
// 下面会介绍各个参数的细节
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode, prevVnode) {}
}
20、如何让样式当前有效? 如何修改第三方组件样式?
在 Vue 中,可以使用以下方法来使样式当前有效:
-
在style标签中
添加scoped属性
<style scoped> /* 修改第三方组件的样式 */ </style>
-
使用内联样式
:可以在 HTML 元素上使用style
属性来直接设置样式。例如:<div style="color: red;">这是红色文字</div>
-
使用动态绑定的类名
:可以通过绑定一个动态的类名来改变元素的样式。可以使用v-bind
或简写的:
来绑定一个包含类名的表达式。例如:<div :class="{ 'red-text': isRed }">这是红色文字</div>
在上述示例中,
isRed
是一个在 Vue 实例中定义的变量,如果它的值为true
,则会将类名red-text
应用到该元素上。 -
使用计算属性
:可以在计算属性中返回一个对象,对象的属性可以作为类名绑定到 HTML 元素上。例如:<div :class="textClass">这是红色文字</div>
new Vue({ data: { isRed: true }, computed: { textClass: function() { return { 'red-text': this.isRed } } } })
在上述示例中,
textClass
是一个计算属性,它会根据isRed
的值返回一个对象,该对象的属性red-text
会作为类名绑定到元素上。
关于修改第三方组件样式,有以下几种方法:
-
使用全局样式
:可以在全局样式中修改第三方组件的样式。在 Vue 项目中,可以在App.vue
或main.js
中引入全局样式文件,并在其中修改第三方组件的样式。 -
使用组件级样式
:可以在组件的样式中修改第三方组件的样式。在组件的样式中,可以使用选择器选择第三方组件,并修改其样式。 -
使用深度选择器
:如果第三方组件的样式被其他样式覆盖,可以使用深度选择器>>>
或/deep/
来穿透组件样式的作用域,直接修改第三方组件的样式。例如:<style scoped> .my-component >>> .third-party-component { /* 修改第三方组件的样式 */ } :deep(.van-search__action) { /* 修改第三方组件的样式 */ } </style>
-
使用自定义类名或样式绑定
:有些第三方组件库提供了自定义类名或样式绑定的API,你可以使用它们来修改组件的样式。具体的方法取决于所使用的组件库和文档。你可以查阅相关文档以了解如何修改特定组件的样式。
⭐️请注意,使用深度选择器可能会导致样式的全局污染,因此应谨慎使用。如果第三方组件提供了自定义样式的选项,最好使用它们提供的方式来修改样式,以避免全局样式冲突的问题。
21、vue.$set (obj, key, value) 方法的实现原理
vue.$set(obj, key, value)
方法的实现原理是通过调用 Vue 实例的 $set 方法来实现。
- 在 Vue 内部,
$set
方法会首先判断对象 obj 是否是一个响应式对象
,如果是,则直接调用内部的setter
方法进行赋值
。如果不是响应式对象,则会判断 obj 是否是一个数组,如果是数组,则调用数组的splice
方法进行赋值。如果既不是响应式对象也不是数组,则会将 key 和 value 作为属性和值添加
到 obj 上。 - 在执行赋值操作后,$set 方法会判断 obj 是否是一个响应式对象,如果是,则会调用内部的
observer
对新添加的属性进行监听。如果不是响应式对象,则会调用 Vue 的defineReactive
方法将新添加的属性转换为响应式属性。
总结来说,vue.$set(obj, key, value)
方法的实现原理是通过检查对象是否为响应式对象,然后根据 key
的存在与否来更新或添加相应的属性,并确保这些属性是响应式的,以便 Vue.js 可以追踪其变化并更新视图。
22、Vue-Router 的导航守卫和执行流程
导航守卫:
- 全局前置守卫(
beforeEach
):在路由切换开始之前调用,可以用来进行全局的权限验证或者其他操作。如果有多个全局前置守卫,它们将按照注册顺序依次执行。 - 路由独享守卫(
beforeEnter
):在路由配置中通过 beforeEnter 字段定义,只对当前路由生效。它们会在全局前置守卫之后调用。 - 组件内守卫(
beforeRouteEnter
、beforeRouteUpdate
、beforeRouteLeave
):在组件内部定义的守卫。beforeRouteEnter 在路由进入组件之前调用,beforeRouteUpdate 在组件复用时调用,beforeRouteLeave 在路由离开组件时调用。 - 全局解析守卫(
beforeResolve
):在所有全局前置守卫和路由独享守卫之后调用。它们会在异步路由组件被解析之后调用。 - 全局后置守卫(
afterEach
):在路由切换完成之后调用,可以用来进行一些清理操作或者其他操作。如果有多个全局后置守卫,它们将按照注册顺序依次执行。
执行流程:
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫(2.2+)。 - 在路由配置里调用
beforeEnter
(路由独享的守卫)。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫(2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入。
23、Vue-Router 中的路由模式和区别
Vue-Router 中的路由模式有两种:哈希模式(Hash Mode)和历史模式(History Mode)。
-
哈希模式(Hash Mode)是 Vue-Router 默认的路由模式。在哈希模式下,URL 中的路由会被格式化为
/#/
开头的带有哈希值的 URL。例如,http://example.com/#/home
。哈希值的改变不会导致浏览器向服务器发送请求,而是通过监听hashchange
事件来触发路由的切换。哈希模式的优点是兼容性好,可以在不支持 HTML5 History API 的浏览器中正常工作。 -
历史模式(History Mode)使用 HTML5 History API 来管理路由。在历史模式下,URL 中的路由不再带有哈希值,而是使用真实的 URL 地址,例如
http://example.com/home
。为了使历史模式在单页应用中正常工作,需要在服务器上进行相应的配置,以确保在用户访问直接路由时返回正确的页面。历史模式的优点是 URL 更加美观,没有哈希值的干扰。
区别:
- 哈希模式不需要服务器配置,可以直接在前端使用,而历史模式需要服务器配置支持。
- 哈希模式的 URL 中包含 # 符号,不太美观,而历史模式的 URL 更加直观。
- 哈希模式的路由不会触发页面刷新,而历史模式会触发页面刷新。
- 哈希模式可以兼容老版本浏览器,而历史模式需要浏览器支持 HTML5 的 history API。
24、Vuex 的理解
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以一种可预测的方式进行状态的修改和响应。Vuex 的核心概念包括 state、mutations、actions、getters 和 modules。
-
State
(状态):Vuex 使用单一状态树(Single State Tree)来存储应用的所有状态,即将所有组件的状态集中存储在一个可响应的对象中。State 是唯一的,可以通过this.$store.state
来访问。 -
Mutations
(变更):Mutations 用于修改 State 中的状态。它是一个同步的操作,通过提交(commit)Mutations 来修改状态。Mutations 接收两个参数:state(当前状态)和 payload(载荷,即传递的参数),每个 Mutation 都有一个字符串类型的事件类型和一个回调函数,用于实际修改状态的逻辑,通过this.$store.commit('mutationName', payload)
来提交 Mutations。 -
Actions
(动作):Actions 用于处理异步操作或者批量的 Mutations。它可以包含任意异步操作,通过提交(dispatch)Actions 来触发。Actions 接收一个 context 对象,包含了与 Mutations 相同的方法和属性,可以通过this.$store.dispatch('actionName', payload)
来触发 Actions。 -
Getters
(计算属性):Getters 用于对 State 中的状态进行计算和派生。它类似于 Vue 组件中的计算属性,可以根据 State 中的状态进行一些复杂的计算,并返回结果。Getters 可以通过this.$store.getters.getterName
来获取。 -
Modules
(模块):Modules 允许将 Store 分割成多个模块,每个模块拥有自己的 state、mutations、actions、getters。模块内部的状态和操作可以通过命名空间来隔离和访问。
通过使用 Vuex,我们可以将应用的状态集中管理,方便状态的共享
和修改
,使得应用的状态变得可追踪、可维护,并且能够更好地处理复杂的状态逻辑。
25、你有对 Vue 项目进行哪些优化?
-
代码压缩:可以使用工具(例如UglifyJS)对Vue项目的代码进行压缩和混淆,以减小文件大小和保护代码逻辑。
-
懒加载:对于大型的Vue项目,可以将路由和组件进行懒加载,只在需要时才加载相应的代码和资源,以减少初始加载时间和减轻服务器负载。
-
图片优化:优化项目中的图片资源,包括压缩图片大小、使用适当的格式(如WebP)使用精灵图和使用图片CDN等,以减少页面加载时间。
-
异步组件:将一些耗时的组件拆分为异步组件,并使用Vue的异步组件加载机制,以提高页面的响应速度。
-
缓存优化:使用合适的缓存策略,例如使用浏览器缓存、CDN缓存或服务器端缓存,以减少请求次数和提高页面加载速度。
-
代码分割:将Vue项目的代码分割成多个小块,按需加载,以提高页面的首次加载速度。
-
减少重绘和回流:通过合理的CSS布局和避免频繁的DOM操作,减少浏览器的重绘(Repaint)和回流(Reflow)操作,以提高页面性能。
26、虚拟DOM的优劣如何?
虚拟DOM(Virtual DOM)是一种在前端开发中常见的技术概念,它的优劣取决于具体的使用场景和需求。
优点:
- 性能优化:虚拟DOM可以通过批量更新和高效的DOM diff算法,减少真实DOM操作的次数,从而提高页面的性能和响应速度。
- 跨平台兼容:虚拟DOM可以在不同的平台上运行,例如浏览器、移动端和服务器端。这种跨平台的能力使得开发者可以共享大部分代码,并提高开发效率。
- 简化复杂性:虚拟DOM提供了一种抽象层,使得开发者可以以声明性的方式描述用户界面的状态和交互,而无需直接操作底层的DOM API,从而简化了开发过程。
- 组件化开发:虚拟DOM与组件化开发相结合,可以提供更高层次的抽象和复用性,使得开发者可以轻松构建和维护复杂的用户界面。
缺点:
- 内存消耗:虚拟DOM需要在内存中维护一份虚拟DOM树的副本,这可能会导致内存消耗较大,尤其是对于大型的应用程序或页面。
- 初始化耗时:虚拟DOM在初始化时需要构建整个虚拟DOM树,并进行初始渲染,这可能会导致一定的初始化耗时。
- 学习成本:虚拟DOM需要开发者掌握额外的概念和技术,例如虚拟DOM的 diff算法和更新策略,这可能对于新手来说存在一定的学习曲线。
综合来说,虚拟DOM在大多数情况下是有益的,它可以提供性能优化、跨平台兼容、简化复杂性和组件化开发等优势。然而,在特定的场景下,如对于简单的应用程序或性能要求极高的场景,直接操作真实的DOM可能更加高效。
27、Vuex和Pinia的区别
Vuex 和 Pinia 都是用于 Vue.js 应用程序的状态管理库,它们有以下几个区别:
-
API 设计:Vuex 使用对象风格的 API,通过创建一个包含状态、mutations、actions 和 getters 的单一 Store 对象来管理应用的状态。而 Pinia 使用类风格的 API,通过创建一个继承自
Pinia
类的 Store 类来管理状态。 -
TypeScript 支持:Pinia 是为 TypeScript 设计的,它提供了完整的类型支持,并且能够自动推断状态和操作的类型。而 Vuex 的 TypeScript 支持相对较弱,需要手动添加类型声明。
-
性能优化:Pinia 采用了更加现代的响应式机制,使用 Proxy 对象来实现状态的监听和更新,相比于 Vuex 的基于 Object.defineProperty 的实现方式,性能更好。Pinia 还支持局部状态,可以将状态和操作定义在组件内部,避免不必要的全局状态更新。
-
插件生态系统:Vuex 有一个丰富的插件生态系统,可以通过插件来扩展 Vuex 的功能,例如调试工具、持久化存储等。而 Pinia 的插件生态系统相对较小,目前的插件支持相对有限。
-
生态和社区支持:Vuex 是 Vue.js 官方推荐的状态管理库,拥有庞大的社区和生态系统,有很多第三方插件和工具可供选择。而 Pinia 是一个相对较新的库,生态和社区支持相对较小,插件和工具的选择相对有限。
总的来说,Vuex 是一个成熟、稳定且功能丰富的状态管理库,适用于大多数Vue项目、大型应用和复杂的状态管理需求。而Pinia则是一个新的、基于Vue 3的状态管理库,Pinia 更加现代化和轻量,提供了更简洁和直观的API设计,并在性能方面进行了优化,适合于 TypeScript 项目和对性能有较高要求的应用。
Vue就先这一期啦!☀️☀️,下期更新React❕ ❕ ❕
个人收集整理、创作不易, 若有帮助🉑, 请帮忙点赞👍➕收藏❤️, 谢谢!✨✨🚀🚀