vue原理面试题

577 阅读5分钟

vue

1、什么是虚拟DOM?

  • 虚拟dom是react框架率先提出并使用的,后来在vue2.0中也引入了虚拟dom的概念。
  • 简单来说,虚拟dom就是js对象来描述真实的dom结构
  • 在虚拟dom出现之前,我们通过直接操作dom来达到视图更新的效果
  • 有了虚拟dom之后,当数据发生改变时,我们通过diff算法找到新旧虚拟dom之间的差异,在最小的范围内来更新真实的dom结构,没有变化的节点则不需要再次渲染

2、什么是diff算法?

  • vue中的diff算法只会进行同层比较,不会进行跨级比较
  • 如果标签名不相同,则直接删除重建,不再深度比较
  • 如果标签名和key都相同,则认为是相同节点,也不再深入比较

3.vue组件是如何渲染和更新的?

  • 初次渲染的过程:
1 先解析template模板为render函数
2 在执行render函数时会触发响应式,监听data属性中的getter
3 然后执行render函数,生成vnode,然后再将虚拟dom转化为真实的dom渲染到页面上(patch(elem,vnode))

image.png

  • 更新过程:
1 修改data,触发setter(此前在getter中已被监听)
2 重新执行render函数,生成newVnode
3 新旧虚拟dom使用diff算法找到最小的更新点,然后进行视图的更新 (patch(oldVnode,newVnode))

image.png

  • 汇总:
首先vue组件中会有template模板和data数据,
开始渲染时,vue会解析template模板会render函数,
然后执行render函数生成虚拟dom树,
在执行render函数时会触发data属性中的getter,
一旦触发getter,vue就会进行依赖收集,把相应的data数据观察起来,
当数据发生改变后,就会触发setter,
被触发setter的数据在确认之前已经被观察过后,就会触发re-render,
也就是重新执行render函数,生成新的虚拟dom树,
然后新旧虚拟dom树通过diff算法进行对比,找到最小的差异点,然后进行视图的更新

4、vue-router

  • 核心原理:通过改变url,在不重新请求页面的前提下,更新页面视图。
  • 实现方式:Hash模式、History模式
  • Hash模式下可以通过window.onhashchange来监听hash的改变
  • history模式下可以使用history.pushState修改浏览器历史记录栈,使用window.onpopstate监听浏览器地址的前进、后退

5、为什么data函数是个对象

  • .vue文件实质上是一个类(Class),使用组件时就相当于对类进行实例化。
  • 如果data只是一个对象的话,所有该组件的实例都会共用一份data内存地址,就会造成一个变全变的结果。
  • 而data是函数的话,数据以函数返回值的形式定义。这样每复用一个组件,就会返回一份新的data内存地址,每个组件的实例都可以拥有一个私有的数据空间,数据修改则不会相互影响

6、ajax请求应该放在哪个生命周期

  • mounted
  • js是单线程的,ajax是异步获取数据的;
  • 所以放在mounted之前没有用,只会让逻辑更加混乱;
  • 为了看起来逻辑的统一性,建议放在mounted中

7、何时需要beforeDestory

  • 解除自定义事件 event.$off
  • 清除定时器
  • 解绑自定义的DOM事件,如window.scroll

8、vue常见性能优化方式

  • 合理使用v-show和v-if
  • 合理使用computed
  • v-for时加key,避免和v-if同时使用(v-for优先级更高)
  • 自定义事件、dom事件及时销毁
  • 合理使用异步组件
  • 合理使用keep-alive
  • data层级不要太深

vue3

1、vue3比vue2有什么优势?

  • 性能更好
  • 体积更小
  • 更好的ts支持
  • 更好的代码组织
  • 更好的逻辑抽离
  • 更多新功能
1 createApp
2 emits属性
3 生命周期
4 多事件
5 fragment:在vue2的template模板中必须有一个根节点,vue3中则支持多个节点
6 移除.sync
7 异步组件写法
8 移除filter
9 新增teleport,可以使用teleport很方便的将组件放到body中
10 suspense:使用插槽封装了suspense组件
11 CompositionApi

2、vue3生命周期

setup, beforeCreate,created, beforeMount,mounted, beforeUpdate,updated, beforeUnmount, unmounted

3、composition API

  • 具有更灵活的代码组织:
在选项式api中,处理相同逻辑关注点的代码被强制拆分在了不同的选项中,位于文件的不同部分。在一个几百行的大组件中,要读懂代码中的一个逻辑关注点,需要在文件中反复上下滚动。
而在组合式api中,相同逻辑的代码被归为一组,代码就不需要反复上下滚动查看。此外,也就可以轻松地将一组代码移动到一个外部文件中,大大降低了重构成本。

