2023Vue面试题+详解

349 阅读9分钟

1. 你知道 Vue 响应式数据原理吗?Proxy 与 Object.defineProperty 优劣对比?

  • 响应式原理 - vue 的响应式实现主要是利用了 Object.defineProperty 的方法里面的 setter 与 getter 方法的观察者模式来实现。在组件初始化时会给每一个 data 属性注册 getter 和 setter,然后再 new 一个自己的 Watcher 对象,此时 watcher 会立即调用组件的 render 函数去生成虚拟 DOM。在调用 render 的时候,就会需要用到 data 的属性值,此时会触发 getter 函数,将当前的 Watcher 函数注册进 sub 里。当 data 属性发生改变之后,就会遍历 sub 里所有的 watcher 对象,通知它们去重新渲染组件。
    • proxy 的优势如下:
      • Proxy 可以直接监听对象而非属性,可以直接监听数组的变化;
      • Proxy 有多达 13 种拦截方法,不限于 apply. ownKeys. deleteProperty. has 等等是 Object.defineProperty 不具备的;
      • Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;
    • Object.defineProperty 的优势如下:
      • 兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill(垫片)来弥

2. 解释单向数据流和双向数据绑定

  • 对于 Vue 来说,组件之间的数据传递具有单向数据流这样的特性称为单向数据流,单向数据流(Unidirectional data flow)方式使用一个上传数据流和一个下传数据流进行双向数据通信,两个数据流之间相互独立,单向数据流指只能从一个方向来修改状态。
  • 而双向数据绑定即为当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化,两个数据流之间互为影响。

3. 对比 jQuery,Vue 有什么不同

  • jQuery 专注视图层,通过直接操作 DOM 去实现页面的一些逻辑渲染;
  • Vue 专注于数据层,通过数据的双向绑定,最终表现在 DOM 层面,减少了 DOM 操作。Vue 使用了组件化思想,使得项目子集职责清晰,提高了开发效率,方便重复利用,便于协同开发

4. Vue 中怎么自定义指令

  • 通过 directive 来自定义指令,自定义指令分为全局指令和局部指令,自定义指令也有几个的钩子函数,常用的有 bind 和 update,当 bind 和 update 时触发相同行为,而不关心其它的钩子时可以简写。
Vue.directive("focus", {
    // 当被绑定的元素插入到 DOM 中时……
    inserted: function (el) {
        // 聚焦元素
        el.focus();
    },
});

Vue.directive("color-swatch", function (el, binding) {
    el.style.backgroundColor = binding.value;
});

5. Vue 等单页面应用的优缺点

  • 优点
    • 单页应用的内容的改变不需要重新加载整个页面,web 应用更具响应性和更令人着迷。
    • 单页应用没有页面之间的切换,就不会出现“白屏现象”,也不会出现假死并有“闪烁”现象
    • 单页应用相对服务器压力小,服务器只用出数据就可以,不用管展示逻辑和页面合成,吞吐能力会提高几倍。
    • 良好的前后端分离。后端不再负责模板渲染. 输出页面工作,后端 API 通用化,即同一套后端程序代码,不用修改就可以用于 Web 界面. 手机. 平板等多种客户端。
  • 缺点
    • 首次加载耗时比较多。
    • SEO 问题,不利于百度,360 等搜索引擎收录。
    • 容易造成 Css 命名冲突。
    • 前进. 后退. 地址栏. 书签等,都需要程序进行管理,页面的复杂度很高,需要一定的技能水平和开发成本高。

6. Vue 如何实现单页面应用

  • 通常的 url 地址由以下内容构成:协议名 域名 端口号 路径 参数 哈希值,当哈希值改变,页面不会发生跳转,单页面应用就是利用了这一点,给 window 注册 onhashchange 事件,当哈希值改变时通过 location.hash 就能获得相应的哈希值,然后就能跳到相应的页面。
    1. hash 通过监听浏览器的 onhashchange()事件变化,查找对应的路由规则
    2. history 原理: 利用 H5 的 history 中新增的两个 API pushState() 和 replaceState() 和一个事件 onpopstate 监听 URL 变化

7. data 为什么是一个函数

  • 在 Vue 组件中,data 选项必须是一个函数,而不能直接是一个对象。 这是因为 Vue 组件可以同时存在多个实例,如果直接使用对象形式的 data 选项,那么所有的实例将会共享同一个 data 对象,这样就会造成数据互相干扰的问题。 因此,将 data 选项设置为函数可以让每个实例都拥有自己独立的 data 对象

8. ref 和 reactive 的简单理解

  1. ref 和 reactive 都是 vue3 的监听数据的方法,本质是 proxy
  2. ref 基本类型复杂类型都可以监听(我们一般用 ref 监听基本类型),reactive 只能监听对象(arr,json)
  3. ref 底层还是 reactive,ref 是对 reactive 的二次包装, ref 定义的数据访问的时候要多一个.value

