写点什么

金三银四的 Vue 面试准备

作者:CRMEB
  • 2022 年 3 月 21 日
  • 本文字数:21493 字

    阅读完需:约 71 分钟

金三银四的 Vue 面试准备

前言

为了金三银四的跳槽季做准备,并且我是 vue 技术栈的,所以整理了若干个 vue 的面试题。

每次看别人的博客,都会不自主的去看答案,为了方便检验自己的掌握程度,我特意将答案折叠起来,大家可以先看题目,在脑海中想象一下如果你被问到会怎么回答,然后再展开答案看看和自己的答案有什么不同。

答案非官方,仁者见仁智者见智,仅供参考。

基础使用

MVVM、MVC 有什么区别

MVC 通过分离 ModelView 和 Controller 的方式来组织代码结构。

  • 其中 View 负责页面的显示逻辑,

  • Model 负责存储页面的业务数据,以及对相应数据的操作。

  • Controller 层是 View 层和 Model 层的纽带,它主要负责用户与应用的响应操作,当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来完成对 Model 的修改,然后 Model 层再去通知 View 层更新。

MVVM 分为 ModelViewViewModel

  • Model 代表数据模型,数据和业务逻辑都在 Model 层中定义;

  • View 代表 UI 视图,负责数据的展示;

  • ViewMode 负责监听 Model 中数据的改变并且控制视图的更新,处理用户交互操作;

Model 和 View 并无直接关联,而是通过 ViewModel 来进行联系的,Model 和 ViewModel 之间有着双向数据绑定的联系。因此当 Model 中的数据改变时会触发 View 层的刷新,View 中由于用户交互操作而改变的数据也会在 Model 中同步。 这种模式实现了 Model 和 View 的数据自动同步,因此开发者只需要专注于数据的维护操作即可,而不需要自己操作 DOM

Vue 并没有完全遵循 MVVM 思想呢?

  • 严格的 MVVM 要求 View 不能和 Model 直接通信,而 Vue 提供了 $refs 这个属性,让 Model 可以直接操作 View,违反了这一规定,所以说 Vue 没有完全遵循 MVVM

Vue 的优点

  • 渐进式框架:可以在任何项目中轻易的引入;

  • 轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十 kb ;

  • 简单易学:国人开发,中文文档,不存在语言障碍 ,易于理解和学习;

  • 双向数据绑定:在数据操作方面更为简单;

  • 组件化:很大程度上实现了逻辑的封装和重用,在构建单页面应用方面有着独特的优势;

  • 视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;

对 SPA 单页面的理解,它的优缺点分别是什么?

SPA 仅在 Web 页面初始化时加载相应的 HTMLJavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。

优点:

  • 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;

  • 有利于前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;

缺点:

  • 初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScriptCSS 统一加载,部分页面按需加载;

  • 不利于 SEO:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。

怎样理解 Vue 的单向数据流?

父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。

每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。在子组件内部改变 prop 的时候 , Vue 会在浏览器的控制台中发出警告。

子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。

有两种常见的试图改变一个 prop 的情形 :

  • 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。 在这种情况下,最好定义一个本地的 data 属性并将这个 prop 用作其初始值:

  • 这个 prop 以一种原始的值传入且需要进行转换。 在这种情况下,最好使用这个 prop 的值来定义一个计算属性

Data 为什么是一个函数?

因为组件是用来复用的,且 JS 里对象是引用关系,如果组件中 data 是一个对象,那么这样作用域没有隔离,子组件中的 data 属性值会相互影响,如果组件中 data 选项是一个函数,那么每个实例可以维护一份被返回对象的独立的拷贝,组件实例之间的 data 属性值不会互相影响;而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。

Computed 和 Watch 有什么区别?

对于 Computed:

  • 它支持缓存,只有依赖的数据发生了变化,才会重新计算

  • 不支持异步,当 Computed 中有异步操作时,无法监听数据的变化

  • 如果一个属性是由其他属性计算而来的,这个属性依赖其他的属性,一般会使用 computed

  • 如果 computed 属性的属性值是函数,那么默认使用 get 方法,函数的返回值就是属性的属性值;在 computed 中,属性有一个 get 方法和一个 set 方法,当数据发生变化时,会调用 set 方法。

对于 Watch:

  • 它不支持缓存,当一个属性发生变化时,它就会触发相应的操作

  • 支持异步监听

  • 监听的函数接收两个参数,第一个参数是最新的值,第二个是变化之前的值

  • 监听数据必须是 data 中声明的或者父组件传递过来的 props 中的数据,当发生变化时,会触发其他操作

  • 函数有两个的参数:immediate:组件加载立即触发回调函数 deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。

Computed 和 Methods 的区别

共同点:

可以将同一函数定义为一个 method 或者一个计算属性。对于最终的结果,两种方式是相同的。

不同点:

  • computed: 计算属性是基于它们的依赖进行缓存的,只有在它的相关依赖发生改变时才会重新求值;

  • method: 调用总会执行该函数。

.Sync 的作用是什么?

vue 修饰符 sync 的功能是:当父组件提供了一个数据,而子组件想要去更改这个数据,但是 Vue 的规则不能让子组件去修改父组件的数据,就需要通过 this.$emit 和 $event,来实现数据修改的目的。

:money.sync="total" // 等价于 :money="total" v-on:update:money="total = $event"复制代码
复制代码

绑定事件 @click=handler 和 @click=handler() 那个正确?有什么区别?

都可以,不带括号会传进来一个事件对象,带括号的不会

事件有哪些修饰符?

