1.Vue实现双向数据绑定的原理
1.Vue 数据双向绑定原理是通过 **数据劫持** + **发布者-订阅者模式** 的方式来实现的,首先是通过 ES5 提供的 Object.defineProperty() 方法来劫持(监听)各属性的 getter、setter,并在当监听的属性发生变动时通知订阅者,是否需要更新,若更新就会执行对应的更新函数。
2.通过Object.defineProperty去劫持data里的属性,将data全部属性替换成getter和setter,配合发布者和订阅者模式,每一个组件都有一个watcher实例,当我们对data属性赋值和改变,就会触发setter,setter会通知watcher,从而使它关联的组件进行重新渲染。
- 常见的
基于数据劫持的双向绑定有两种实现-
一个是目前Vue在用的
Object.defineProperty -
一个是ES2015中新增的
Proxy,而在Vue3.0版本后加入Proxy从而代替Object.defineProperty
-
2. 几种实现双向绑定的做法
目前几种主流的mvc(vm)框架都实现了单向数据绑定,而双向数据绑定可以理解为是在单向绑定的基础上给可输入元素(input、textarea等)添加了change(input)事件,来动态修改model和 view。
实现数据绑定的做法有大致如下几种:
发布者-订阅者模式(backbone.js)
脏值检查(angular.js)
数据劫持(vue.js)
**发布者-订阅者模式:** 一般通过sub,pub的方式实现数据和视图的绑定监听,更新数据方式通常做法是 vm.set('property', value)。而我们更希望通过 vm.property = value 这种方式更新数据,同时自动更新视图,于是有了下面两种方式。
**脏值检查:** angular.js 是通过脏值检测的方式比对数据是否有变更,来决定是否更新视图,最简单的方式就是通过 setInterval() 定时轮询检测数据变动,当然Google 限制 angular只有在指定的事件触发时进入脏值检测,大致如下:
- DOM事件,如用户输入文本,点击按钮等。( ng-click )
- XHR响应事件 (
$http) - 浏览器Location变更事件 (
$location) - Timer事件(
$timeout,$interval) - 执行
$digest()或$apply()
**数据劫持**: vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty() 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的 监听回调。
数据劫持的优势
目前业界分为两个大的流派,一个是以React为首的单向数据绑定,另一个是以Angular、Vue为主的双向数据绑定。
三大框架都是既可以双向绑定 也可以单向绑定,如React可以手动绑定onChange和value实现双向绑定,也可以调用一些双向绑定库;Vue也加入了props这种单向流的api。
对比其他双向绑定的实现方法,数据劫持的优势所在:
- 无需显示调用: 例如Vue运用数据劫持+发布订阅,直接可以通知变化并驱动视图,而如Angular的脏检测则需要显示调用markForCheck(可以用zone.js避免显示调用,不展开),react需要显示调用setState。
- 可精确得知变化数据:劫持了属性的setter,当属性值改变,可以精确获知变化的内容
newVal,因此在这部分不需要额外的diff操作,否则只知道数据发生了变化而不知道具体哪些数据变化了,这个时候需要大量diff来找出变化值是额外性能损耗。
3.3 实现思路
基于数据劫持双向绑定的实现思路:数据劫持是双向绑定各种方案中比较流行的一种,最著名的实现是Vue。
要实现一个完整的双向绑定需要以下几个要点:
-
利用
Proxy或Object.defineProperty生成的Observer针对对象/对象的属性进行"劫持",在属性发生变化后通知订阅者 -
解析器
Compile解析模板中的Directive(指令),收集指令所依赖的方法和数据,等待数据变化然后进行渲染 -
Watcher属于Observer和Compile桥梁,它将接收到的Observer产生的数据变化,并根据Compile提供的指令进行视图渲染,使得数据变化促使视图变化
信息多来自Vue双向数据绑定原理(面试必问) - 掘金 (juejin.cn)
Vue实例挂载的过程中发生了什么?
在 Vue 实例挂载的过程中,主要发生了以下几个步骤:
-
实例初始化:Vue 首先会对实例进行初始化,包括合并选项、初始化生命周期钩子、初始化事件等。
-
数据观测:Vue 会对实例的数据进行观测,即将数据转换为响应式的,以便在数据发生变化时能够通知相关的视图进行更新。这是通过 Vue 的响应式系统(Reactivity System)实现的。
-
模板编译:如果实例中存在模板(template),Vue 会将模板编译成渲染函数。模板编译过程会将模板解析为抽象语法树(AST),然后将其转换为渲染函数,最后生成可被执行的渲染函数。
-
创建虚拟节点:在挂载之前,Vue 会根据渲染函数的结果创建一个虚拟节点(Virtual DOM),虚拟节点是一个描述真实 DOM 结构的 JavaScript 对象。
-
挂载到真实 DOM:Vue 将创建的虚拟节点(Virtual DOM)挂载到真实的 DOM 上,通过将虚拟节点转换为真实 DOM 元素,并插入到指定的挂载点(mount point)上,完成了实例的挂载过程。
-
数据初始化与计算属性:在挂载完成后,Vue 会对实例的数据进行初始化,并计算所定义的计算属性(computed properties)。
-
监听器和侦听器:Vue 会建立监听器(watcher)来观察数据的变化,并且会建立侦听器(listener)来监听事件的发生。
-
生命周期钩子:最后,Vue 会按照生命周期的顺序调用相应的生命周期钩子函数,比如
created、mounted等。
总结起来,Vue 实例挂载的过程主要包括实例初始化、数据观测、模板编译、创建虚拟节点、挂载到真实 DOM、数据初始化与计算属性、监听器和侦听器以及生命周期钩子等步骤。这些步骤确保了 Vue 实例的正确初始化和挂载,并为后续的数据更新和事件处理提供了基础。
Vue 模板是如何编译的
Vue 模板的编译是在运行时进行的,它将模板转换为渲染函数,包括以下几个步骤:
-
模板解析:Vue 首先将模板字符串解析为抽象语法树(AST)。AST 是一个树状结构,它描述了模板中的各种节点和它们之间的关系。
-
静态节点优化:在 AST 中,有些节点是静态的,即它们在整个组件的生命周期内不会发生变化。Vue 会通过静态节点优化,将这些静态节点标记出来,并且在首次渲染时只创建一次,后续渲染直接复用,减少了不必要的性能开销。
-
生成渲染函数:根据经过解析和优化的 AST,Vue 会生成一个渲染函数,渲染函数是一个函数,它会返回虚拟 DOM(Virtual DOM)。
-
执行渲染函数:当组件需要渲染时,Vue 会执行渲染函数,生成新的虚拟 DOM。
-
虚拟 DOM 更新:通过对比新旧虚拟 DOM 的差异,Vue 可以找到需要更新的部分,并且只更新这些部分的真实 DOM,而不是整个页面。
这样的编译过程在运行时执行,可以根据组件实例的状态和数据动态生成渲染函数,从而实现了 Vue 的响应式更新机制。
需要注意的是,Vue 还提供了编译器(Compiler)可以将模板编译为可执行的 JavaScript 代码,这样可以在构建阶段提前进行模板的编译,从而减少运行时的编译开销。但是大多数情况下,Vue 在浏览器中是以运行时版本的形式使用,即使用运行时编译器来动态编译模板。
3.Vue组件间通信方式都有哪些?
Vue组件间通信可以通过以下几种方式实现:
-
Props / Events(父子组件通信):
- 父组件通过props向子组件传递数据。
- 子组件通过触发事件并携带数据,来向父组件发送消息。
-
自定义事件(子组件通知父组件):
- 可以使用
$emit方法在子组件中触发一个自定义事件,并在父组件中监听这个事件。
- 可以使用
-
Event Bus(非父子关系组件通信):
- 可以创建一个空的Vue实例作为事件总线,用于在任意组件间进行事件传递。
-
Vuex(全局状态管理):
- 通过Vuex实现全局的状态管理,不同组件可以通过Vuex来进行状态共享和通信。
-
Provide / Inject(祖先和后代组件通信):
- 使用
provide和inject选项可以实现祖先组件向所有后代组件注入数据。
- 使用
-
listeners(透传属性和事件):
- 在具有详细选项或事件监听器的组件上,可以使用
$attrs和$listeners来进行属性和事件的透传。
- 在具有详细选项或事件监听器的组件上,可以使用
以上是Vue中常见的组件间通信方式,开发者可以根据实际需求和场景选择合适的方式来实现组件间的通信。
详见vue中8种组件通信方式, 值得收藏! - 掘金 (juejin.cn)
3.Vue 2.0和Vue 3有以下几个主要区别
-
性能优化:Vue 3在内部进行了重写,采用了新的响应式系统。通过使用Proxy代理对象来跟踪属性的变化,大大提升了性能。Vue 3的编译器也经过了优化,生成的代码更加高效。
-
Composition API:Vue 3引入了Composition API,它是一种基于函数的API风格,可以更灵活地组织和重用组件逻辑。使用Composition API可以将相关逻辑聚合在一起,提高代码的可读性和维护性。
-
更好的 TypeScript 支持:Vue 3对TypeScript的支持更加完善。通过使用Composition API,可以更好地结合TypeScript的类型推断和类型校验功能。
-
更小的体积:Vue 3的体积相比Vue 2.0更小,同时还提供了更好的Tree-shaking支持,可以更有效地减少打包后的文件大小。
-
Fragment、Teleport 和 Suspense:Vue 3引入了Fragment(片段)、Teleport(传送门)和Suspense(占位符)等新的特性,使得组件的编写更加灵活和便捷。
-
更强大的响应式系统:Vue 3的响应式系统被重写,并且提供了更多的API来处理响应式数据。例如,可以使用
ref和reactive来创建响应式数据,并使用新的watchAPI来监听数据的变化。
需要注意的是,由于Vue 3进行了较大的改动,与Vue 2.0存在一些不兼容的地方。因此,在升级到Vue 3之前,需要仔细阅读官方文档并进行相应的迁移工作。
4.Vue 3.0 引入的 Composition API 和 Vue 2.x 使用的 Options API 有以下几个主要区别:
- 组织代码的方式:
- Options API:Options API 是基于配置对象的方式组织代码,将相关逻辑分散在不同的选项中(如
data、methods、computed等),导致当组件变得复杂时,代码难以维护和理解。 - Composition API:Composition API 是基于函数的方式组织代码,将相关逻辑聚合在一起,使得组件逻辑更加清晰和可维护。通过使用
setup函数,可以将相关代码放在一个地方,提高代码的可读性和重用性。
2.数据与方法的定义:
- Options API:在 Options API 中,需要将数据和方法定义在不同的选项中,如
data中定义数据,methods中定义方法,computed中定义计算属性等。 - Composition API:在 Composition API 中,可以根据功能或逻辑将相关的数据和方法聚合在一起,使得代码更加集中和易于管理。可以使用
ref和reactive来定义响应式数据,使用computed来定义计算属性,使用watch来监听数据变化等。
3.生命周期钩子函数
-
Options API:在 Options API 中,生命周期钩子函数是通过在配置对象中定义相应的方法来实现的,如
created、mounted、updated等。 -
Composition API:在 Composition API 中,可以使用
onBeforeMount、onMounted、onUpdated等函数来实现生命周期的钩子函数。这些函数可以在setup函数内部直接使用。
5.Vue3中常用的函数
在Vue 3中,以下是一些常用的函数:
-
reactive:用于将一个普通的JavaScript对象转换为响应式对象。通过调用reactive函数,可以使对象的属性变成响应式的,当属性的值发生改变时,相关组件会自动更新。 -
ref:用于创建一个包装器对象,将普通的JavaScript值转换为响应式的。ref函数会返回一个带有value属性的对象,通过读写value属性来访问和修改值。当使用ref封装的值发生改变时,相关组件也会相应地更新。 -
computed:用于创建一个计算属性。计算属性是根据依赖数据进行计算得到的值,当依赖数据发生改变时,计算属性会自动重新计算并返回新的值。computed函数接受一个函数作为参数,该函数包含计算逻辑,并返回计算结果。 -
watch:用于监听响应式数据的变化,当指定的响应式数据发生改变时,执行相应的回调函数。watch函数接受两个参数,第一个参数是要监听的响应式数据,第二个参数是回调函数,用于处理数据变化的逻辑。 -
toRefs:用于将响应式对象的属性转换为单独的ref对象。在Vue 3中,通过setup函数创建的响应式对象默认是不可解构的,使用toRefs函数可以消除这种限制,将每个属性都转换为可解构的ref对象。 -
onMounted:在组件挂载完成后执行的钩子函数。可以通过onMounted函数注册一个回调函数,在组件挂载完成后执行特定的操作,比如发送请求、订阅事件等。 -
onUnmounted:在组件卸载前执行的钩子函数。可以通过onUnmounted函数注册一个回调函数,在组件卸载前执行特定的清理操作,比如取消订阅、清除定时器等。
这些函数是Vue 3中常用的函数,用于处理响应式数据、计算属性、监听数据变化以及组件的生命周期钩子等。它们能够提高代码的可读性和可维护性,并简化Vue组件的开发过程
当然,我可以给你提供一些示例代码来演示这些函数的用法。
-
reactive示例:import { reactive } from 'vue';
const state = reactive({ count: 0, message: 'Hello Vue 3', });
console.log(state.count); // 输出: 0
state.count++; // 修改响应式属性
console.log(state.count); // 输出: 1
-
ref示例:import { ref } from 'vue';
const count = ref(0);
console.log(count.value); // 输出: 0
count.value++; // 修改ref封装的值
console.log(count.value); // 输出: 1
-
computed示例:import { computed, reactive } from 'vue';
const state = reactive({ count: 0, });
const doubleCount = computed(() => state.count * 2);
console.log(doubleCount.value); // 输出: 0
state.count++;
console.log(doubleCount.value); // 输出: 2 (自动重新计算)
-
watch示例:import { watch, reactive } from 'vue';
const state = reactive({ count: 0, });
watch(() => state.count, (newVal, oldVal) => { console.log(
count 发生了变化,新值为 ${newVal},旧值为 ${oldVal}); });state.count++; // 输出: count 发生了变化,新值为 1,旧值为 0
-
toRefs示例:import { reactive, toRefs } from 'vue';
const state = reactive({ count: 0, message: 'Hello Vue 3', });
const { count, message } = toRefs(state);
console.log(count.value); // 输出: 0 console.log(message.value); // 输出: 'Hello Vue 3'
这些示例代码演示了Vue 3中常用函数的用法,包括reactive、ref、computed、watch和toRefs。你可以根据自己的需求参考这些示例代码,并结合Vue 3的文档进行更详细的学习和实践。
**reactive**和**ref**在Vue 3中都用于处理响应式数据,但在不同的场景下有不同的使用情况。
-
reactive:-
当你需要将一个普通的JavaScript对象转换为响应式对象时,可以使用
reactive。它会递归地将对象的所有属性转换为响应式,并返回一个代理对象。你可以通过访问和修改代理对象的属性来触发组件的重新渲染。 -
例如,当你有一个包含多个属性的状态对象时,你可以使用
reactive来将其转换为响应式对象,并在组件中使用该对象的属性。import { reactive } from 'vue';
const state = reactive({ count: 0, message: 'Hello Vue 3', });
-
-
ref:-
当你只需要处理一个单一的值时,可以使用
ref。它会将普通的JavaScript值转换为响应式对象,并返回一个带有value属性的对象。你可以通过读取和写入value属性来访问和修改值。 -
例如,当你需要处理一个计数器变量时,你可以使用
ref来将其转换为响应式对象,并在组件中使用value属性来访问和修改计数器的值。import { ref } from 'vue';
const count = ref(0);
-
总之,reactive适用于处理包含多个属性的对象,而ref适用于处理单一的值。你可以根据数据的复杂性和使用场景选择合适的函数来处理响应式数据。
6.如何在组件中实现 v-model ?
在 Vue 2 组件中实现 v-model,只需定义 model 属性即可。
export default {
model: {
prop: 'value', // 属性
event: 'input', // 事件
},
}
在 Vue 3 组合式 API 实现 v-model,需要定义 modelValue 参数,和 emits 方法。
这部分代码使用了defineEmits来定义组件可以发射的事件,其中包括update:modelValue事件。在onInput函数中,通过emits来触发update:modelValue事件,并传递了一个值val。
总的来说,这两种方式本质上是相同的,都是使用了Vue 3的Composition API 中的defineProps来定义props,以及defineEmits来定义组件可以发射的事件。然后通过onInput函数来触发update:modelValue事件,从而实现了双向绑定的功能。
所以,这段代码和之前给出的示例都是用于实现类似v-model的双向绑定功能,只是采用了稍微不同的写法。
defineProps({
modelValue: { type: String, default: '' },
})
const emits = defineEmits(['update:modelValue'])
function onInput(val) {
emits('update:modelValue', val)
}
Vue 3 对 diff 算法进行了哪些优化
在 Vue 2 中,每当数据发生变化时,Vue 会创建一个新的虚拟 DOM 树,并对整个虚拟 DOM 树进行递归比较,即使其中大部分内容是静态的,最后再找到不同的节点,然后进行更新。
Vue 3 引入了静态标记的概念,通过静态标记,Vue 3 可以将模板中的静态内容和动态内容区分开来。这样,在更新过程中,Vue 3 只会关注动态部分的比较,而对于静态内容,它将跳过比较的步骤,从而避免了不必要的比较,提高了性能和效率。
**vue2**中采用 **defineProperty**来劫持整个对象,然后进行深度遍历所有属性,给每个属性添加**getter**和**setter**,实现响应式
**vue3**采用**proxy**重写了响应式系统,因为**proxy**可以对整个对象进行监听,所以不需要深度遍历
- 可以监听动态属性的添加
- 可以监听到数组的索引和数组
**length**属性 - 可以监听删除属性
**Object.defineProperty** 与 **Proxy** 的优缺点
- defineProperty方式每次只能监控一个key,初始化时需要循环递归遍历obj中的所有key,速度慢,资源占用大,闭包
- defineProperty无法检测动态属性新增和删除
- defineProperty无法很好的支持数组,需要额外的数组响应式实现
- Vue2无法支持Collection类型:set、map
- Proxy不支持IE11及以下版本
Object.defineProperty只能遍历对象属性进行劫持
Proxy直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的
Vuex有几种属性,它们存在的意义分别是什么?
在 Vuex 中,主要包含以下几种属性,它们各自的存在意义如下:
-
state:
state是存储数据的地方,类似于组件中的 data。在整个应用中需要共享的数据可以存储在state中。
-
getters:
getters可以理解为 store 的计算属性,用于对state中的数据进行一些计算操作,并基于这些数据的变化来返回新的数据。它们类似于 Vue 组件中的计算属性。
-
mutations:
mutations是唯一能够修改state的地方,它们是同步函数,负责修改state中的数据。通过提交(commit)mutation 来改变 state 中的数据。
-
actions:
actions类似于mutations,但它们可以包含任意异步操作。actions通过提交 mutation 来间接修改state,可以用于处理异步逻辑、批量操作多个 mutation 等。
-
modules:
modules允许将 store 分割成模块,每个模块拥有自己的 state、getters、mutations、actions。这样可以更好地组织和管理大型应用的状态。
这些属性共同构成了 Vuex 的核心,通过严格的规则和流程来管理应用的状态,并使得状态的变化变得可预测和可追踪。通过合理地使用这些属性,我们能够更好地组织和管理应用的状态,从而提高代码的可维护性和可扩展性。
Vue2.0为什么不能检查数组的变化,该怎么解决?
因为 Vue 在初始化响应式数据时使用了 Object.defineProperty 或 Proxy 来劫持对象的属性访问,从而实现对对象属性的监听和追踪。但是对于数组来说,它的索引并不是一个具体的属性,所以 Vue 无法劫持数组的索引访问。
解决这个问题的方法有两种:
-
使用 Vue 提供的数组变异方法:
- Vue 提供了一些特殊的数组变异方法,如
push、pop、shift、unshift、splice、sort和reverse等。这些方法会触发数组的变化通知,从而使 Vue 能够感知到数组的变化。
- Vue 提供了一些特殊的数组变异方法,如
-
使用
$set方法:- 如果需要直接修改数组的元素,可以使用 Vue 提供的
$set方法来触发数组的变化通知。例如:this.$set(array, index, newValue)。
- 如果需要直接修改数组的元素,可以使用 Vue 提供的
为什么Vue中的v-if和v-for不建议一起用?
-
优先级冲突:
v-for比v-if具有更高的优先级。当v-for和v-if同时存在于同一个元素上时,Vue 首先会先执行v-for循环,然后再应用v-if条件判断。这意味着v-if条件会被应用到每个循环的元素上,而不是整个循环本身。这可能会导致不必要的循环操作,降低性能。
-
可读性差:
- 同时使用
v-if和v-for可能会使模板逻辑变得复杂,难以理解和维护。在模板中,我们通常希望保持逻辑的简洁和明确,这样可以提高代码的可读性和可维护性。
- 同时使用
针对这种情况,官方推荐的做法是将需要过滤的数据在计算属性或者方法中进行处理,然后使用单独的元素来应用 v-if 条件判断,而不是将 v-if 和 v-for 同时应用在同一个元素上。这样可以避免冲突和提高代码的可读性。
<template>
<div>
<div v-for="item in filteredItems" :key="item.id">
{{ item.name }}
</div>
</div>
</template>
<script>
export default {
data() {
return {
items: [...],
filterCondition: ...
};
},
computed: {
filteredItems() {
// 在计算属性中根据条件过滤数据
return this.items.filter(item => item.condition === this.filterCondition);
}
}
};
</script>
vue 中 router 有什么区别?
在 Vue.js 中,router 都是与路由相关的对象,但它们之间有以下区别:
-
route 是一个当前路由信息的对象,包括当前 URL 路径、查询参数、路径参数等信息。$route 对象是只读的,不可以直接修改其属性值,而需要通过路由跳转来更新。
-
router 是 Vue Router 的**实例对象,**包括了许多用于导航控制和路由操作的 API,例如 push、replace、go、forward 等方法。$router 可以用来动态地改变 URL,从而实现页面间的无刷新跳转。
因此,router 在功能上有所不同,router 则是用于进行路由操作,例如跳转到指定的路由、前进、后退等。通常来说,router 是紧密关联的,并且常常一起使用。
vue路由传参的基本方式
params、query是什么?
params:/router1/:id ,/router1/123,/router1/789 ,这里的id叫做params
通过name来匹配路由,通过param来传递参数
this.$router.push({
name:'Home',
params:{
id:id
}
})
用params传递参数,不使用:/id
{
path:'/home',
name:Home,
component:Home
}
Home组件中获取参数
this.$route.params.id
使用router的name属性也就是params来传递参数,params:参数不会显示到路径上,用params传参,这个方法有一个bug就是当你传参过去的时候,再次刷新页面时参数就会丢失;
**解决方法:**除了使用 Vuex 或者浏览器的本地存储来保存参数外,还可以使用以下方法来解决参数丢失的问题:
1. 使用 sessionStorage
-
可以使用
sessionStorage来保存参数,该参数会在当前会话期间保持有效,即使页面刷新也不会丢失。 -
示例代码:
javascriptCopy Code// 保存参数到 sessionStorage sessionStorage.setItem('params', JSON.stringify(params)); // 从 sessionStorage 中获取参数 const params = JSON.parse(sessionStorage.getItem('params'));
2. 在路由导航守卫中处理
-
可以在路由导航守卫中将参数保存到某个地方(如 Vuex、localStorage、sessionStorage),并在页面重新加载时再次从该地方获取参数。
-
示例代码:
javascriptCopy Coderouter.beforeEach((to, from, next) => { // 在这里处理参数的保存和获取逻辑 next(); });
3. 使用插件或工具库
- 有一些 Vue 插件或工具库专门用于处理路由状态的保存和恢复,可以考虑使用这些工具来简化参数丢失的处理逻辑。
以上是一些常见的方法,你可以根据具体需求选择合适的方式来解决参数丢失的问题。
query:/router1?id=123 ,/router1?id=456 ,这里的id叫做query。
**query:最好也用name来识别,保持与params一致性,好记了,路径传参,query:由于参数适用路径传参的所以F5强制刷新也不会被清空。(传参强烈建议适用string)
**
path+query;query传递的参数会通过?id = xxx展示
this.$router.push({
path:'/home',
query:{
id:id
}
})
路由配置
{
path:'/home',
name:Home,
component:Home
}
获取参数的方法
this.$route.query.id
Vue自定义指令是什么?
Vue自定义指令是Vue.js框架中的一项功能,允许你注册自定义指令并在DOM元素上应用其特殊行为。指令是带有v-前缀的特殊属性,用于对元素进行操作、绑定事件或执行其他自定义行为。
应用场景:
-
事件处理: 自定义指令可用于处理DOM元素的事件。例如,你可以创建一个自定义指令,使一个元素在插入文档时自动获得焦点:
<input v-focus> 注册全局自定义指令 `v-focus` Vue.directive('focus', { // 当被绑定的元素插入到 DOM 中时…… inserted: function (el) { // 聚焦元素 el.focus(); } }) -
条件渲染: 自定义指令可以根据条件决定是否渲染或操作元素。例如,你可以创建一个自定义指令,根据用户的权限控制元素的显示与隐藏。
<div v-show-auth="userHasPermission"></div> 注册全局自定义指令 `v-show-auth` Vue.directive('show-auth', { bind(el, binding) { if (!binding.value) { el.style.display = 'none'; } } }) -
表单验证: 自定义指令可用于创建表单验证逻辑。例如,你可以创建一个自定义指令,验证输入是否符合特定的规则。
<input v-validate="isEmail"> 注册全局自定义指令 `v-validate` Vue.directive('validate', { bind(el, binding) { el.addEventListener('input', function() { if (!binding.value(el.value)) { // 处理验证失败的逻辑 } }); } }) -
操作DOM: 自定义指令可用于创建表单验证逻辑。例如,你可以创建一个自定义指令,验证输入是否符合特定的规则。
<p v-highlight="'yellow'">This is a paragraph with custom highlight directive.</p> // 注册全局自定义指令 `v-highlight` Vue.directive('highlight', { // 当被绑定的元素插入到 DOM 中时…… bind(el, binding) { // 设置元素的背景颜色为指令的值 el.style.backgroundColor = binding.value; } });
全局自定义指令 v-highlight,该指令在元素被绑定到 DOM 时触发。它通过设置元素的背景颜色来实现“高亮”效果。在使用指令时,我们通过传递参数来指定高亮的颜色。
总的来说,Vue自定义指令提供了一种灵活的方式来扩展Vue.js的功能,使开发者能够更好地控制和定制DOM元素的行为。
Vue常用的修饰符有哪些?分别有什么应用场景
Vue 提供了一些常用的修饰符,用于在指令中增加特定的行为或功能。以下是 Vue 常用的修饰符及其应用场景:
-
.prevent:- 用于阻止默认事件的发生,可以在事件指令上使用(如
v-on)。常用于提交表单时防止页面刷新。
- 用于阻止默认事件的发生,可以在事件指令上使用(如
-
.stop:- 用于阻止事件冒泡,可以在事件指令上使用。常用于嵌套元素的事件处理中,防止事件冒泡到父元素。
-
.once:- 用于只触发一次事件处理程序,可以在事件指令上使用。常用于需要只执行一次的事件处理逻辑,例如点击按钮后只执行一次操作。
-
.capture:- 用于添加事件监听器时使用事件捕获模式,可以在事件指令上使用。常用于在父组件中捕获子组件触发的事件。
-
.self:- 用于只触发当前元素自身的事件,可以在事件指令上使用。常用于元素包含子元素时,只对当前元素的事件进行处理。
-
.passive:- 用于提升滚动性能,在绑定滚动事件时可以使用该修饰符。常用于监听滚动事件时,优化滚动的流畅度。
-
.sync:- 用于实现双向数据绑定,可以在自定义指令或组件的属性上使用。常用于父子组件之间的数据通信,使得父组件的数据可以响应子组件的变化。
这些修饰符可以根据具体的需求来选择使用,可以单独使用也可以组合使用。它们提供了一些额外的功能,使得事件处理、事件传递和双向数据绑定更加灵活和方便。根据不同的场景,选择合适的修饰符可以提高开发效率和用户体验。
根据每一个修饰符的功能,我们可以得到以下修饰符的应用场景:
- .stop:阻止事件冒泡
- .native:绑定原生事件
- .once:事件只执行一次
- .self :将事件绑定在自身身上,相当于阻止事件冒泡
- .prevent:阻止默认事件
- .capture:用于事件捕获
- .once:只触发一次
- .keyCode:监听特定键盘按下
- .right:右键
React 和 Vue 在技术层面有哪些区别?
eact 和 Vue 是当前比较流行的前端框架,它们在技术层面有以下区别:
-
组件化方式不同:React 是基于组件实现的,组件包含了状态和行为,所有组件共享一个状态树。Vue 也是基于组件实现的,但是每个组件都有自己的状态,并且可以很容易地将数据和行为绑定在一起。
-
数据驱动方式不同:React 使用单向数据流来管理数据,即从父组件到子组件的传递,所以 React 中组件之间的数据交互相对更加复杂。Vue 则使用双向数据绑定来管理数据,使得组件之间的数据交互更加简洁。
-
模板语法不同:React 使用 JSX 语法,将 HTML 和 JavaScript 结合在一起,使得编写组件更加直观和灵活。Vue 则使用模板语法,并且支持模板内的表达式和指令,使得编写组件具有更高的可读性和可维护性。
-
生命周期不同:React 组件的生命周期分为三个阶段:初始化、更新和卸载。Vue 组件的生命周期分为八个阶段:创建、挂载、更新、销毁等。
-
状态管理方式不同:React 使用 Redux 或者 MobX 来管理应用程序的状态。Vue 则提供了自己的状态管理库 Vuex,可以更方便地管理组件之间的共享状态。
-
性能优化方式不同:React 使用虚拟 DOM 技术来实现高效的渲染性能,可以减少每次渲染时需要操作真实 DOM 的次数。Vue 则使用模板编译和响应式系统来实现高效的渲染性能,并且还提供了一些优化技术,例如懒加载和缓存等
vue-router路由懒加载
vue 路由懒加载有以下三种方式:
- vue 异步组件
- ES6 的 import()
- webpack 的 require.ensure()
1. vue 异步组件 这种方法主要是使用了 resolve 的异步机制,用 require 代替了 import 实现按需加载
export default new Router({
routes: [
{
path: '/home',',
component: (resolve) => require(['@/components/home'], resolve),
},
{
path: '/about',',
component: (resolve) => require(['@/components/about'], resolve),
},
],
})
2. ES6 的 import() vue-router 在官网提供了一种方法,可以理解也是为通过 Promise 的 resolve 机制。因为 Promise 函数返回的 Promise 为 resolve 组件本身,而我们又可以使用 import 来导入组件。
export default new Router({
routes: [
{
path: '/home',
component: () => import('@/components/home'),
},
{
path: '/about',
component: () => import('@/components/home'),
},
],
})
1. webpack 的 require.ensure() 这种模式可以通过参数中的 webpackChunkName 将 js 分开打包。
export default new Router({
routes: [
{
path: '/home',
component: (resolve) => require.ensure([], () => resolve(require('@/components/home')), 'home'),
},
{
path: '/about',
component: (resolve) => require.ensure([], () => resolve(require('@/components/about')), 'about'),
},
],
})
history和hash模式的区别
需要根据实际需求和项目情况选择适合的模式。通常情况下,推荐使用 history 模式,但在一些特殊情况下,比如需要兼容老式浏览器或者不方便进行服务器配置时,可以选择使用 hash 模式。
vue导航守卫
Vue 导航守卫是用于控制路由跳转的过程中执行特定逻辑的钩子函数。它可以在路由切换之前、之后以及在路由更新时触发相应的回调函数。
Vue Router 提供了全局导航守卫和路由独享的导航守卫两种类型。
-
全局导航守卫:
beforeEach(to, from, next):在路由切换之前触发,可以用来进行全局的权限验证或者其他逻辑处理。afterEach(to, from):在路由切换之后触发,可以用来进行页面的统计或者其他收尾工作。beforeResolve(to, from, next):在与当前路由匹配的所有组件内守卫和异步路由组件被解析之后触发。
-
路由独享的导航守卫:
beforeEnter(to, from, next):在进入某个特定路由之前触发,可以用来对该路由进行单独的权限验证或者其他处理。
-
组件内的导航守卫:
beforeRouteEnter(to, from, next):在进入当前路由之前触发,但是无法直接访问实例的this,可以使用回调函数来获取组件实例。beforeRouteUpdate(to, from, next):在当前路由复用组件时触发,可以对组件进行更新处理。beforeRouteLeave(to, from, next):在离开当前路由之前触发,可以用来进行离开前的确认提示或者其他处理。
这些导航守卫函数接收三个参数:to(即将进入的目标路由对象)、from(当前导航正要离开的路由对象)和 next(调用该方法后才能进入下一个钩子)。
需要注意的是,在导航守卫中使用 next 方法来控制路由跳转:
- 调用
next()直接进行下一个导航守卫。 - 调用
next(false)中断当前导航。 - 调用
next('/path')或者next({ path: '/path' })跳转到一个不同的路径。
通过使用导航守卫,我们可以对路由跳转过程中的各个阶段进行控制和处理,实现诸如权限验证、登录判断、动态路由加载等功能。
Vue中keep-alive的理解
在 Vue 中,<keep-alive> 是一个抽象组件,用于缓存动态组件的状态,以便在组件切换时保留这些状态而不重新渲染和销毁。这对于需要频繁切换显示/隐藏的组件或页面来说是非常有用的,可以提高性能并改善用户体验。
当包裹动态组件的 <keep-alive> 被激活时,它会缓存这些动态组件的实例,而不是销毁它们。当组件在 <keep-alive> 内被切换时,它们的状态将被保留,包括数据、DOM 状态以及可能存在的一些副作用。
以下是我对 <keep-alive> 的一些理解:
-
缓存动态组件:
<keep-alive>可以缓存动态组件的实例,避免在组件切换时反复创建和销毁,从而提高性能。 -
包裹条件渲染组件:
<keep-alive>通常用于包裹条件渲染的组件,例如通过v-if控制显示隐藏的组件,这样可以在组件显示时缓存其状态,在隐藏时保留状态而不进行销毁。 -
钩子函数:
<keep-alive>提供了两个钩子函数,分别是activated和deactivated,可以在组件被激活和停用时执行相应的逻辑操作,比如数据更新或者重新加载。 -
动态组件和路由视图:
<keep-alive>通常与动态组件和路由视图结合使用,可以实现页面切换时的缓存和状态保留,提升用户体验。 -
注意事项:虽然
<keep-alive>可以提高性能,但需要注意不要滥用,过多的缓存可能会导致内存占用过高,因此需要根据实际情况慎重考虑使用。
总的来说,<keep-alive> 是 Vue 中一个非常实用的功能,可以帮助我们优化页面性能,提升用户体验,但需要根据具体场景和需求来合理地应用和配置。
当你使用 <keep-alive> 包裹动态组件时,可以通过 **include** 和 **exclude** 属性来指定哪些组件需要被缓存,以及哪些组件不需要被缓存。下面是一个简单的示例,演示了如何在 Vue 中使用 <keep-alive>:
<template>
<div>
<button @click="toggleComponent">Toggle Component</button>
<keep-alive>
<component :is="currentComponent"></component>
</keep-alive>
</div>
</template>
<script>
import FirstComponent from './FirstComponent.vue';
import SecondComponent from './SecondComponent.vue';
export default {
data() {
return {
currentComponent: 'FirstComponent',
};
},
components: {
FirstComponent,
SecondComponent,
},
methods: {
toggleComponent() {
this.currentComponent = this.currentComponent === 'FirstComponent' ? 'SecondComponent' : 'FirstComponent';
},
},
};
</script>
在这个示例中,我们有两个组件 FirstComponent 和 SecondComponent,通过一个按钮来切换它们的显示状态。这两个组件被动态地渲染在 <keep-alive> 内,从而实现了缓存的效果。
如果需要对特定的组件进行缓存或排除缓存,你可以使用 <keep-alive> 的 include 和 exclude 属性。例如,如果你想要只缓存 FirstComponent 组件,可以这样做:
<keep-alive :include="['FirstComponent']">
<component :is="currentComponent"></component>
</keep-alive>
这样,只有 FirstComponent 被渲染时才会被缓存,而 SecondComponent 不会被缓存。
希望这个示例能够帮助你更好地理解如何在 Vue 中使用 <keep-alive> 来实现组件的缓存功能。
Vue中的$nextTick有什么作用
在 Vue 中,$nextTick 是一个实例方法,用于在 DOM 更新之后执行一段代码。它的主要作用是在当前的 JavaScript 执行结束时,等待 Vue 完成 DOM 更新之后再执行指定的回调函数或代码。
具体来说,$nextTick 主要用于以下几个方面:
-
异步更新DOM后执行代码:当你需要在 Vue 更新 DOM 后执行一些操作(比如访问更新后的 DOM 元素或计算更新后的样式)时,可以将这些操作包裹在
$nextTick的回调函数中。这样可以确保操作发生在下一次 DOM 更新之后,避免出现不一致的情况。 -
避免数据更新延迟导致的问题:有时候在修改了 Vue 实例的数据后,立即访问更新后的 DOM 元素可能会遇到问题,因为 Vue 是异步更新 DOM 的。这时可以使用
$nextTick来确保数据更新后立即进行操作。 -
在 Vue 生命周期钩子中使用:在 Vue 的生命周期钩子中,有时候需要等待异步更新后执行一些逻辑,此时也可以使用
$nextTick来确保在更新后执行逻辑。
下面是一个简单的示例,演示了如何在 Vue 中使用 $nextTick:
<template>
<div>
<p ref="message">{{ message }}</p>
<button @click="updateMessage">Update Message</button>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello, Vue!',
};
},
methods: {
updateMessage() {
this.message = 'Updated message';
this.$nextTick(() => {
// 在 DOM 更新之后操作 DOM 元素
console.log(this.$refs.message.textContent); // 输出更新后的文本内容
});
},
},
};
</script>
在这个示例中,当点击按钮时会更新 message 的值,并且通过 $nextTick 确保在 DOM 更新之后打印出更新后的文本内容。这样就避免了在数据更新后立即获取文本内容时可能出现的问题。
总的来说,$nextTick 在 Vue 中的作用是确保在 DOM 更新之后执行指定的回调函数或代码,从而避免因为异步更新引起的问题,并且能够在更新后进行相应的操作。
插槽slot有几种方式
在 Vue 中,插槽(slot)用于在组件中插入内容。Vue 提供了多种方式来使用插槽,包括以下几种常见的方式:
-
默认插槽(Default Slot):
-
在组件模板中使用
<slot></slot>标签即可定义默认插槽。 -
在组件使用时,放置在组件标签内的内容将会替换掉
<slot></slot>的位置。 -
示例代码:
<!-- 组件模板 --> <div> <slot></slot> </div> <!-- 组件使用 --> <custom-component> <p>This content will replace the default slot</p> </custom-component>
-
-
具名插槽(Named Slot):
-
可以为插槽定义名称,以便在组件中使用多个插槽。
-
在组件模板中使用
<slot name="slotName"></slot>标签定义具名插槽。 -
在组件使用时,可以使用
<template v-slot:slotName></template>或者<template #slotName></template>来指定具名插槽的位置。 -
示例代码:
<!-- 组件模板 --> <div> <slot name="header"></slot> <slot name="content"></slot> </div> <!-- 组件使用 --> <custom-component> <template v-slot:header> <h1>Header Content</h1> </template> <template v-slot:content> <p>Content goes here</p> </template> </custom-component>
-
-
作用域插槽(Scoped Slot):
-
作用域插槽允许在插槽中访问组件作用域的数据。
-
在组件模板中使用
<slot name="slotName" :data="componentData"></slot>标签定义作用域插槽,并通过:data属性传递数据给插槽。 -
在组件使用时,可以通过
<template v-slot:slotName="slotProps"></template>或者<template #slotName="slotProps"></template>来接收作用域插槽的数据,并在模板中使用。 -
示例代码:
<!-- 组件模板 --> <div> <slot name="item" v-for="item in items" :item="item"></slot> </div> <!-- 组件使用 --> <custom-component> <template v-slot:item="slotProps"> <p>{{ slotProps.item }}</p> </template> </custom-component>
-
vue 什么是混合和混入,有什么区别
在 Vue 中,混合(mixin)和混入(mixin)是用于组件复用的概念,它们可以帮助开发者在多个组件中共享相同的逻辑、选项或功能。虽然它们的名称相似,但它们在 Vue 中有一些区别。
混合(Mixins)
混合(Mixins)是一种将一组选项对象合并到组件中的技术。当组件使用混合时,混合对象中的选项会被合并到组件自身的选项中,从而实现了代码复用。
-
示例代码:
Code// 定义一个混合对象 const myMixin = { created() { console.log('Mixin created hook'); }, methods: { greet() { console.log('Hello from mixin!'); } } }; // 在组件中使用混合 Vue.component('my-component', { mixins: [myMixin], created() { console.log('Component created hook'); } });
混入(Mixins)
混入(Mixins)是指将一个混入对象应用到多个组件中的过程。通过在多个组件中引入同一个混入对象,可以实现在多个组件中共享相同的逻辑、方法等。
-
示例代码:
Code// 定义一个混入对象 const myMixin = { created() { console.log('Mixin created hook'); }, methods: { greet() { console.log('Hello from mixin!'); } } }; // 在多个组件中引入相同的混入对象 Vue.mixin(myMixin);
区别
区别在于使用方式和影响范围:
- 混合(Mixins) 是将一组选项对象合并到组件中,因此它是针对单个组件的。
- 混入(Mixins) 则是将一个混入对象应用到多个组件中,可以在全局范围内共享。
总的来说,混合和混入都是用于实现组件复用的方式,但是混合更偏向于局部范围的复用,而混入则更倾向于全局范围的复用。根据实际需求,可以灵活选择使用混合或混入来实现组件间的功能共享。