前言

一尾流莺在此祝大家虎年大吉 ~ !

【我的年面试准备】 Vue这一块拿捏了_插槽

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

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

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

基础使用

MVVM、MVC有什么区别

​MVC​​​ 通过分离 ​​Model​​​、​​View​​​ 和 ​​Controller​​ 的方式来组织代码结构。

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

​MVVM​​​ 分为 ​​Model​​​、​​View​​​、​​ViewModel​​。

  • ​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​​​ 页面初始化时加载相应的 ​​HTML​​​、​​JavaScript​​​ 和 ​​CSS​​​。一旦页面加载完成,​​SPA​​​ 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 ​​HTML​​​ 内容的变换,​​UI​​ 与用户的交互,避免页面的重新加载。

优点:

  • 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
  • 有利于前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;

缺点:

  • 初次加载耗时多:为实现单页​​Web​​​ 应用功能及显示效果,需要在加载页面的时候将​​JavaScript​​​、​​CSS​​ 统一加载,部分页面按需加载;
  • 不利于​​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.xxx​​​,​​xxx​​​ 为插槽名,当组件执行渲染函数时候,遇到 ​​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)服务端渲染的优点:

  • 更好的​​SEO​​​:​​SSR​​ 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;
  • 首屏加载更快:​​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的首屏加载性能优化有哪些

优化前的大小

【我的年面试准备】 Vue这一块拿捏了_前端_02

1.图片优化

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

2.禁止生成.map文件

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

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

【我的年面试准备】 Vue这一块拿捏了_插槽_03

3.路由懒加载

【我的年面试准备】 Vue这一块拿捏了_数组_04

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还是很大

【我的年面试准备】 Vue这一块拿捏了_插槽_05

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这一块拿捏了_插槽_06

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()​​​ 之后触发的第一个钩子,在当前阶段​​data​​​、​​methods​​​、​​computed​​​ 以及​​watch​​ 上的数据和方法都不能被访问。
  • ​created​​​:在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发​​updated​​​ 函数。可以做一些初始数据的获取,在当前阶段无法与​​Dom​​​ 进行交互,如果非要想,可以通过​​vm.$nextTick​​​ 来访问​​Dom​​。
  • ​beforeMount​​​:发生在挂载之前,在这之前​​template​​​ 模板已导入渲染函数编译。而当前阶段虚拟​​Dom​​​ 已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发​​updated​​。
  • ​mounted​​​:在挂载完成后发生,在当前阶段,真实的​​Dom​​​ 挂载完毕,数据完成双向绑定,可以访问到​​Dom​​​ 节点,使用​​$refs​​​ 属性对​​Dom​​ 进行操作。
  • ​beforeUpdate​​​:发生在更新之前,也就是响应式数据发生更新,虚拟​​dom​​ 重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染。
  • ​updated​​​:发生在更新完成之后,当前阶段组件​​Dom​​ 已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新。
  • ​beforeDestroy​​:发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器。
  • ​destroyed​​​:发生在实例销毁之后,这个时候只剩下了​​dom​​ 空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。

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

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

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

  • 能更快获取到服务端数据,减少页面​​loading​​ 时间;
  • ​ssr​​​ 不支持​​beforeMount​​​ 、​​mounted​​​ 钩子函数,所以放在​​created​​ 中有助于一致性;

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

​beforeCreate​​​,​​created​​​,​​beforeMount​​​,​​mounted​

哪个钩子可以进行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这一块拿捏了_插槽_07

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

是 ​​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​​​ 指令在表单 ​​input​​​、​​textarea​​​、​​select​​​ 等元素上创建双向数据绑定,我们知道 ​​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 全局组件和局部组件注册的方式?

  • Vue2:​​Vue.component()​
  • Vue3:​​app.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.vue
mounted() {
this.$emit("mounted");
}

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

//  Parent.vue
<Child @hook:mounted="doSomething" ></Child>

doSomething() {
console.log('父组件监听到 mounted 钩子函数 ...');
},

// Child.vue
mounted(){
console.log('子组件触发 mounted 钩子函数 ...');
},

// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ...

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

原理相关

Vue初始化时都做了什么?

​【源码学习】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 响应式原理的核心 , 处理 ​​props​​​、​​methods​​​、​​computed​​​、​​data​​​、​​watch​​ 等。

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

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

第四部分

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

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

数据响应式的原理

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

  1. 使用​​observe​​​ 对需要响应式的数据进行递归,将对像的所有属性及其子属性,都加上​​setter​​​ 和​​getter​​​ 这样的话,给这个对象的某个属性赋值的时候,就会触发​​setter​​,那么就能监听到了数据变化。
  2. ​compile​​ 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。
  3. ​Watcher​​​ 订阅者是​​Observer​​​ 和​​Compile​​ 之间通信的桥梁,主要做的事情是:
  • 在自身实例化时往属性订阅器(​​dep​​)里面添加自己
  • 自身必须有一个​​update()​​ 方法
  • 待属性变动触发​​dep.notice()​​​ 通知时,能调用自身的​​update()​​​ 方法,并触发​​Compile​​ 中绑定的回调,完成视图更新。

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

【我的年面试准备】 Vue这一块拿捏了_前端_08

使用 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.set
Vue.set(vm.items, indexOfItem, newValue)
// vm.$set,Vue.set的一个别名
vm.$set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

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

// Array.prototype.splice
vm.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 会生成 ​​vnode​​,​​render​​ 的时候也会渲染成真实节点,只是在 ​​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 种路由模式:​​hash​​​、​​history​​​、​​abstract​​:

  • 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​​ 等。

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

可以通过​​query​​​,​​param​​两种方式

区别:​​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有几种钩子函数?具体是什么及执行流程是怎样的?​

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​​。
  • 在视图更新时,先触发 ​​actions​​,​​actions​​ 再触发 ​​mutation​
  • ​mutation​​ 的参数是 ​​state​​,它包含 ​​store​​ 中的数据;​​action​​ 的参数是 ​​context​​,它是 ​​state​​ 的父级,包含 ​​state​​、​​getters​​等。

为什么 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​​​ 的大多都在这里了。 ​​​【初学者笔记】整理的一些Vue3知识点​

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 生命周期有哪些变化?

【我的年面试准备】 Vue这一块拿捏了_面试_09

注意:​​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

后语

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