「事件修饰符」

  • .stop 阻止事件继续传播

  • .prevent 阻止标签默认行为

  • .capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理

  • .self 只当在 event.target 是当前元素自身时触发处理函数

  • .once 事件将只会触发一次

  • .passive 告诉浏览器你不想阻止事件的默认行为

「v-model 的修饰符」

  • .lazy 通过这个修饰符,转变为在 change 事件再同步

  • .number 自动将用户的输入值转化为数值类型

  • .trim 自动过滤用户输入的首尾空格

「键盘事件的修饰符」

  • .enter

  • .tab

  • .delete (捕获“删除”和“退格”键)

  • .esc

  • .space

  • .up

  • .down

  • .left

  • .right

「系统修饰键」

  • .ctrl

  • .alt

  • .shift

  • .meta

「鼠标按钮修饰符」

  • .left

  • .right

  • .middle

什么是插槽?具名插槽?作用域插槽?原理是什么?

slot 又名插槽,是 Vue 的内容分发机制,插槽 slot 是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。slot 又分三类,默认插槽,具名插槽和作用域插槽。

  • 默认插槽:又名匿名插槽,当 slot 没有指定 name 属性值的时候,默认显示的插槽,一个组件内只允许有一个匿名插槽。

  • 具名插槽:带有具体名字的插槽,也就是带有 name 属性的 slot,一个组件可以出现多个具名插槽。

  • 作用域插槽:可以是匿名插槽,也可以是具名插槽,该插槽在渲染时,父组件可以使用子组件内部的数据。

实现原理:当子组件 vm 实例化时,获取到父组件传入的 slot 标签的内容,存放在 vm.$slot 中,默认插槽为 vm.$slot.default,具名插槽为 vm.$slot.xxxxxx 为插槽名,当组件执行渲染函数时候,遇到 slot 标签,使用 $slot 中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。

Vue 中如何实现过度效果?如何实现列表过度?

过渡效果,当然只有 dom 从显示到隐藏或隐藏到显示才能用

Vue.js 为我们提供了内置的过渡组件 transition 和 transition-group

Vue 将元素的过渡分为四个阶段,进入前,进入后,消失前,消失后

支持 mode 属性,可选值:

  • in-out:要进入的先进入,然后要消失的再消失

  • out-in:要消失的先消失,然后要进入的再进入

多个元素需要加上过渡效果可以使用 name 属性进行区分。

可以配合 animate.css 实现更多的动画效果。

过滤器的作用,如何实现一个过滤器

过滤器是用来过滤数据的,在 Vue 选项中声明 filters 来实现一个过滤器,filters不会修改数据,而是处理数据,改变用户看到的输出。

使用场景:

  • 需要格式化数据的情况,比如需要处理时间、价格等数据格式的输出 / 显示。

  • 比如后端返回一个 年月日的日期字符串,前端需要展示为 多少天前 的数据格式,此时就可以用fliters 过滤器来处理数据。

过滤器是一个函数,它会把表达式中的值始终当作函数的第一个参数。过滤器用在 插值表达式 {{ }} 和 v-bind 表达式 中,然后放在操作符 | 后面进行指示。

例如,在显示金额,给商品价格添加单位:

<li>商品价格:{{item.price | filterPrice}}</li>
filters: { filterPrice (price) { return price ? ('¥' + price) : '--' } }复制代码
复制代码

assets 和 static 的区别

相同点: assets 和 static 两个都是存放静态资源文件。项目中所需要的资源文件图片,字体图标,样式文件等都可以放在这两个文件下,这是相同点

不相同点: assets 中存放的静态资源文件在项目打包时,也就是运行 npm run build 时会将 assets 中放置的静态资源文件进行打包上传,所谓打包简单点可以理解为压缩体积,代码格式化。而压缩后的静态资源文件最终也都会放置在 static 文件中跟着 index.html 一同上传至服务器。static 中放置的静态资源文件就不会要走打包压缩格式化等流程,而是直接进入打包好的目录,直接上传至服务器。因为避免了压缩直接进行上传,在打包时会提高一定的效率,但是 static 中的资源文件由于没有进行压缩等操作,所以文件的体积也就相对于 assets 中打包后的文件提交较大点。在服务器中就会占据更大的空间。

建议: 将项目中 template需要的样式文件 js 文件等都可以放置在 assets 中,走打包这一流程。减少体积。而项目中引入的第三方的资源文件如iconfoont.css 等文件可以放置在 static 中,因为这些引入的第三方文件已经经过处理,我们不再需要处理,直接上传。

对 SSR 的理解

SSR 大致的意思就是 vue 在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的 html 片段直接返回给客户端,这个过程就叫做服务端渲染。

(1)服务端渲染的优点:

  • 更好的 SEOSSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;

  • 首屏加载更快:SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;

(2) 服务端渲染的缺点:

  • 更多的开发条件限制:例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,

  • 不能进行 dom 操作。

这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行。

Vue 的性能优化有哪些

(1)代码层面的优化

  • v-if 和 v-show 区分使用场景

  • computed 和 watch 区分使用场景

  • v-for 遍历必须为 item 添加 key,且避免同时使用 v-if

  • 长列表性能优化

  • 事件的销毁

  • 图片资源懒加载

  • 路由懒加载

  • 第三方插件的按需引入

  • 优化无限列表性能

  • 服务端渲染

(2)Webpack 层面的优化

  • Webpack 对图片进行压缩

  • 减少 ES6 转为 ES5 的冗余代码

  • 提取公共代码

  • 模板预编译

  • 提取组件的 CSS

  • 优化 SourceMap

  • 构建结果输出分析

  • Vue 项目的编译优化

(3)基础的 Web 技术的优化

  • 开启 gzip 压缩

  • 浏览器缓存

  • CDN 的使用

  • 使用 Chrome Performance 查找性能瓶颈