4、为何需要ref?

  • 因为返回的值类型需要响应式
  • 如在setup、computed、合成函数中,都有可能返回值类型
  • vue如果不定义ref,用户将自造ref,就会更混乱

5、为何需要.value

  • ref是一个对象,需要利用value属性存储值
  • 通过.value 属性的get和set实现并保持响应式
  • 用于模板、reactive时不需要.value,其他情况都需要

6、为何需要toRef和toRefs

  • 针对的是reactive封装的响应式对象
  • 目的是在不丢失响应式的前提下,把对象数据进行解构、分解
  • 不创造响应式,而是延续响应式

7 vue3如何实现响应式

  • vue2中Object.defineProperty的缺点:
1 对于多层级对象,需要深度监听,递归到底,一次性计算量很大
2 无法监听新增或删除的属性,所以vue2中才会有Vue.$set/vue.$delete
3 无法原生监听数组,需要重新定义数组原型,重写数组方法才能实现数组的监听
  • vue3 使用proxy
1 深度监听,性能更好:
   vue2中响应式是一次性监听完成,一开始就会通过递归监听对象每个层级的属性;
   而vue3中只有在get到某个属性时才会递归,并且只会递归一层,里面的内容也只有遇到的时候才会把它包装成代理对象
2 可监听新增或删除的属性( deleteProperty)
3 可监听数组的变化
4 总之,能规避Object.defineProperty的问题,但是也有缺点,无法兼容所有浏览器,无法polyfill
  <script>
        function reactive(target = {}) {
            // 如果不是对象或数组,则返回
            if (typeof target !== 'object' || target == null) {
                return target
            }
            const proxyConf = {
                get(target, key, receiver) {
                    const result = Reflect.get(target, key, receiver)
                    console.log('get', key, result)
                    // return result
                    // 深度监听,
                    // 性能提升: vue2中响应式是一次性监听完成,一开始就会通过递归监听对象每个层级的属性;
                    // 而vue3中只有在get到某个属性时才会递归,并且只会递归一层,里面的内容也只有遇到的时候才会把它包装成代理对象
                    return reactive(result)
                },
                set(target, key, val, receiver) {
                    // 判断是不是新增的属性:可以在set时判断key是不是原来的key集合中,此处可以用reflect.ownKeys获取到原来的所有key值
                    const ownKeys = Reflect.ownKeys(target)
                    if(ownKeys.includes(key)) {
                        console.log('已有的key')
                    } else {
                        console.log('新增的key')
                    }
                    // 直接支持新增属性
                    const result = Reflect.set(target, key, val, receiver)
                    console.log('set', key, val, result)
                    return result
                },
                deleteProperty(target, key) {
                    const result = Reflect.deleteProperty(target, key)
                    console.log('delete', result)
                    return result
                }

            }
            const observed = new Proxy(target, proxyConf)
            return observed
        }
        const data = {
            name: 'zs',
            age: 20,
            info: {
                city: 'bj'
            }
        }
        // const data = ['a', 'b', 'c']

        const proxyData = reactive(data)
        //  数组的变化proxy原生就支持,
        //  对象属性删除可以用proxy的deleteProperty
    </script>

8.watch与watchEffect的区别

  • 两者都可监听data属性的变化
  • watch需要明确指出要监听的属性,而watchEffect会根据其中的属性自动监听其变化
  • watchEffect初始化时一定会执行一次,因为要收集需要监听的数据;而watch只有设置了初始化监听才会监听

9 setup中如何获取组件的实例

  • 在CompositionApi中没有this,通过getCurrentInstance获取当前实例
  • 而在OptionsApi中照常使用this

10 vue3为什么比vue2快

  • proxy响应式优化:
 可以更好地监听数组的变化,也可以更好监听新增或删除的属性,
 对于多层级需要深度监听的对象来说,
 vue2中是一次性监听完成,一开始就会通过递归监听对象每个层级的属性;
 而在vue3中,只有在get到某个属性时才会递归,并且只会递归一层,里面的内容也只有遇到的时候才会被包装成代理对象
  • patchFlag,静态标记:
对动态节点做标记,如TEXT/PROPS,
这样做diff算法时就可以区分静态节点以及不同类型的动态节点,也就会更快一些
  • hoistStatic
把静态节点提到父作用域缓存起来,多个相邻的静态节点会被合并到一起缓存起来
  • cacheHandler
缓存事件,
  • SSR 优化
在ssr输出时,静态节点不再走vdom的逻辑,而是直接输出字符串;
动态节点还是需要动态渲染
  • tree-shaking
根据模板的内容动态从vue中import需要的内容