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 源码(二)虚拟 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 性能优化

  1. 路由懒加载,使用时才加载
  1. const router = new VueRouter({
  2. path: '/',
  3. component: () => import('./Home.vue'),
  4. name: 'home'
  5. });
  1. keep-alive 缓存页面
  1. <keep-alive>
  2. <component v-bind:is="currentTabComponent"></component>
  3. </keep-alive>
  1. 使用 v-show 复用 DOM
  1. <template>
  2. <div class="cell">
  3. <div class="on">
  4. <Heavy v-show="value" :n="10000" />
  5. </div>
  6. <section v-show="!value" class="off">
  7. <Heavy :n="10000" />
  8. </section>
  9. </div>
  10. </template>
  1. v-for 遍历避免同时使用 v-if
  2. 长列表性能优化

5.1 存储的数据展示,不会有任何变化,就不需要做响应化

  1. <script>
  2. export default {
  3. data: () => {
  4. return ({
  5. users: []
  6. })
  7. },
  8. async created() {
  9. const users = await axios.get('/api/users')
  10. this.users = Object.freeze(users)
  11. }
  12. }
  13. </script>

5.2 如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容

  1. 定时器销毁,手动销毁
  2. 图片懒加载
  3. 第三方插件按需加载
  4. 无状态组件标记为函数式组件
  1. <template functional>
  2. <div class="cell">
  3. <div v-if="pops.value" class="on" />
  4. <section v-else class="off" />
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. props: ['value']
  10. }
  11. </script>
  1. 子组件分割
  1. <template>
  2. <div>
  3. <childcomp />
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. components: {
  9. chlidcomp: {
  10. methods: {
  11. heavy() {
  12. // 耗时任务
  13. },
  14. render(h) {
  15. return h('div', this.heavy())
  16. }
  17. }
  18. }
  19. }
  20. }
  21. </script>
  1. 变量本地化
  2. SSR

Vue.js 3 新特征

  • 更快
    • 虚拟 DOM 重写
    • 优化 slots 的生成
    • 静态树提升
    • 静态属性提升
    • 基于 Proxy 的响应式系统
  • 更小:通过摇树优化核心库体积
  • 更易于维护:TypeScript + 模块化
  • 更加友好
    • 跨平台:编译器核心和运行时与平台无关,使得 Vue.js 容易与任何其他平台使用
  • 更易于使用
    • 改进的 TypeScript 支持,编译器能够提供有力的类型检查和错误以及警告
    • 更好的调试支持
    • 独立的响应式模块
    • Composition API

虚拟 DOM 重写

image.png

image.png

响应式数据的原理

学习 Vue.js 源码(一)变化侦测

Vue.js 如何检测数组变化

因为 JavaScript 的限制,Vue.js 不能检测到下面数组变化:

  • 直接用索引设置元素,如 vm.items[0] = {}
  • 修改数据的长度,如 vm.items.length = 0

可以使用如下两种方式来改变数据。

  1. Vue.set(this.arr,0,{name: '小明'})
  2. 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 无法获取到原值,只能得到变化后的值

    1. <script>
    2. import {reactive, watch} from 'vue'
    3. export default {
    4. setup() {
    5. const state = reactive({count: 0})
    6. watch(() => state.count, (newValue, oldValue) => {
    7. console.log(`原值为${oldValue}`)
    8. console.log(`新值为${newValue}`)
    9. /* 1秒后打印结果:
    10. 原值为0
    11. 新值为1
    12. */
    13. })
    14. // 1秒后将state.count的值+1
    15. setTimeout(() => {
    16. state.count ++
    17. }, 1000)
    18. }
    19. }
    20. </script>
    1. <script>
    2. import {reactive, watchEffect} from 'vue'
    3. export default {
    4. setup() {
    5. const state = reactive({ count: 0, name: 'zs' })
    6. watchEffect(() => {
    7. console.log(state.count)
    8. console.log(state.name)
    9. /* 初始化时打印:
    10. 0
    11. zs
    12. 1秒后打印:
    13. 1
    14. ls
    15. */
    16. })
    17. setTimeout(() => {
    18. state.count ++
    19. state.name = 'ls'
    20. }, 1000)
    21. }
    22. }
    23. </script>

    ref toRefs reactive 区别

  • toRefs:将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref

  • ref:接受一个内部值并返回一个响应式且可变的 ref 对象。响应式转换是“浅”的。ref 对象具有指向内部值的单个 property .value
  • reactive:返回对象的响应式副本。用来定义复杂类型响应式数据,响应式转换是“深”的——它影响所有嵌套 property。在基于 ES2015 Proxy 的实现中,返回的代理是不等于原始对象。建议只使用响应式代理,避免依赖原始对象。

    1. <script>
    2. const count = ref<number>(0)
    3. console.log(count.value) // 0
    4. count.value++
    5. console.log(count.value) // 1
    6. const state = reactive({
    7. foo: 1,
    8. bar: 2
    9. })
    10. const stateAsRefs = toRefs(state)
    11. /*
    12. Type of stateAsRefs:
    13. {
    14. foo: Ref<number>,
    15. bar: Ref<number>
    16. }
    17. */
    18. // ref 和 原始property “链接”
    19. state.foo++
    20. console.log(stateAsRefs.foo.value) // 2
    21. stateAsRefs.foo.value++
    22. console.log(state.foo) // 3
    23. </script>

    mixins

    优点:可以很容易的将其它功能混入到现有的模块中
    缺点:很难搞清楚这些功能是在哪个文件中

    参考

【1】用vue想拿20k,面试题要这样答(B站视频)
【2】哈默聊前端 Vue.js 面试题
【3】你知道nextTick的原理吗?
【4】化身面试官出30+Vue面试题,超级干货(附答案)|牛气冲天新年征文