Vue 的首屏加载性能优化有哪些

优化前的大小



1.图片优化

之前为了方便开法, 背景图片直接在 assets 里面扔了一个 jpg , 导致加载这张图片的时候就用了十几秒, 于是乎我就把图片上传空间了, 然后改用网络地址。

2.禁止生成.map 文件

build 出来的 dist 文件夹里面有很多的 .map 文件,这些文件主要是帮助线上调试代码,禁止生成这些文件.

在 vue.config.js 里面加上这句。



3.路由懒加载



4.cdn 引入公共库

    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">    <script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>    <script src="https://unpkg.com/element-ui/lib/index.js"></script>    <script src="https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js"></script>    <script src="https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script>    <script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>复制代码
复制代码


 //cdn引入    configureWebpack: {        externals: {            'vue': 'Vue',            'element-ui': 'ELEMENT',            'vue-router': 'VueRouter',            'vuex': 'Vuex',            'axios': 'axios'        }    }复制代码
复制代码

网上说可以把 import 注释掉,亲自操作会报错,也有资料说不用注释也不会打包。

一顿操作最后的文件,效果显著,app.js 还是很大



5.终极法宝 GZIP 压缩

做完这个感觉前四步都是小菜一碟,直接把 1.4m 的 app.js 干成一百多 kb ,其他的都不足挂齿了。

 configureWebpack: config => {        return {            //配置cdn            externals: {                'vue': 'Vue',                'element-ui': 'ELEMENT',                'vue-router': 'VueRouter',                'vuex': 'Vuex',                'axios': 'axios'            },            //配置gzip压缩            plugins: [                new CompressionWebpackPlugin({
test: new RegExp('\.(js|css)$'), threshold: 10240, minRatio: 0.8 }) ], } }复制代码
复制代码

服务端也要配,不然不认识 GZIP 文件。

//配置GZIP压缩模块const compression = require('compression');//在所有中间件之前引入app.use(compression());复制代码
复制代码

最垃圾的服务器通过以上几个优化,一样飞起来了!!!



vue 初始化页面闪动问题

使用 vue 开发时,在 vue 初始化之前,由于 div 是不归 vue 管的,所以我们写的代码在还没有解析的情况下会容易出现花屏现象,看到类似于 {{message}} 的字样,虽然一般情况下这个时间很短暂,但是我们还是有必要让解决这个问题的。

首先:在 css 里加上 [v-cloak] { display: none; } 。如果没有彻底解决问题,则在根元素加上 style="display: none;" :style="{display:  block }"

Class 与 Style 如何动态绑定?

Class 可以通过对象语法和数组语法进行动态绑定:

  • 对象语法:

<div v-bind:class="{ active: isActive, 'text-danger': hasError }"></div>
data: { isActive: true, hasError: false}复制代码
复制代码


  • 数组语法:

<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
data: { activeClass: 'active', errorClass: 'text-danger'}复制代码
复制代码

Style 也可以通过对象语法和数组语法进行动态绑定:

  • 对象语法:

<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: { activeColor: 'red', fontSize: 30}复制代码
复制代码


  • 数组语法:

<div v-bind:style="[styleColor, styleSize]"></div>
data: { styleColor: { color: 'red' }, styleSize:{ fontSize:'23px' }}复制代码
复制代码

如何让 CSS 只在当前组件中起作用?

在组件中的 style 标签中加上 scoped

如何获取 dom

ref="domName" 用法:this.$refs.domName

vue-loader 是什么?使用它的用途有哪些?

vue 文件的一个加载器,把 template/js/style转换成 js 模块。

生命周期

Vue 有哪些生命周期钩子?

Vue 的生命周期钩子核心实现是利用发布订阅模式先把用户传入的的生命周期钩子订阅好(内部采用数组的方式存储)然后在创建组件实例的过程中会一次执行对应的钩子方法(发布)。

  • beforeCreate:是 new Vue() 之后触发的第一个钩子,在当前阶段 datamethodscomputed 以及 watch 上的数据和方法都不能被访问。

  • created:在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发 updated 函数。可以做一些初始数据的获取,在当前阶段无法与 Dom 进行交互,如果非要想,可以通过 vm.$nextTick 来访问 Dom

  • beforeMount:发生在挂载之前,在这之前 template 模板已导入渲染函数编译。而当前阶段虚拟 Dom 已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发 updated

  • mounted:在挂载完成后发生,在当前阶段,真实的 Dom 挂载完毕,数据完成双向绑定,可以访问到 Dom 节点,使用 $refs 属性对 Dom 进行操作。

  • beforeUpdate:发生在更新之前,也就是响应式数据发生更新,虚拟 dom 重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染。

  • updated:发生在更新完成之后,当前阶段组件 Dom 已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新。

  • beforeDestroy:发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器。

  • destroyed:发生在实例销毁之后,这个时候只剩下了 dom 空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。

如果需要发送异步请求,最好放在哪个钩子内?

可以在钩子函数 createdbeforeMountmounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。

推荐在 created 钩子函数中调用异步请求,有以下优点:

  • 能更快获取到服务端数据,减少页面 loading 时间;

  • ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;

第一次页面加载会触发哪几个钩子?

beforeCreatecreatedbeforeMountmounted

哪个钩子可以进行 dom 操作?

在钩子函数 mounted 被调用前,Vue 已经将编译好的模板挂载到页面上,所以在 mounted 中可以访问操作 DOM

父子组件嵌套时,父组件和子组件生命周期钩子执行顺序是什么?

Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:

  • 加载渲染过程 父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted

  • 子组件更新过程 父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated

  • 父组件更新过程 父beforeUpdate -> 父updated

  • 销毁过程 父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed

父子组件嵌套时,父组件视图和子组件视图谁先完成渲染?

  • 加载渲染过程 父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted

可知子组件先完成渲染

keep-alive 中的生命周期哪些

  • 对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated

指令相关

说说 vue 内置指令



什么是自定义指令?有哪些生命周期?

是 vue 对 HTML 元素的扩展,给 HTML 元素增加自定义功能。vue 编译 DOM 时,会找到指令对象,执行指令的相关方法。

自定义指令有五个生命周期

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。

  • update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。

  • componentUpdated:被绑定元素所在模板完成一次更新周期时调用。

  • unbind:只调用一次,指令与元素解绑时调用。

v-text 和 v-html 有什么区别?

  • v-text 和 {{}} 表达式渲染数据,不解析标签。

  • v-html 不仅可以渲染数据,而且可以解析标签。

v-if 和 v-for 的优先级

当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级,这意味着 v-if 将分别重复运行于每个 v-for 循环中。所以,不推荐 v-if 和 v-for 同时使用。如果 v-if 和 v-for 一起用的话,vue 中的的会自动提示 v-if 应该放到外层去。

V-if 和 v-show 有什么区别?

  • 手段v-if 是动态的向 DOM 树内添加或者删除 DOM 元素;v-show 是通过设置 DOM 元素的 display 样式属性控制显隐;

  • 编译过程v-if 切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show 只是简单的基于 css 切换;

  • 编译条件v-if 是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译; v-show 是在任何条件下,无论首次条件是否为真,都被编译,然后被缓存,而且 DOM 元素保留;

  • 性能消耗v-if 有更高的切换消耗;v-show 有更高的初始渲染消耗;

  • 使用场景v-if 适合运营条件不大可能改变;v-show 适合频繁切换。

组件的 v-model 是如何实现的?

我们在 vue 项目中主要使用 v-model 指令在表单 inputtextareaselect 等元素上创建双向数据绑定,我们知道 v-model 本质上不过是语法糖,v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:

  • text 和 textarea 元素使用 value 属性和 input 事件;

  • checkbox 和 radio 使用 checked 属性和 change 事件;

  • select 字段将 value 作为 prop 并将 change 作为事件。

以 input 表单元素为例:

<input v-model='something'>    // 相当于
<input v-bind:value="something" v-on:input="something = $event.target.value">复制代码
复制代码

v-model 可以被用在自定义组件上吗?如果可以,如何使用?

如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:

父组件:<ModelChild v-model="message"></ModelChild>复制代码
复制代码


子组件:<div>{{value}}</div>
props:{ value: String},methods: { test1(){ this.$emit('input', '小红') },},复制代码
复制代码


v-on 可以监听多个方法吗?

可以

<input type="text" v-on="{ input:onInput,focus:onFocus,blur:onBlur, }">复制代码
复制代码

组件相关

组件通信的 N 种方式

(1)props / $emit 适用 父子组件通信

(2)ref 适用 父子组件通信

  • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例

(3)$parent / $children / $root:访问父 / 子实例 / 根实例

(4)EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信

这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。

(5)$attrs/$listeners 适用于 隔代组件通信

  • $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。

  • $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件

(6)provide / inject 适用于 隔代组件通信

祖先组件中通过 provide 来提供变量,然后在子孙组件中通过 inject 来注入变量。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

(7)Vuex 适用于 父子、隔代、兄弟组件通信

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。store 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

  • 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

(8)插槽

Vue3 可以通过 usesolt 获取插槽数据。

(9)mitt.js 适用于任意组件通信

Vue3 中移除了 $on$off等方法,所以 EventBus 不再使用,相应的替换方案就是 mitt.js

Vue3 和 vue2 全局组件和局部组件注册的方式?

  • Vue2Vue.component()

  • Vue3app.component()

什么是动态组件?动态组件的钩子如何执行?

让多个组件使用同一个挂载点,并动态切换,这就是动态组件

简单的说,动态组件就是将几个组件放在一个挂载点下,这个挂载点就是标签,其需要绑定 is 属性,属性值为父组件中的变量,变量对应的值为要挂载的组件的组件名,然后根据父组件里某个变量来动态显示哪个,也可以都不显示。

缓存 <keep-alive>

  • 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。

  • 可以将动态组件放到组件内对动态组件进行缓存,这样动态组件进行切换的时候,就不会每次都重新创建了。

Keep-alive 的作用?使用 keep-alive 的组件如何监控组件切换?

keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,其有以下特性:

  • 一般结合路由和动态组件一起使用,用于缓存组件;

  • 提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高;

  • 对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated

父组件如何监听子组件的生命周期?

比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:

// Parent.vue<Child @mounted="doSomething"/>    // Child.vuemounted() {  this.$emit("mounted");}复制代码
复制代码

以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:

//  Parent.vue<Child @hook:mounted="doSomething" ></Child>
doSomething() { console.log('父组件监听到 mounted 钩子函数 ...');}, // Child.vuemounted(){ console.log('子组件触发 mounted 钩子函数 ...');}, // 以上输出顺序为:// 子组件触发 mounted 钩子函数 ...// 父组件监听到 mounted 钩子函数 ... 复制代码
复制代码

当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:createdupdated 等都可以监听。

原理相关

Vue 初始化时都做了什么?

第一部分

⭐ 每个 vue 实例都有一个 _uid,并且是依次递增的,确保唯一性。

⭐ vue 实例不应该是一个响应式的,做个标记。

第二部分

