- v-if 和 v-for 优先级谁的优先级高?如何避免性能问题
- Vue.js 组件的 data 为什么必须是一个函数而 Vue.js 的根实例没有此限制?
- Vue.js 中 key 的作用和工作原理?说说你对它的理解
- 你怎么理解 Vue.js 中的 diff 算法
- Vue.js 组件化的理解
- Vue.js 设计理念
- Vue.js 性能优化
- Vue.js 3 新特征
- 响应式数据的原理
- Vue.js 如何检测数组变化
- nextTick 实现原理
- Vue.js 中 computed、methods 和 watch 的区别
- watch 中的 deep:true 如何实现 todo
- Vue.js 生命周期 todo
- watchEffect 与 watch 的区别
- ref toRefs reactive 区别
- mixins
- 参考
v-if 和 v-for 优先级谁的优先级高?如何避免性能问题
v-for 优先于 v-if 被解析。如果它们同时出现,任何一个节点都会先执行循环再判断条件,无论如何循环都不可避免,浪费了性能。要避免这种情况,在外层嵌套 template,在外层进行 v-if 判断,然后在内部进行 v-for 循环。
Vue.js 组件的 data 为什么必须是一个函数而 Vue.js 的根实例没有此限制?
如果 data 是一个函数则使用函数执行后的返回值作为 vm.$data,否则直接将 data 作为 vm.$data。Vue.js 的组件可能有多个实例,每个实例的数据不能使用相同的data,所以组件的 data 必须是一个函数。根实例只会有一个,所有它没有限制。
Vue.js 中 key 的作用和工作原理?说说你对它的理解
- key 的作用是为了高效的更新虚拟DOM ,其原理是 vue.js 在 patch 过程中通过判断两个节点是否是同一个,从而避免频繁更新不同的元素,使得整个 patch 过程更加高效,减少 DOM 操作量,提高性能。
- 另外,如果不设置 key 在更新时可能引发一些隐蔽的 BUG。
- Vue.js 中在使用相同标签名元素的过渡切换时,需要用 key 区分它们,否则只会替换内部属性不会引发过渡效果。
你怎么理解 Vue.js 中的 diff 算法
- Vue 2.x 选择了中粒度的解决方案,引入了虚拟 DOM。组件级别是一个 watcher 实例,就是说即便一个组件内有 10 个节点使用了某个状态,但其实只有一个 watcher 在观察这个状态的变化。当这个状态发生变化时,只能通知到组件,然后组件内部通过虚拟 DOM 去对比与渲染。
- 为了避免不必要的 DOM 操作,虚拟 DOM 在虚拟节点映射到视图的过程中,将虚拟节点与上一次渲染视图所使用的旧虚拟节点做对比,找出真正需要更新的节点来进行 DOM 操作,从而避免操作其他无任何改动的 DOM。
Vue.js 组件化的理解
- 将重复的功能形成独立的代码块,使代码的复用性、可维护性、测试性更强。
- 组件分类:页面组件、业务组件、通用组件。
vue-loader
会将template
编译为render
函数,最终输出的是组件的配置对象。- Vue.js 常用的组件化技术:属性 prop,自定义事件,插槽等,它们主要用于组件通信、拓展等。
- 合理的组件划分,有助于提供应用性能。
- 组件应该是高内聚、低耦合的。
- 遵循单向数据流。
Vue.js 设计理念
- 渐进式 JavaScript 框架:渐进式的含义是你可以在已经建立的项目中使用它,选择性的使用其周边库和方式(例如单文件、vue-router、vuex)。它的核心是 vue.js 只关注视图层,能做到外链即用,其它部分由其它库和工具链负责。
- 易用性:Vue.js 提供响应式、声明式模板语法和基于配置的组件系统等核心特性,使我们只要关心业务即可,不需要再操作 DOM、挂载事件。
- 灵活性:如果我们的应用足够的小可以只使用 Vue.js 核心库,根据应用规模的不断扩大加入路由、状态管理、Vue-CLI 等库和工具。
- 高效性:中密度的数据侦测和更快的 diff 算法,更少的代码。Vue.js3 中引入的 Proxy 对数据响应式的改进以及编辑器中对静态内容的改进都会让 Vue.js 更加高效。
Vue.js 性能优化
- 路由懒加载,使用时才加载
const router = new VueRouter({
path: '/',
component: () => import('./Home.vue'),
name: 'home'
});
keep-alive
缓存页面
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
- 使用
v-show
复用DOM
<template>
<div class="cell">
<div class="on">
<Heavy v-show="value" :n="10000" />
</div>
<section v-show="!value" class="off">
<Heavy :n="10000" />
</section>
</div>
</template>
- v-for 遍历避免同时使用 v-if
- 长列表性能优化
5.1 存储的数据展示,不会有任何变化,就不需要做响应化
<script>
export default {
data: () => {
return ({
users: []
})
},
async created() {
const users = await axios.get('/api/users')
this.users = Object.freeze(users)
}
}
</script>
5.2 如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容
- 定时器销毁,手动销毁
- 图片懒加载
- 第三方插件按需加载
- 无状态组件标记为函数式组件
<template functional>
<div class="cell">
<div v-if="pops.value" class="on" />
<section v-else class="off" />
</div>
</template>
<script>
export default {
props: ['value']
}
</script>
- 子组件分割
<template>
<div>
<childcomp />
</div>
</template>
<script>
export default {
components: {
chlidcomp: {
methods: {
heavy() {
// 耗时任务
},
render(h) {
return h('div', this.heavy())
}
}
}
}
}
</script>
- 变量本地化
- SSR
Vue.js 3 新特征
- 更快
- 虚拟 DOM 重写
- 优化 slots 的生成
- 静态树提升
- 静态属性提升
- 基于 Proxy 的响应式系统
- 更小:通过摇树优化核心库体积
- 更易于维护:TypeScript + 模块化
- 更加友好
- 跨平台:编译器核心和运行时与平台无关,使得 Vue.js 容易与任何其他平台使用
- 更易于使用
- 改进的 TypeScript 支持,编译器能够提供有力的类型检查和错误以及警告
- 更好的调试支持
- 独立的响应式模块
- Composition API
虚拟 DOM 重写
响应式数据的原理
Vue.js 如何检测数组变化
因为 JavaScript 的限制,Vue.js 不能检测到下面数组变化:
- 直接用索引设置元素,如
vm.items[0] = {}
- 修改数据的长度,如
vm.items.length = 0
可以使用如下两种方式来改变数据。
Vue.set(this.arr,0,{name: '小明'})
this.arr.splice(0,1,{name: '小明'})
Object.defineProperty()
只能对属性进行数据劫持,不能对整个对象进行劫持,同理无法对数组进行劫持。Vue Vue.js 通过遍历数组和递归遍历对象,从而达到利用 Object.defineProperty()
也能对对象和数组(部分方法的操作)进行监听。
nextTick 实现原理
Vue.js 中 DOM 的更新是异步执行的,只要数据发生变化,将会开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。
简单来说,就是当数据发生变化时,视图不会立即更新,而是等到同一事件循环中所有数据变化完成之后,再统一更新视图。
vue 自己维护了一个任务队列去配合宏微任务使用,其目的是:
- 减少宏微任务的注册。尽量把所有异步代码放在一个任务循环完成,减少消耗。
- 加快异步代码的执行。我们知道,如果一个异步代码就注册一个宏微任务的话,那么执行完全部异步代码肯定慢很多。
- 避免频繁地更新。Vue.js 中就算我们一次性修改多次数据,页面还是只会更新一次。就是因为这样,避免多次修改数据导致的多次频繁更新页面,让多次修改只用更新最后一次。
Vue.js 中 computed、methods 和 watch 的区别
omputed 属性和 methods 区别
computed
是响应式的,methods
并非响应式。- 调用方式不一样,
computed
定义的成员像属性一样访问,methods
定义的成员必须以函数形式调用。 computed
是带缓存的,只有其引用的响应式属性发生改变时才会重新计算,而methods
里的函数在每次调用时都要执行。computed
中的成员可以只定义一个函数作为只读属性,也可以定义get/set
变成可读写属性,这点是methods
中的成员做不到的。
比如:我们想去时刻监控数据的变化,在视图上显示不同的结果,当然这两中方法都可以实现这种效果,这个时候用computed 就比较合理了,因为 computed 是可缓存的,只要数据层值不改变,computed 就不会去改变,而且可缓存,如果数据层的值变了,computed 就会实时更新到视图层上,所以说 computed 是响应式的。
computed 属性和 watch 区别
- computed 里属性名是自定义的,它可以监听一个或多个它所依赖的数据项;而 watch 一次只能监听一个属性,这个属性函数接收两个参数,一个是新值一个是旧值。
- computed 里自定义的属性不能与 data 里的属性重复,否则会报错;而 watch 里监听的属性必须是已存在的,其要么是 data 里的属性,要么是 computed 里计算出来的属性。
- watch 是允许异步操作的(访问一个 API),并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch
选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
watch 中的 deep:true 如何实现 todo
Vue.js 生命周期 todo
watchEffect 与 watch 的区别
- watchEffect 不需要手动传入依赖
- watchEffect每次初始化时会执行一次回调函数来自动获取依赖
watchEffect 无法获取到原值,只能得到变化后的值
<script>
import {reactive, watch} from 'vue'
export default {
setup() {
const state = reactive({count: 0})
watch(() => state.count, (newValue, oldValue) => {
console.log(`原值为${oldValue}`)
console.log(`新值为${newValue}`)
/* 1秒后打印结果:
原值为0
新值为1
*/
})
// 1秒后将state.count的值+1
setTimeout(() => {
state.count ++
}, 1000)
}
}
</script>
<script>
import {reactive, watchEffect} from 'vue'
export default {
setup() {
const state = reactive({ count: 0, name: 'zs' })
watchEffect(() => {
console.log(state.count)
console.log(state.name)
/* 初始化时打印:
0
zs
1秒后打印:
1
ls
*/
})
setTimeout(() => {
state.count ++
state.name = 'ls'
}, 1000)
}
}
</script>
ref toRefs reactive 区别
toRefs
:将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的ref
。ref
:接受一个内部值并返回一个响应式且可变的 ref 对象。响应式转换是“浅”的。ref 对象具有指向内部值的单个 property.value
。reactive
:返回对象的响应式副本。用来定义复杂类型响应式数据,响应式转换是“深”的——它影响所有嵌套 property。在基于 ES2015 Proxy 的实现中,返回的代理是不等于原始对象。建议只使用响应式代理,避免依赖原始对象。<script>
const count = ref<number>(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
/*
Type of stateAsRefs:
{
foo: Ref<number>,
bar: Ref<number>
}
*/
// ref 和 原始property “链接”
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
</script>
mixins
优点:可以很容易的将其它功能混入到现有的模块中
缺点:很难搞清楚这些功能是在哪个文件中参考
【1】用vue想拿20k,面试题要这样答(B站视频)
【2】哈默聊前端 Vue.js 面试题
【3】你知道nextTick的原理吗?
【4】化身面试官出30+Vue面试题,超级干货(附答案)|牛气冲天新年征文