下面是当简历项目为vue2 时面试遇到过得问题
为了确保每个组件实例都有独立的、隔离的数据作用域;
假如data是对象,所有组件实例会共享同一个数据对象,如果某个组件实例中修改了这个对象会影响其他的组件实例。
而写成函数的形式,每次创建一个新的组件实例,这个函数都会被调用从而返回一个新的独立的数据对象,每个组件实例都有自己的数据对象,互不干扰。
export default {
data() {
return {
message: 'Hello, Vue!'
};
}
在vue中,我们变更了数据DOM也会更新,但不是同步生效的,vue会记录所有的更改并在下一次事件循环中批量更新DOM,以避免不必要的DOM更新。但在开发中,我们可能需要数据变更后等DOM 也变更后再执行某种操作。nextTick 方法允许你延迟执行代码,直到 Vue 完成下一个 DOM 更新周期。在该方法的回调中我们可以访问到更新后的DOM
import { nextTick } from 'vue'
function increment() {
state.count++
nextTick(() => {
// 访问更新后的 DOM
})
}
通常使用情况:
1、需要计算DOM最新的大小或位置
2、确保元素有了样式后进行过渡或动画
3、确保DOM数据展示是最新的后再执行某种操作
模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。使用计算属性来描述依赖响应式状态的复杂逻辑,实现逻辑和结构的分离,同时可以实现复用;计算属性有getter 和 setter ,返回值为一个新的派生数据,计算属性会自动追踪响应式依赖,当依赖变化时重新计算;
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
<template>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
</template>
概念上的区别,computed 是计算属性,返回一个派生的计算结果;watch 是监听器,可以监听指定数据源并监听到变化后触发指定的回调函数;
不同点:
1、computed 支持缓存,只有依赖数据发生改变,才会重新进行计算; watch不支持缓存,数据变,直接会触发相应的操作;
2、computed第一次加载时就监听;watch默认第一次加载时不监听(需要设置immediate:true)
3、不支持异步,当computed内有异步操作时无效,无法监听数据的变化;watch支持异步;
4、computed 有getter 和 setter 方法,watch 可一设置deep:true, immediate:true,flush: ‘post’
默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。这意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态。
如果想在侦听器回调中能访问被 Vue 更新之后的 DOM,你需要指明 flush: ‘post’ 选项:
watch(source, callback, {
flush: 'post'
})
watchEffect(callback, {
flush: 'post'
})
1、回调会立即执行,不需要指定 immediate: true
2、不再需要明确传递数据源,会自动追踪依赖项
以下是代码示例:
const todoId = ref(1)
const data = ref(null)
watch(
todoId,
async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
},
{ immediate: true }
)
watchEffect(async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
})
1.计算属性是一个属性 必须要有返回值 methods不一定
2.计算属性在页面渲染时 不需要加括号 methods必须要加
3.计算属性有缓存,一个计算属性仅会在其响应式依赖更新时才重新计算。 methods没有缓存 从性能上来讲 计算属性更具有优势
v-if 是 真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
v-show 就简单得多, 不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 display 属性进行切换。
所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。
在vue2中,v-for的优先级是高于v-if的,如果同时出现,每次渲染都会先执行循环再判断条件,无论如何循环都不可避免,浪费了性能;另外需要注意的是在vue3则完全相反,v-if的优先级高于v-for,所以v-if执行时,它调用的变量还不存在,就会导致异常。
解决办法:
1、将原来使用v-if判断的项目使用计算属性过滤,只要遍历过滤后的列表
2、在外层包裹template,在外层循环,内层判断
<!--
这会抛出一个错误,因为属性 todo 此时
没有在该实例上定义
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
</li>
在外新包装一层 <template> 再在其上使用 v-for 可以解决这个问题 (这也更加明显易读):
template
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
Vue 默认按照“就地更新”的策略来更新通过 v-for 渲染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染。为了给 Vue 一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素,你需要为每个元素对应的块提供一个唯一的 key attribute。
.once - 只触发一次回调。
.prevent - 调用 event.preventDefault()阻止默认行为
.stop - 调用 event.stopPropagation()阻止冒泡
.self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
.capture - 添加事件侦听器时使用 capture 模式,默认情况下是事件冒泡, 如果想变成事件捕获, 那么就需要使用.capture修饰符
1、v-if:根据表达式的值的真假条件渲染元素。在切换时元素及它的数据绑定 / 组件被销毁并重建。
2、v-show:根据表达式之真假值,切换元素的 display CSS 属性。
3、v-for:循环指令,基于一个数组或者对象渲染一个列表,vue 2.0以上必须需配合 key值 使用。
4、v-bind:动态地绑定一个或多个特性,或一个组件 prop 到表达式。
5、v-on:用于监听指定元素的DOM事件,比如点击事件。绑定事件监听器。
6、v-model:实现表单输入和应用状态之间的双向绑定
7、v-pre:跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。
8、v-once:只渲染元素和组件一次。随后的重新渲染,元3 素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能
keep-alive 组件是 vue 的内置组件,用于缓存内部组件实例。这样做的目的在于,keep-alive 内部的组件切回时,不用重新创建组件实例,而直接使用缓存中的实例,一方面能够避免创建组件带来的开销,另一方面可以保留组件的状态。
keep-alive 具有 include 和 exclude 属性,通过它们可以控制哪些组件进入缓存。另外它还提供了 max 属性,通过它可以设置最大缓存数,当缓存的实例超过该数时,vue 会移除最久没有使用的组件缓存
受keep-alive的影响,其内部所有嵌套的组件都具有两个生命周期钩子函数,分别是 activated 和 deactivated,它们分别在组件激活和失活时触发。第一次 activated 触发是在 mounted 之后
<script setup>
import { onActivated, onDeactivated } from 'vue'
onActivated(() => {
// 调用时机为首次挂载
// 以及每次从缓存中被重新插入时
})
onDeactivated(() => {
// 在从 DOM 上移除、进入缓存
// 以及组件卸载时调用
})
</script>
keep-alive的实现原理是什么?
// keep-alive 内部的声明周期函数
created () {
this.cache = Object.create(null)
this.keys = []
}
key 数组记录目前缓存的组件 key 值,如果组件没有指定 key 值,则会为其自动生成一个唯一的 key 值
cache 对象以 key 值为键,vnode 为值,用于缓存组件对应的虚拟 DOM
在 keep-alive 的渲染函数中,其基本逻辑是判断当前渲染的 vnode 是否有对应的缓存,如果有,从缓存中读取到对应的组件实例;如果没有则将其缓存。
当缓存数量超过 max 数值时,keep-alive 会移除掉 key 数组的第一个元素。
1、前端路由通常基于URL路径来映射到特定的组件或页面。后端路由根据客户端发送的请求路径和HTTP方法来决定执行哪些逻辑处理。
2、前端路由切换页面时通常不会导致网页的刷新,保持浏览器的历史记录。后端路由可能导致网页的刷新,因为可能需要重新加载整个页面。
3、前端路由可以在不向服务器发送请求的情况下实现页面间的导航。后端路由涉及到与服务器的通信,以确定返回的内容类型(如HTML、JSON等)。
4、前端路由采用的是客户端渲染,后端路由是服务端渲染。
1、全局组件注册在全局,应用中的任意模块都可以使用;局部组件在vue2中需要再使用的组件中注册,vue3中在 的单文件组件中直接导入即可使用无需注册。但是局部组件仅在导入的组件中可以使用;
2、全局组件没被使用过的组件在生产打包中不会被自动移除(tree-shaking);
3、全局组件在大型项目中会让依赖关系变得不明确,如果使用过多不利于维护,局部组件显示导入,依赖关系明确。
所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
导致你想要更改一个 prop 的需求通常来源于以下两种场景:
prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。在这种情况下,最好是新定义一个局部数据属性,从 props 上获取初始值即可:
export default {
props: ['initialCounter'],
data() {
return {
// 计数器只是将 this.initialCounter 作为初始值
// 像下面这样做就使 prop 和后续更新无关了
counter: this.initialCounter
}
}
}
需要对传入的 prop 值做进一步的转换。在这种情况中,最好是基于该 prop 值定义一个计算属性:
export default {
props: ['size'],
computed: {
// 该 prop 变更时计算属性也会自动更新
normalizedSize() {
return this.size.trim().toLowerCase()
}
}
}
透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。最常见的例子就是 class、style 和 id
在vue2中,可以通过 $attrs 这个实例属性来访问组件的所有透传 attribute
export default {
created() {
console.log(this.$attrs)
}
}
在vue3中,你可以在 中使用 useAttrs() API 来访问一个组件的所有透传 attribute:
<script setup>
import { useAttrs } from 'vue'
const attrs = useAttrs()
</script>
如果没有使用