⭐ 如果是子组件,将组件配置对象上的一些深层次属性放到 vm.$options 选项中,以提高代码的执行效率。

⭐ 如果是根组件,对 options 进行合并,vue 会将相关的属性和方法都统一放到 vm.$options 中。vm.$options 的属性来自两个方面,一个是 Vue 的构造函数 vm.constructor 预先定义的,一个是 new Vue 时传入的入参对象。

第三部分

⭐ initProxy / vm._renderProxy 在非生产环境下执行了 initProxy 函数,参数是实例;在生产环境下设置了实例的 _renderProxy 属性为实例自身。

⭐ 设置了实例的 _self 属性为实例自身。

⭐ initLifecycle 初始化组件实例关系属性 , 比如 $parent$children$root$refs 等 (不是组件生命周期 mounted , created...)

⭐ initEvents 初始化自定义事件。

⭐ initRender 初始化插槽 , 获取 this.slots , 定义 this._c , 也就是 createElement 方法 , 平时使用的 h 函数。

⭐ callHook 执行 beforeCreate 生命周期函数。

⭐ initInjections 初始化 inject 选项

⭐ initState 响应式原理的核心 , 处理 propsmethodscomputeddatawatch 等。

⭐ initProvide 解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上。

⭐ callHook 执行 created 生命周期函数。

第四部分

⭐ 如果有 el 属性,则调用 vm.$mount 方法挂载 vm ,挂载的目标就是把模板渲染成最终的 DOM

⭐ 不存在 el 的时候不挂载 , 需要手动挂载。

数据响应式的原理

Vue.js 是采用 数据劫持 结合 发布者-订阅者模式 的方式,通过 Object.defineProperty() 来劫持各个属性的 settergetter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:

  1. 使用 observe 对需要响应式的数据进行递归,将对像的所有属性及其子属性,都加上 setter 和 getter 这样的话,给这个对象的某个属性赋值的时候,就会触发 setter,那么就能监听到了数据变化。

  2. compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。

  3. Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是:

  • 在自身实例化时往属性订阅器(dep)里面添加自己

  • 自身必须有一个 update() 方法

  • 待属性变动触发 dep.notice() 通知时,能调用自身的 update() 方法,并触发 Compile 中绑定的回调,完成视图更新。

总结:通过 Observer 来监听自己的 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到一个数据响应式的效果。



使用 Object.defineProperty() 来进行数据劫持有什么缺点?

无法劫持以下操作

  • 给对象新增属性

  • 给对象删除属性

  • 大部分的操作数组

Vue 框架怎么实现对象和数组的监听?

Vue 框架是通过 遍历数组 和 递归遍历对象,从而达到利用 Object.defineProperty() 对对象和数组的部分方法的操作进行监听。

Vue 中给 data 中的对象属性添加一个新的属性或删除一个属性时会发生什么?如何解决?

什么都不会发生,因为 Object.defineProperty() 监听不到这类变化。

可以使用 vm.$set 和 Vue.set 方法去添加一个属性。

可以使用 vm.$delete 和 Vue.delete 方法去删除一个属性。

如何解决索引赋值或者修改数组长度无法改变视图?

由于 Vue 只改写了 7 种修改数组的方法,所以 Vue 不能检测到以下数组的变动:

  • 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue

  • 当你修改数组的长度时,例如:vm.items.length = newLength

为了解决第一个问题,Vue 提供了以下操作方法:

// Vue.setVue.set(vm.items, indexOfItem, newValue)// vm.$set,Vue.set的一个别名vm.$set(vm.items, indexOfItem, newValue)// Array.prototype.splicevm.items.splice(indexOfItem, 1, newValue)复制代码
复制代码

为了解决第二个问题,Vue 提供了以下操作方法:

// Array.prototype.splicevm.items.splice(newLength)复制代码
复制代码

数组的响应式是怎么实现的?

择对 7 种数组(push,shift,pop,splice,unshift,sort,reverse)方法进行重写

所以在 Vue 中修改数组的索引和长度是无法监控到的。需要通过以上 7 种变异方法修改数组才会触发数组对应的 watcher 进行更新

// src/obserber/array.js\// 先保留数组原型\const arrayProto = Array.prototype;\// 然后将arrayMethods继承自数组原型\// 这里是面向切片编程思想(AOP)--不破坏封装的前提下,动态的扩展功能\export const arrayMethods = Object.create(arrayProto);\let methodsToPatch = [\  "push",\  "pop",\  "shift",\  "unshift",\  "splice",\  "reverse",\  "sort",\];\methodsToPatch.forEach((method) => {\  arrayMethods[method] = function (...args) {\    //   这里保留原型方法的执行结果\    const result = arrayProto[method].apply(this, args);\    // 这句话是关键\    // this代表的就是数据本身 比如数据是{a:[1,2,3]} 那么我们使用a.push(4)  this就是a  ob就是a.__ob__ 这个属性就是上段代码增加的 代表的是该数据已经被响应式观察过了指向Observer实例\    const ob = this.__ob__;\\    // 这里的标志就是代表数组有新增操作\    let inserted;\    switch (method) {\      case "push":\      case "unshift":\        inserted = args;\        break;\      case "splice":\        inserted = args.slice(2);\      default:\        break;\    }\    // 如果有新增的元素 inserted是一个数组 调用Observer实例的observeArray对数组每一项进行观测\    if (inserted) ob.observeArray(inserted);\    // 之后咱们还可以在这里检测到数组改变了之后从而触发视图更新的操作--后续源码会揭晓\    return result;\  };\});复制代码
复制代码

$nextTick 的原理是什么?nextTick 中的回调是在下次 DOM 更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。主要思路就是采用微任务优先的方式调用异步方法去执行 nextTick 包装的方法。