9. 谈谈你对 Vue3.0 有什么了解?

  • 新增亮点
    1. 性能比 vue2.x 快 1.2~2 倍
    2. 支持 tree-shaking,按需编译,体积比 vue2.x 更小
    3. 支持组合 API
    4. 更好的支持 TS
    5. 更先进的组件 - 性能比 vue2.x 快 1.2~2 倍如何实现的呢
    6. diff 算法更快
      • vue2.0 是需要全局去比较每个节点的,若发现有节点发生变化后,就去更新该节点
      • vue3.0 是在创建虚拟 dom 中,会根据 DOM 的的内容会不会发生内容变化,添加静态标记, 谁有 flag!比较谁。
    7. 静态提升
      • vue2 中无论元素是否参与更新,每次都会重新创建,然后再渲染 vue3 中对于不参与更新的元素,会做静态提升,只被创建一次,在渲染时直接复用即可
    8. 事件侦听缓存
      • 默认情况下,onclick 为动态绑定,所以每次都会追踪它的变化,但是因为是同一函数,没有必要追踪变化,直接缓存复用即可
      • 在之前会添加静态标记 8 会把点击事件当做动态属性 会进行 diff 算法比较, 但是在事件监听缓存之后就没有静态标记了,就会进行缓存复用 - 为什么 vue3.0 体积比 vue2.x 小
    • 在 vue3.0 中创建 vue 项目 除了 vue-cli,webpack 外还有 一种创建方法是 Vite Vite 是作者开发的一款有意取代 webpack 的工具,其实现原理是利用 ES6 的 import 会发送请求去加载文件的特性,拦截这些请求,做一些预编译,省去 webpack 冗长的打包时间

10. 你做过哪些 Vue 的性能优化

  1. 首屏加载优化
  2. 路由懒加载
  3. 开启服务器 Gzip
    • 开启 Gzip 就是一种压缩技术,需要前端提供压缩包,然后在服务器开启压缩,文件在服务器压缩后传给浏览器,浏览器解压后进行再进行解析。首先安装 webpack 提供的 compression-webpack-plugin 进行压缩,然后在 vue.config.js:
    const CompressionWebpackPlugin = require('compression-webpack-plugin')
    const productionGzipExtensions = ['js', 'css']......plugins: [
        new CompressionWebpackPlugin({
            algorithm: 'gzip',
            test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')\$'),
            threshold: 10240,
            minRatio: 0.8
        })
    ]....
    
  4. 启动 CDN 加速
    • 我们继续采用 cdn 的方式来引入一些第三方资源,就可以缓解我们服务器的压力,原理是将我们的压力分给其他服务器点。
  5. 代码层面优化
    • computed 和 watch 区分使用场景
      • computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
      • watch:类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
    • v-if 和 v-show 区分使用场景 v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。这里要说的优化点在于减少页面中 dom 总数,我比较倾向于使用 v-if,因为减少了 dom 数量。
    • v-for 遍历必须为 item 添加 key,且避免同时使用 v-if v-for 遍历必须为 item 添加 key,循环调用子组件时添加 key,key 可以唯一标识一个循环个体,可以使用例如 item.id 作为 key 避免同时使用 v-if,v-for 比 v-if 优先级高,如果每一次都需要遍历整个数组,将会影响速度。
  6. Webpack 对图片进行压缩
  7. 避免内存泄漏
  8. 减少 ES6 转为 ES5 的冗余代码

11. Vue 路由实现的底层原理

  • 在 Vue 中利用数据劫持 defineProperty 在原型 prototype 上初始化了一些 getter,
    • 分别是 router 代表当前 Router 的实例.
    • route 代表当前 Router 的信息。
  • 在 install 中也全局注册了 router-view, router-link, 其中的 Vue.util.defineReactive, 这是 Vue 里面观察者劫持数据的方法,劫持 _route,当 _route 触发 setter 方法的时候,则会通知到依赖的组件。
  • 接下来在 init 中,会挂载判断是路由的模式,是 history 或者是 hash,点击行为按钮,调用 hashchange 或者 popstate 的同时更_route, _route 的更新会触发 route-view 的重新渲染。

12. 用过插槽吗?用的是具名插槽还是匿名插槽

  • 用过,都使用过。插槽相当于预留了一个位置,可以将我们书写在组件内的内容放入,写一个插槽就会将组件内的内容替换一次,两次则替换两次。为了自定义插槽的位置我们可以给插槽取名,它会根据插槽名来插入内容,一一对应。
  • 举例来说,这里有一个 <FancyButton> 组件,可以像这样使用:
    <FancyButton>
        Click me!
        <!-- 插槽内容 -->
    </FancyButton>
    
    • <FancyButton> 的模板是这样的:
    <button class="fancy-btn">
        <slot></slot>
        <!-- 插槽出口 -->
    </button>