简单的理解是:当数据更新了,在 dom 中渲染后, 自动执行该函数。Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,Vue 是异步执行 DOM 更新的。created 钩子函数进行的 DOM 操作一定要放在 Vue.nextTick() 的回调函数中,原因是在函数执行的时候 DOM 其实并未进行任何渲染。常用的场景是在进行获取数据后,需要对新视图进行下一步操作或者其他操作时,发现获取不到 dom。因为赋值操作只完成了数据模型的改变并没有完成视图更新。


有一个 timerFunc 这个函数用来执行 callbacks 里存储的所有回调函数
先判断是否原生支持 promise,如果支持,则利用 promise 来触发执行回调函数;
否则,如果支持 MutationObserver,则实例化一个观察者对象,观察文本节点发生变化时,触发执行所有回调函数。 如果都不支持,则利用 setTimeout 设置延时为 0。
复制代码


列表循环时 key 有什么作用?key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速。Vue 的 diff 过程可以概括为:oldCh 和 newCh 各有两个头尾的变量 oldStartIndex、oldEndIndex 和 newStartIndex、newEndIndex,它们会新节点和旧节点会进行两两对比,即一共有 4 种比较方式:newStartIndex 和 oldStartIndex 、newEndIndex 和 oldEndIndex 、newStartIndex 和 oldEndIndex 、newEndIndex 和 oldStartIndex,如果以上 4 种比较都没匹配,如果设置了 key,就会用 key 再进行比较,在比较的过程中,遍历会往中间靠,一旦 StartIdx > EndIdx 表明 oldCh 和 newCh 至少有一个已经遍历完了,就会结束比较。


所以 Vue 中 key 的作用是:key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速,因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快,源码如下:

function createKeyToOldIdx (children, beginIdx, endIdx) {  let i, key  const map = {}  for (i = beginIdx; i <= endIdx; ++i) {    key = children[i].key    if (isDef(key)) map[key] = i  }  return map}复制代码
复制代码

为什么不建议用 index 作为 key?

使用 index 作为 key 和没写基本上没区别,因为不管数组的顺序怎么颠倒,index 都是 0, 1, 2... 这样排列,导致 Vue 会复用错误的旧子节点,做很多额外的工作。

v-if、v-show、v-html 的原理

  • v-if 会调用 addIfCondition 方法,生成 vnode 的时候会忽略对应节点,render 的时候就不会渲染;

  • v-show 会生成 vnoderender 的时候也会渲染成真实节点,只是在 render 过程中会在节点的属性中修改 show 属性值,也就是常说的 display

  • v-html 会先移除节点下的所有节点,设置 innerHTML 为 v-html 的值。

Vue 中封装的数组方法有哪些,其如何实现页面更新

数组就是使用 object.defineProperty 重新定义数组的每一项,那能引起数组变化的方法我们都是知道的,

  • pop 、

  • push 、

  • shift 、

  • unshift 、

  • splice 、

  • sort 、

  • reverse

这七种,只要这些方法执行改了数组内容,我就更新内容就好了,是不是很好理解。

  1. 是用来函数劫持的方式,重写了数组方法,具体呢就是更改了数组的原型,更改成自己的,用户调数组的一些方法的时候,走的就是自己的方法,然后通知视图去更新。

  2. 数组里每一项可能是对象,那么我就是会对数组的每一项进行观测,(且只有数组里的对象才能进行观测,观测过的也不会进行观测)

vue3:改用 proxy ,可直接监听对象数组的变化。

Vue 模板渲染的原理是什么?

vue 中的模板 template 无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的 HTML 语法,所有需要将 template 转化成一个 JavaScript 函数,这样浏览器就可以执行这一个函数并渲染出对应的 HTML 元素,就可以让视图跑起来了,这一个转化的过程,就成为模板编译。

模板编译又分三个阶段,解析 parse,优化 optimize,生成 generate,最终生成可执行函数 render

  • parse 阶段:使用大量的正则表达式对 template 字符串进行解析,将标签、指令、属性等转化为抽象语法树 AST

  • optimize 阶段:遍历 AST,找到其中的一些静态节点并进行标记,方便在页面重渲染的时候进行 diff 比较时,直接跳过这一些静态节点,优化 runtime 的性能。

  • generate 阶段:将最终的 AST 转化为 render 函数字符串。

说一下什么是 Virtual DOM

Virtual DOM 是 DOM 节点在 JavaScript 中的一种抽象数据结构,之所以需要虚拟 DOM,是因为浏览器中操作 DOM 的代价比较昂贵,频繁操作 DOM 会产生性能问题。虚拟 DOM 的作用是在每一次响应式数据发生变化引起页面重渲染时,Vue 对比更新前后的虚拟 DOM,匹配找出尽可能少的需要更新的真实 DOM,从而达到提升性能的目的。

Vue data 中某一个属性的值发生改变后,视图会立即同步执行重新渲染吗?

  • Vue 是异步执行 DOM 更新。

  • 只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。

  • 如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。

  • 然后,在下一个的事件循环 tick 中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MessageChannel,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。

例如,当你设置 vm.someData = 'new value' ,该组件不会立即重新渲染。

  • 当刷新队列时,组件会在事件循环队列清空时的下一个 tick 更新。

  • 多数情况我们不需要关心这个过程,但是如果你想在 DOM 状态更新后做点什么,这就可能会有些棘手。

  • 虽然 Vue.js 通常鼓励开发人员沿着 “数据驱动” 的方式思考,避免直接接触 DOM,但是有时我们确实要这么做。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。

Vue.mixin 的使用场景和原理

在日常的开发中,我们经常会遇到在不同的组件中经常会需要用到一些相同或者相似的代码,这些代码的功能相对独立,可以通过 Vue 的 mixin 功能抽离公共的业务逻辑,原理类似“对象的继承”,当组件初始化时会调用 mergeOptions 方法进行合并,采用策略模式针对不同的属性进行合并。当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。

Vue.extend 作用和原理

其实就是一个子类构造器 ,是 Vue 组件的核心 api 实现思路就是使用原型继承的方法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并。

Vue 事件绑定原理

原生事件绑定是通过 addEventListener 绑定给真实元素的,组件事件绑定是通过 Vue 自定义的 $on 实现的。如果要在组件上使用原生事件,需要加 .native 修饰符,这样就相当于在父组件中把子组件当做普通 html 标签,然后加上原生事件。

on、emit 是基于发布订阅模式的,维护一个事件中心,on 的时候将事件按名称存在事件中心里,称之为订阅者,然后 emit 将对应的事件进行发布,去执行事件中心里的对应的监听器。

虚拟 DOM 实现原理?

虚拟 DOM 的实现原理主要包括以下 3 部分:

  • 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;

  • diff 算法 — 比较两棵虚拟 DOM 树的差异;

  • pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。

虚拟 dom 和真实 dom 的区别

  • 虚拟 DOM 不会进行排版与重绘操作 

  • 虚拟 DOM 就是把真实 DOM 转换为 Javascript 代码

  • 虚拟 DOM 进行频繁修改,然后一次性比较并修改真实 DOM 中需要改的部分,最后并在真实 DOM 中进行排版与重绘,减少过多 DOM 节点排版与重绘损耗

生态相关

vue-router 路由模式有几种?

vue-router 有 3 种路由模式:hashhistoryabstract

  • hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器;

  • history : 依赖 HTML5 History API 和服务器配置。

  • abstract : 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.

路由的 hash 和 history 模式的区别

(1)hash 模式的实现原理

早期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。比如下面这个网站,它的 location.hash 的值为 #search

https://www.word.com#search复制代码
复制代码

hash 路由模式的实现主要是基于下面几个特性:

  • URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;

  • hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制 hash 的切换;

  • 可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;

  • 我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。

(2)history 模式的实现原理

HTML5 提供了 History API 来实现 URL 的变化。其中做最主要的 API 有以下两个:history.pushState() 和 history.repalceState()。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,如下所示:

window.history.pushState(null, null, path);window.history.replaceState(null, null, path);复制代码
复制代码

history 路由模式的实现主要基于存在下面几个特性:

  • pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;

  • 我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);

  • history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。

如何获取页面的 hash 变化

监听 $route 对象

// 监听,当路由发生变化的时候执行watch: { $route: { handler: function(val, oldVal){  console.log(val); },  // 深度观察监听 deep: true}复制代码
复制代码

route 和 router 的区别

$router 是 VueRouter 的实例,在 script 标签中想要导航到不同的 URL,使用 $router.push 方法。返回上一个历史 history 用 $router.to(-1)

$route 为当前 router 跳转对象。里面可以获取当前路由的 name,path,query,parmas 等。

如何定义动态路由?如何获取传过来的动态参数?

可以通过queryparam两种方式

区别:query通过url传参,刷新页面还在;params属性页面不在

params的类型:

  • 配置路由格式: /router/:id

  • 传递的方式:在 path 后面跟上对应的值

  • 传递后形成的路径:/router/123

  • 通过 $route.params.id 获取传递的值

query 的类类型

  • 配置路由格式 :/router 也就是普通配置

  • 传递的方式:对象中使用 query 的 key 作为传递方式

  • 传递后形成的路径 :/route?id=123

  • 通过 $route.query 获取传递的值

Vue-router 导航守卫有哪些

Vue-router 跳转和 location.href 有什么区别

使用 location.href= /url 来跳转,简单方便,但是刷新了页面;

使用 history.pushState( /url ) ,无刷新页面,静态跳转;

引进 router ,然后使用 router.push( /url ) 来跳转,使用了 diff 算法,实现了按需加载,减少了 dom 的消耗。

其实使用 router 跳转和使用 history.pushState() 没什么差别的,因为 vue-router 就是用了 history.pushState() ,尤其是在 history 模式下。

params 和 query 的区别

用法query 要用 path 来引入,params 要用 name 来引入,接收参数都是类似的,分别是 this.$route.query.name 和 this.$route.params.name 。

url 地址显示query 更加类似于我们 ajax 中 get 传参,params 则类似于 post,说的再简单一点,前者在浏览器地址栏中显示参数,后者则不显示。

注意点query 刷新不会丢失 query 里面的数据, params 刷新会丢失 params 里面的数据。

Vuex 的原理

  • Vue 组件会触发 (dispatch)一些事件或动作,也就是 Actions;

  • 在组件中发出的动作,肯定是想获取或者改变数据的,但是在 vuex 中,数据是集中管理的,不能直接去更改数据,所以会把这个动作提交(Commit)到 Mutations 中;

  • 然后 Mutations 就去改变 State 中的数据;

  • 当 State 中的数据被改变之后,就会重新渲染(Render)到 Vue Components 中去,组件展示更新后的数据,完成一个流程。

Vuex 有哪几种属性

有五种,分别

  • State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。

  • Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。

  • Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。

  • Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。

  • Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。

Vuex 和单纯的全局对象有什么区别?

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

  • 不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 mutation

Vuex 中 action 和 mutation 的区别

  • Mutation 专注于修改 State,理论上是修改 State 的唯一途径;Action 业务代码、异步请求。

  • Mutation:必须同步执行;Action:可以异步,但不能直接操作 State

  • 在视图更新时,先触发 actionsactions 再触发 mutation

  • mutation 的参数是 state,它包含 store 中的数据;action 的参数是 context,它是 state 的父级,包含 stategetters等。

为什么 Vuex 的 mutation 中不能做异步操作?

  • Vuex 中所有的状态更新的唯一途径都是 mutation,异步操作通过 Action 来提交 mutation 实现,这样可以方便地跟踪每一个状态的变化,从而能够实现一些工具帮助更好地了解我们的应用。

  • 每个 mutation 执行完成后都会对应到一个新的状态变更,这样 devtools 就可以打个快照存下来。如果 mutation 支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。

Vuex 和 localStorage 的区别

(1)最重要的区别

  • vuex 存储在内存中

  • localstorage 则以文件的方式存储在本地,只能存储字符串类型的数据,存储对象需要 JSON 的 stringify 和 parse 方法进行处理。 读取内存比读取硬盘速度要快

(2)应用场景

  • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。vuex 用于组件之间的传值。

  • localstorage 是本地存储,是将数据存储到浏览器的方法,一般是在跨页面传递数据时使用 。

  • Vuex 能做到数据的响应式,localstorage 不能

(3)永久性

刷新页面时 vuex 存储的值会丢失,localstorage 不会,对于不变的数据可以用 localstorage 可以代替 vuex

Vuex 的严格模式是什么,有什么作用,如何开启?

在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。

在 Vuex.Store 构造器选项中开启,如下

const store = new Vuex.Store({    strict:true,})复制代码
复制代码

如何在组件中批量使用 Vuex 的 getter 属性

使用 mapGetters 辅助函数, 利用对象展开运算符将 getter 混入 computed 对象中

import {mapGetters} from 'vuex'export default{    computed:{        ...mapGetters(['total','discountTotal'])    }}复制代码
复制代码

如何在组件中重复使用 Vuex 的 mutation

使用 mapMutations 辅助函数,在组件中这么使用

import { mapMutations } from 'vuex'methods:{    ...mapMutations({        setNumber:'SET_NUMBER',    })}复制代码
复制代码

Vuex 页面刷新数据丢失怎么解决

  1. 在 created 周期中读取 sessionstorage 中的数据存储在 store 中,此时用 vuex.store 的 replaceState 方法,替换 store 的根状态

  2. 在 beforeunload 方法中将 store.state 存储到 sessionstorage 中。

export default {  name: 'App',  created() {    //在页面加载时读取sessionStorage里的状态信息    if (sessionStorage.getItem("store")) {      this.$store.replaceState(Object.assign({},        this.$store.state, JSON.parse(sessionStorage.getItem("store"))))    }    //在页面刷新时将vuex里的信息保存到sessionStorage里    window.addEventListener("beforeunload", () => {      sessionStorage.setItem("store", JSON.stringify(this.$store.state))    })  }}复制代码
复制代码

3.0 相关

Vue3.0 有什么更新

Vue3.0 defineProperty 和 proxy 的区别

Vue3.x 改用 Proxy 替代 Object.defineProperty。因为 Proxy 可以直接监听对象和数组的变化,并且有多达 13 种拦截方法。

Proxy 与 Object.defineProperty 优劣对比

Proxy 的优势如下:

  • Proxy 可以直接监听对象而非属性;

  • Proxy 可以直接监听数组的变化;

  • Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;

  • Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;

Object.defineProperty 的优势如下:

  • 兼容性好,支持 IE9

Vue 3.0 生命周期有哪些变化?



注意:3.0中的生命周期钩子要比2.X中相同生命周期的钩子要快

Composition API还新增了以下调试钩子函数:但是不怎么常用

  • onRenderTracked

  • onRenderTriggered

Vue 3.0 自定义指令有哪些变化?

先看看Vue2自定义指令的钩子

  • bind:当指令绑定在对应元素时触发。只会触发一次。

  • inserted:当对应元素被插入到 DOM 的父元素时触发。

  • update:当元素更新时,这个钩子会被触发(此时元素的后代元素还没有触发更新)。

  • componentUpdated:当整个组件(包括子组件)完成更新后,这个钩子触发。

  • unbind:当指令被从元素上移除时,这个钩子会被触发。也只触发一次。

 Vue3 中,官方为了更有助于代码的可读性和风格统一,把自定义指令的钩子名称改的更像是组件生命周期,尽管他们是两回事

  • bind => beforeMount

  • inserted => mounted

  • beforeUpdate新的钩子,会在元素自身更新前触发

  • update => 移除

  • componentUpdated => updated

  • beforeUnmount新的钩子,当元素自身被卸载前触发

  • unbind => unmounted

后语

最后祝大家在新的一年里,都能找到满意的工作,升职加薪,赚的盆满钵满!

最后

如果你觉得此文对你有一丁点帮助,点个赞。或者可以加入我的开发交流群:1025263163 相互学习,我们会有专业的技术答疑解惑

如果你觉得这篇文章对你有点用的话,麻烦请给我们的开源项目点点 star:http://github.crmeb.net/u/defu不胜感激 !

PHP 学习手册:https://doc.crmeb.com

技术交流论坛:https://q.crmeb.com

用户头像

CRMEB

关注

还未添加个人签名 2021.11.02 加入

CRMEB就是客户关系管理+营销电商系统实现公众号端、微信小程序端、H5端、APP、PC端用户账号同步,能够快速积累客户、会员数据分析、智能转化客户、有效提高销售、会员维护、网络营销的一款企业应用

评论

发布
暂无评论
金三银四的 Vue 面试准备_CRMEB_InfoQ写作平台