Web 面试之 Vue框架

Web 面试之 Vue框架


前言

1、前端常见面试流程

一面:基础知识

  • JS 基础知识
  • 框架基本使用

二面:高级特性 + 原理

  • 框架的高级特性
  • 框架原理

三面:设计 + 经验

  • 项目设计能力
  • 工作经验和环境

2、知识点介绍

框架面试:

  • Vue 基本使用
  • Vue 高级特性
  • Vue 原理

工具面试:

  • Webpack 配置
  • 性能优化
  • Babel

项目设计:

  • 状态设计
  • 组件设计
  • 组件通讯

一、Vue 使用

1、vue-cli 或 @vue/cli 创建项目

vue create 项目名

2、基本使用

(1)模版(插值,指令)

  • 插值、表达式:模版 {{ }} 里不能是 js 语句
    插值:{{value}}
    表达式:{{ flag ? ‘yes’ : ‘no’}}
  • 指令、动态属性
    v-bind 指令,可以简写为“:”
    动态属性: <p :id=“dynamicId”></p>
  • v-html:会有 XSS(XSS 全称跨站脚本攻击, 是Web程序中最常见的漏洞) 风险,会覆盖子组件
<p> v-html="rawHtml">
	<span>有 XSS 风险</span>
    <span>【注意】 使用 v-html 之后,将会覆盖子元素</span>
</P>
data() {
	return {
		rawHtml: '指令 - 原始 html <b>加粗</b> <i>斜体</i>'
	}
}

(2)computed 和 watch

  • computed 有缓存,如果 data 中 computed 用到的数据没有发生变化则不会重新计算
  • watch 如何深度监听?
    • watch 监听引用类型,拿不到 oldValue
return {
	info: {
		city: 'nj'
	}
}

watch: {
	info: {
		handler(oldVal, currentVal) {
			// 引用类型,拿不到 oldVal
			console.log(oldVal, currentVal)
		},
		// 设置 deep 为true
		deep: true
	}
}

(3)class 和 style

  • 使用动态属性
  • 使用驼峰式写法
<div>
	<p :class="{ black: isBlack, yellow: isYellow }">使用 class</p>
	<p :class="[black,yellow]">使用 class (数组)</p>
	<p :style="styleData">使用 style</p>
</div>

data() {
	return {
		isBlack: true,
		isYellow: true,
		black: 'black',
		yellow: 'yellow',
		styleData: {
			fontSize: '40px',         // 转为驼峰式
			color: 'red',
			backgroundColor: '#ccc'   // 转为驼峰式
		}
	}
}

(4)条件(v-if 和 v-show)

  • v-if v-else 的用法。可使用变量,也可以使用 === 表达式
  • v-if 和 v-show 的区别?
    v-if是通过控制dom节点的存在与否来控制元素的显隐;v-show是通过设置DOM元素的display样式,block为显示,none为隐藏
  • v-if 和 v-show 的使用场景?
    如果条件更新不频繁,使用 v-if;如果频繁切换,使用 v-show

(5)循环

  • 如何遍历对象?
    也可以用 v-for
  • key 的重要性。key 不能乱写为 random 或者 index
  • v-for 和 v-if 不能一起使用!v-for 的优先级更高一些

(6)事件

  • event 参数,自定义参数
// 没有传参
<button @click="increment1">+1</button>
// 传参
<button @click="increment2(2, $event)">+2</button>

methods: {
	increament1(event) {},
	increament2(num, event) {}
}
  • 事件修饰符,按键修饰符
    事件修饰符:stop、prevent、capture、self
    按键修饰符:ctrl、exact
  • 【观察】事件被绑定到哪里?

(7)表单

  • v-model
  • 常见表单项:textarea、checkbox、radio、select
  • 表单相关修饰符:lazy、number、trim

3、Vue 组件使用

  • props 和 $emit
  • 组件间通信 - 自定义事件
  • 组件生命周期

(1)生命周期

(2)props(类型和默认值)

(3)v-on 和 $emit

(4)自定义事件

二、Vue 原理

1、Vue 原理考察哪些

  • 面试为何会考察原理?
    知其然知其所以然
    了解原理,才能更好的应用
    大厂造轮子(针对业务进行定制)
  • 面试如何考察?以何种方式?
    考察重点,而不是考察细节
    考察和使用相关的原理,例如 vdom(虚拟dom)、模版渲染
    整体流程是否全面?热门技术是否有深度?
  • Vue 原理包括哪些?
    组件化
    响应式
    vdom 和 diff 算法
    模版编译
    渲染过程
    前端路由

2、如何理解 MVVM

  • MVVM 分为三个部分:
    M(Model):模型层,主要负责业务数据相关;
    V(View):视图层,顾名思义,负责视图相关,细分下来就是html+css层;
    VM(ViewModel):Model 与 View 沟通的桥梁,也可以看作是控制器,负责监听 Model 或者 View 的修改,是实现 MVVM 双向绑定的要点;
  • MVVM支持双向绑定,意思就是当M层数据进行修改时,VM层会监测到变化,并且通知V层进行相应的修改,反之修改V层则会通知M层数据进行修改,以此也实现了视图与模型层的相互解耦。
  • 关系图
    图片来源于网络

3、Vue 响应式

  • 组件 data 的数据一旦变化,立即触发视图的更新
  • 实现数据驱动视图的第一步
  • 考察 Vue 原理的第一题

()

()

()

()

()

三、Vue3

四、Vue 面试真题

1、v-show 和 v-if 的区别?

2、为何 x-for 中要用 key ?

3、描述 Vue 组件生命周期(有父子组件的情况)?

4、Vue 组件如何通信?

5、描述组件渲染和更新的过程?

6、数据双向绑定 v-model 的实现原理?

2、MVC MVVM 的区别

MVC

MVC是应用最广泛的软件架构之一,一般MVC分为:Model(模型),View(视图),Controller(控制器)。 这主要是基于分层的目的,让彼此的职责分开。View一般用Controller来和Model进行联系。Controller是Model和View的协调者,View和Model不直接联系。基本都是单向联系。M和V指的意思和MVVM中的M和V意思一样。C即Controller指的是页面业务逻辑。MVC是单向通信。也就是View跟Model,必须通过Controller来承上启下。

MVVM

在MVVM框架下视图和模型是不能直接通信的,只能通过ViewModel进行交互,它能够监听到数据的变化,然后通知视图进行自动更新,而当用户操作视图时,VM也能监听到视图的变化,然后通知数据做相应改动,这实际上就实现了数据的双向绑定。并且V和VM可以进行通信。

区别

MVC和MVVM的区别并不是VM完全取代了C,ViewModel存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现。也就是说MVVM实现的是业务逻辑组件的重用。

MVC中Controller演变成MVVM中的ViewModel

MVVM通过数据来显示视图层而不是节点操作

MVVM主要解决了MVC中大量的dom操作使页面渲染性能降低,加载速度变慢,影响用户体验

4、说说你对单页应用SPA的理解?有什么优缺点?

单页应用

单页应用又称 SPA(Single Page Application)指的是使用单个 HTML 完成多个页面切换和功能的应用。这些应用只有一个 html 文件作为入口,一开始只需加载一次 js,css 等相关资源。使用 js 完成页面的布局和渲染。页面展示和功能室根据路由完成的。单页应用跳转,就是切换相关组件,仅刷新局部资源。

多页应用

多页应用又称 MPA(Multi Page Application)指有多个独立的页面的应用,每个页面必须重复加载 js,css 等相关资源。多页应用跳转,需要整页资源刷新。

区别

单页应用优点:

具有桌面应用的即时性、网站的可移植性和可访问性

用户体验好、快,内容的改变不需要重新加载整个页面

良好的前后端分离,分工更明确

单页应用缺点:

1.首次渲染速度相对较慢:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载

2.前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理

3.SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势

7、Vue实例挂载过程

1.new Vue的时候调用会调用_init方法

定义 s e t 、 set、 setget 、 d e l e t e 、 delete、 deletewatch 等方法

定义 o n 、 on、 onoff、 e m i t 、 emit、 emitoff等事件

定义 _update、 f o r c e U p d a t e 、 forceUpdate、 forceUpdatedestroy生命周期

2.调用$mount进行页面的挂载

3.挂载的时候主要是通过mountComponent方法

4.定义updateComponent更新函数

5.执行render生成虚拟DOM

6._update将虚拟DOM生成真实DOM结构,并且渲染到页面中

9、vue的生命周期

vue实例从创建到销毁的整个过程就是生命周期。

beforeCreate:组件实例被创建之初

在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。

执行时组件实例还未创建,通常用于插件开发中执行一些初始化任务

created:组件实例已经完全创建

在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。

组件初始化完毕,各种数据可以使用,常用于异步数据获取

beforeMount:组件挂载之前

在挂载开始之前被调用:相关的 render 函数首次被调用。(该钩子在服务器端渲染期间不被调用)

未执行渲染、更新,dom未创建

mounted:组件挂载到实例上去之后

el 被新创建的 vm. e l 替 换 , 并 挂 载 到 实 例 上 去 之 后 调 用 该 钩 子 。 如 果 r o o t 实 例 挂 载 了 一 个 文 档 内 元 素 , 当 m o u n t e d 被 调 用 时 v m . el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm. elrootmountedvm.el 也在文档内。

初始化结束,dom已创建,可用于获取访问数据和dom元素

beforeUpdate:组件数据发生变化,更新之前

数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。(该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务端进行。)

更新前,可用于获取更新前各种状态

updated:组件数据更新之后

由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。

当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。

更新后,所有状态已是最新

beforeDestory:组件实例销毁之前

实例销毁之前调用。在这一步,实例仍然完全可用。(该钩子在服务器端渲染期间不被调用)

销毁前,可用于一些定时器或订阅的取消

destroyed:组件实例销毁之后

Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。(该钩子在服务器端渲染期间不被调用)

组件已销毁

11、SPA首屏加速慢怎么解决

减小入口文件积

常用的手段是路由懒加载,把不同路由对应的组件分割成不同的代码块,待路由被请求的时候会单独打包路由,使得入口文件变小,加载速度大大增加

routes: [
  path: 'Blogs',
  name: 'ShowBlogs',
  component: () => import('./components/ShowBlogs.vue')
]

静态资源本地缓存

后端返回资源采用HTTP缓存,设置Cache-Control,Last-Modified,Etag等响应头

前端合理利用localStorage

UI框架按需加载

在日常使用UI框架,例如element-UI,我们经常性直接饮用整个UI库,但实际上我用到的组件只有按钮,分页,表格,输入与警告 所以我们要按需引用

import { Button, Input, Pagination, Table, TableColumn, MessageBox } from "element-ui "
Vue.use(Button)
Vue.use(Input)
Vue.use(Pagination)
图片资源的压缩

图片资源虽然不在编码过程中,但它却是对页面性能影响最大的因素。对于所有的图片资源,我们可以进行适当的压缩。对页面上使用到的icon,可以使用在线字体图标,或者雪碧图,将众多小图标合并到同一张图上,用以减轻http请求压力

组件重复打包

假设A.js文件是一个常用的库,现在有多个路由使用了A.js文件,这就造成了重复下载

解决方案:在webpack的config文件中,修改CommonsChunkPlugin的配置。设置minChunks为3表示会把使用3次及以上的包抽离出来,放进公共依赖文件,避免了重复加载组件

minChunks: 3
开启GZip压缩

拆完包之后,我们再用gzip做一下压缩 安装compression-webpack-plugin

使用SSR

SSR(Server side ),也就是服务端渲染,组件或页面通过服务器生成html字符串,再发送到浏览器。从头搭建一个服务端渲染是很复杂的,vue应用建议使用Nuxt.js实现服务端渲染

12、data属性为何是函数而不是一个对象

根实例对象data可以是对象也可以是函数(根实例是单例),不会产生数据污染情况

组件实例对象data必须为函数,目的是为了防止多个组件实例对象之间共用一个data,产生数据污染。采用函数的形式,initData时会将其作为工厂函数都会返回全新data对象

13、Vue给对象添加新属性界面不刷新

原因是一开始data所有的属性通过Object.defineProperty设置成了响应式数据,如果是后面新增的属性,并没有通过Object.defineProperty设置成响应式数据,所有新增属性数据更新了,但是页面不刷新

解决方法

Vue.set():使用Vue.set( target, propertyName/index, value )给对象添加新属性,这个方法再次调用了Object.defineProperty方法,实现新增属性的的响应式

Object.assign():创建一个新的对象,合并原对象和混入对象的属性

$forceUpdated():强制刷新

14、Vue组件和插件的区别

组件

组件就是把图形、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式,在Vue中每一个.vue文件都可以视为一个组件

插件

插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制

编写形式不同

组件:编写一个组件,常见的就是vue单文件的这种格式,每一个.vue文件我们都可以看成是一个组件

插件:vue插件的实现应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象

注册形式不同

组件: vue组件注册主要分为全局注册与局部注册。全局注册通过Vue.component方法,第一个参数为组件的名称,第二个参数为传入的配置项,局部注册只需在用到的地方通过components属性注册一个组件

插件:插件的注册通过Vue.use()的方式进行注册(安装),第一个参数为插件的名字,第二个参数是可选择的配置项

使用场景不同

组件:组件 (Component) 是用来构成 App 的业务模块,它的目标是 App.vue

插件:插件 (Plugin) 是用来增强你的技术栈的功能模块,它的目标是 Vue 本身,简单来说,插件就是指对Vue的功能的增强或补充

15、Vue组件通信方式

vue中8种通信方案

1.通过 props 传递

2.通过 $emit 触发自定义事件

3.使用 ref

4.EventBus

5. p a r e n t 或 parent 或 parentroot

6.attrs 与 listeners

7.Provide 与 Inject

8.Vuex

16、props传递数据

适用场景:父组件传递数据给子组件

子组件设置props属性,定义接收父组件传递过来的参数

父组件在使用子组件标签中通过字面量来传递值
$emit 触发自定义事件

适用场景:子组件传递数据给父组件

子组件通过 e m i t 触 发 自 定 义 事 件 , emit触发自定义事件, emitemit第二个参数为传递的数值

父组件绑定监听器获取到子组件传递过来的参数

ref

父组件在使用子组件的时候设置ref

父组件通过设置子组件ref来获取子组件数据

EventBus

使用场景:兄弟组件传值

创建一个中央事件总线EventBus

兄弟组件通过 e m i t 触 发 自 定 义 事 件 , emit触发自定义事件, emitemit第二个参数为传递的数值

另一个兄弟组件通过$on监听自定义事件

//创建一个中央事件总线类 Bus.js
class Bus {
  constructor() {
    this.callbacks = {}; // 存放事件的名字
  }
  $on(name, fn) {
    this.callbacks[name] = this.callbacks[name] || [];
    this.callbacks[name].push(fn);
  }
  $emit(name, args) {
    if (this.callbacks[name]) {
      this.callbacks[name].forEach((cb) => cb(args));
    }
  }
}
// main.js
Vue.prototype.$bus = new Bus() // 将$bus挂载到vue实例的原型上
//另一种方式
Vue.prototype.$bus = new Vue() // Vue已经实现了Bus的功能

// Children1.vue
this. b u s . bus. bus.emit(‘foo’)
// Children2.vue
this. b u s . bus. bus.on(‘foo’, this.handle)
p a r e n t 或 parent 或 parent root

通过共同祖辈 p a r e n t 或 者 parent或者 parentroot搭建通信侨联

兄弟组件:this.$parent.on(‘add’,this.add)

另一个兄弟组件:this.$parent.emit(‘add’)
a t t r s 与 attrs 与 attrs listeners

适用场景:祖先传递数据给子孙

设置批量向下传属性$attrs和 $listeners

包含了父级作用域中不作为 prop 被识别 (且获取) 的特性绑定 ( class 和 style 除外)。

可以通过 v-bind="$attrs" 传⼊内部组件
provide 与 inject

在祖先组件定义provide属性,返回传递的值

在后代组件通过inject接收组件传递过来的值

vuex

适用场景: 复杂关系的组件数据传递

Vuex作用相当于一个用来存储共享变量的容器

小结

父子关系的组件数据传递选择 props 与 $emit进行传递,也可选择ref

兄弟关系的组件数据传递可选择 b u s , 其 次 可 以 选 择 bus,其次可以选择 busparent进行传递

祖先与后代组件数据传递可选择attrs与listeners或者 Provide与 Inject

复杂关系的组件数据传递可以通过vuex存放共享的变量

17、Vuex介绍

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

核心流程中的主要功能

  1. Vue Components是我们的vue组件,组件会触发( dispatch)一些事件或动作,也就是图中的Actions

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

3.然后Mutations就去改变 State中的数据

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

各模块在核心流程中的主要功能

1.Vue Components: Vue组件,HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应

2.dispatch:操作行为触发方法,是唯一能执行action的方法

3.actions: 操作行为处理模块,负责处理VueComponents接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以歧持action的链式触发

4.commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法

5.mutations:状态改变操作方法。是Vuex修改state的唯一推荐方法,其他修改方式在严格模式下将会报错。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等

6.state:页面状态管理容器对象。集中存储Vuecomponents中data对象的零散数据,全局唯一, 以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新

7.getters: state对象读取方法。图中没有单独列出该模块,应该被包含在了render中, Vue Components通过该方法读取全局state对象

store拆分

在项目比较复杂的时候,数据全部写在一个state 方法全部集中一个mutations中,将会使我们的文件显得太过于臃肿,而且不易维护,那怎么办呢?还是那句话办法总比问题多,vuex为我们提供了module这样一个模块的概念。我们可以利用它来根据我们个个组件或者页面所需要的数据一一分割成不同的模块

const moduleA = {
  state: {},
  mutations: {},
  actions: {},
  getters: {}
}

const moduleB = {
  state: {},
  mutations: {},
  actions: {},
  getters: {}
}

export default new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

// 在各自的模块内部使用
state.price // 这种使用方式和单个使用方式-样,直接使用就行

// 在组件中使用
store.state.a.price // 先找到模块的名字,再去调用厢性
store.state.b.price // 先找到模块的名字,再去调用属性

vuex的原理

Vuex是通过全局注入store对象,来实现组件间的状态共享。store注入 vue的实例组件的方式,是通过vue的 mixin机制,借助vue组件的生命周期 钩子 beforeCreate 完成的。即 每个vue组件实例化过程中,会在 beforeCreate 钩子前调用 vuexInit 方法。

Vue.mixin({
  beforeCreate() {
    if (this.$options && this.$options.store) {
      // 找到根组件main上面挂一个$store
      this.$store = this.$options.store
      // console.log(this.$store);
    } else {
      // 非根组件指向其父组件的$store
      this.$store = this.$parent && this.$parent.$store
    }
  }
})

在大型复杂的项目中(多级组件嵌套),需要实现一个组件更改某个数据,多个组件自动获取更改后的数据进行业务逻辑处理,这时候使用vuex比较合适。假如只是多个组件间传递数据,使用vuex未免有点大材小用,其实只用使用组件间常用的通信方法即可。

18、vuex的action和mutation的区别

Mutation

在vuex的严格模式下, Mutaion是vuex中改变State的唯一途径

Mutation 中只能是同步操作

通过store.commit()调用Mutation

Action

一些对State的异步操作可放在Action中,并通过在Action中commit调用 Mutation变更状态

通过store.dispatch() 方法触发,或者通过mapActions辅助函数将vue组件的methods映射成store.dispatch()调用

总结

mutations 可以直接修改state ,但只能包含同步操作,同时,只能通过提交commit调用。actions是用来触发mutations的,它无法直接改变state ,它可以包含异步操作,它只能通过store.dispatch触发

19、双向绑定原理

vue是通过数据劫持结合发布者-订阅者模式的方式来实现数据双向绑定的,通过Object.defineProperty()方法来劫持对象属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

viewModel主要职责

数据变化后更新视图

视图变化后更新数据

viewModel两个主要部分组成

监听器(Observer):对所有数据的属性进行监听

解析器(Compiler):对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数

具体步骤:

第一步:对需要observe的数据对象data进行递归遍历,包括子属性对象的属性,都加上 setter和getter,这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化

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

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

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

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

3.待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。

第四步:MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

总结

1.new Vue()首先执行初始化,对data执行响应化处理,这个过程发生Observe中

2.同时对模板执行编译,找到其中动态绑定的数据,从data中获取并初始化视图,这个过程发生在Compile中。同时定义⼀个更新函数和Watcher,将来对应数据变化时Watcher会调用更新函数

3.由于data的某个key在⼀个视图中可能出现多次,所以每个key都需要⼀个管家Dep来管理多个Watcher。将来data中数据⼀旦发生变化,会首先找到对应的Dep,通知所有Watcher执行更新函数

20、vue2. x中如何监测数组变化

通过Object.defineProperty()劫持数组为其设置getter和setter后,调用的数组的push、splice、pop等方法改变数组元素时并不会触发数组的setter,这就会造成使用上述方法改变数组后,页面上并不能及时体现这些变化,也就是数组数据变化不是响应式的,那么vue中是如何实现的呢?

vue重写了数组的push、pop、shift、unshift、splice、sort、reverse七种方法,重写方法在实现时除了将数组方法名对应的原始方法调用一遍并将执行结果返回外,还通过执行ob.dep.notify()将当前数组的变更通知给其订阅者,这样当使用重写后方法改变数组后,数组订阅者会将这边变化更新到页面中。

21、对nextTick的理解?

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM

我们可以理解成,Vue 在更新 DOM 时是异步执行的。当数据发生变化,Vue将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新,如果想要在修改数据后立刻得到更新后的DOM结构,可以使用Vue.nextTick()

22、对mixin混入的理解

mixin(混入),提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能

本质其实就是一个js对象,它可以包含我们组件中任意功能选项,如data、components、methods、created、computed等等。我们只要将共用的功能以对象的方式传入 mixins选项中,当组件使用 mixins对象时所有mixins对象的选项都将被混入该组件本身的选项中来

合并策略

替换型:同名的props、methods、inject、computed会被后来者代替

合并型:data,通过set方法进行合并和重新赋值

队列型:生命周期钩子和watch被合并为一个数组,然后正序遍历依次执行

叠加型:component、directives、filters,通过原型链进行层层的叠加

23、对slot的理解?

通俗易懂的讲,slot具有“占坑”的作用,在子组件占好了位置,那父组件使用该子组件标签时,新添加的DOM元素就会自动填到这个坑里面

默认插槽:子组件用标签来确定渲染的位置,父组件在使用的时候,直接在子组件的标签内写入内容即可

具名插槽:给子组件占的每一个坑取名,将父组件添加的HTML元素添加到指定名字的坑,就实现了分发内容在不同位置显示

作用域插槽:子组件在作用域上绑定属性来将子组件的信息传给父组件使用,这些属性会被挂在父组件v-slot接受的对象上

24、v-for为何要加key关键字

key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确, 更快的找到对应的vnode节点

25、说说对keep-alive缓存组件的理解

keep-alive是vue中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM, keep-alive 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们,它自身不会渲染一个DOM元素,也不会出现在父组件链中

keep-alive可以设置以下props属性:

include : 字符串或正则表达式,只有名称匹配的组件会被缓存

Exclude: 字符串或正则表达式,任何名称匹配的组件都不会被缓存

max : 数字,最多可以缓存多少组件实例

activated和deactivated两个生命周期函数

activated:当组件激活时,钩子触发的顺序是created->mounted->activated

deactivated: 组件停用时会触发deactivated,当再次前进或者后退的时候只触发activated

26、Vue常用修饰符

表单修饰符

lazy:在我们填完信息,光标离开标签的时候,才会将值赋予给value,也就是在change事件之后再进行信息同步

trim: 自动过滤用户输入的首尾空格字符,而中间的空格不会过滤

number:自动将用户的输入值转为数值类型,但如果这个值无法被parseFloat解析,则会返回原来的值

事件修饰符

stop:阻止了事件冒泡,相当于调用了event.stopPropagation方法

prevent:阻止了事件的默认行为,相当于调用了event.preventDefault方法

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

once:绑定了事件以后只能触发一次,第二次就不会触发

capture:使事件触发从包含这个元素的顶层开始往下触发

passive:在移动端,当我们在监听元素滚动事件的时候,会一直触发onscroll事件会让我们的网页变卡,因此我们使用这个修饰符的时候,相当于给onscroll事件整了一个.lazy修饰符

native:让组件变成像html内置标签那样监听根元素的原生事件,否则组件上使用 v-on 只会监听自定义事件

鼠标按键修饰符

left:左键点击

right:右键点击

middle:中键点击

键值修饰符

普通键:(enter、tab、delete、space、esc、up…)

系统修饰键:(ctrl、alt、meta、shift…)

v-bind修饰符

async:能对props进行一个双向绑定

prop:设置自定义标签属性,避免暴露数据,防止污染HTML结构

camel:将命名变为驼峰命名法,如将view-Box属性名转换为 viewBox

27、项目中有用过自定义指令吗?

项目中定义一个auth自定义指令来显示隐藏页面元素,从而实现按钮权限

// util/auth.js
import store from "@/store";
export default {
  bind(el, binding, vnode) {
    let auths = store.state.loginInfo.roleBtns; // 后台返回的按钮权限列表
    let hasAuth = auths.includes(binding.value);
    if (hasAuth) {
      delete el.style.display;
    } else {
      el.style.display = "none";
    }
  }
}

全局注册

import Vue from 'vue'
import App from './App.vue'
import router from './router '
import store from './store'
import iview from 'iview'
import 'iview/dist/styles/iview.css'
import auth from '@/util/auth'; // 引入
Vue.config.productionTip = false
Vue.use(iview)
Vue.directive("auth", auth); // 注册
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

通过自定义指令v-auth绑定字段,当绑定的字段包含在后端返回的roleBtns列表里,那么该按钮会显示,否则隐藏

28、过滤器filter的应用场景

在Vue中使用过滤器(Filters)来渲染数据是一种很有趣的方式。过滤器不改变真正的data,而只是改变渲染的结果,并返回过滤后的版本。局部过滤器优先于全局过滤器被调用。一个表达式可以使用多个过滤器。过滤器之间需要用管道符“|”隔开。其执行顺序从左往右

定义

组件内定义局部过滤器

filters: {
  capitalize: function (value) {
    if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase + value.slice(1)
  }
}

vue全局过滤器

Vue.filter('capitalize', function (value) {
  if (!value) return
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

new Vue({
  //...
})

使用

{{message| capitalize}}

应用场景

单位转换、数字打点、文本格式化、时间格式化之类的等

29、什么是虚拟dom,如何实现一个虚拟dom

定义

它只是一层对真实DOM的抽象,以JavaScript 对象 (VNode 节点) 作为基础的树,用对象的属性来描述节点,最终可以通过一系列操作使这棵树映射到真实环境上

在Javascript对象中,虚拟DOM 表现为一个 Object对象。并且最少包含标签名 (tag)、属性 (attrs) 和子元素对象 (children) 三个属性,不同框架对这三个属性的名命可能会有差别

创建虚拟DOM就是为了更好将虚拟的节点渲染到页面视图中,所以虚拟DOM对象的节点与真实DOM的属性一一照应

为何需要虚拟DOM

真正的 DOM 元素非常庞大,由此可以得知DOM是很慢的。而且操作它们的时候你要小心翼翼,轻微的触碰可能就会导致页面重排。浏览器里一遍又一遍的渲染DOM是非常非常消耗性能的,常常会出现页面卡死的情况,影响用户的体验

得益于V8引擎的出现,让JavaScript可以高效地运行,在性能上有了极大的提高。相对于 DOM 对象,原生的 JavaScript 对象处理起来更快,而且更简单。DOM 树上的结构、属性信息我们都可以很容易地用 JavaScript 对象表示出来,为Virtual DOM的产生提供了大前提。

虚拟DOM的优势

很多人认为虚拟 DOM 最大的优势是 diff 算法,减少 JavaScript 操作真实 DOM 的带来的性能消耗。虽然这一个虚拟 DOM 带来的一个优势,但并不是全部。虚拟 DOM 最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力,而不仅仅局限于浏览器的 DOM,可以是安卓和 IOS 的原生组件,可以是近期很火热的小程序,也可以是各种GUI

30、你了解vue的diff算法吗

定义

diff 算法是一种通过同层的树节点进行比较的高效算法

diff整体策略为:深度优先,同层比较

两个特点

比较只会在同层级进行, 不会跨层级比较

在diff比较的过程中,循环从两边向中间比较

Vue中当数据发生改变时,订阅者watcher就会调用patch给真实的DOM打补丁

通过isSameVnode进行判断,相同则调用patchVnode方法

patchVnode做了以下操作:

找到对应的真实dom,称为el

如果都有都有文本节点且不相等,将el文本节点设置为Vnode的文本节点

如果oldVnode有子节点而VNode没有,则删除el子节点

如果oldVnode没有子节点而VNode有,则将VNode的子节点真实化后添加到el

如果两者都有子节点,则执行updateChildren函数比较子节点

updateChildren主要做了以下操作:

设置新旧VNode的头尾指针

新旧头尾指针进行比较,循环向中间靠拢,根据情况调用patchVnode进行patch重复流程、调用createElem创建一个新节点,从哈希表寻找 key一致的VNode 节点再分情况操作

30、Axios是什么?有封装过axios吗?

定义

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中

特点

支持从浏览器中创建 XMLHttpRequests

支持从 node.js 创建 http 请求

支持 Promise API

能拦截请求和响应

能转换请求数据和响应数据

能取消请求

自动转换 JSON 数据

客户端支持防御 CSRF

为什么要封装

axios 的 API 很友好,你完全可以很轻松地在项目中直接使用。

不过随着项目规模增大,如果每发起一次HTTP请求,就要把这些比如设置超时时间、设置请求头、根据项目环境判断使用哪个请求地址、错误处理等等操作,都需要写一遍,这种重复劳动不仅浪费时间,而且让代码变得冗余不堪,难以维护。为了提高我们的代码质量,我们应该在项目中二次封装一下 axios 再使用

如何封装

封装的同时,需要和后端协商好一些约定,请求头,状态码,请求超时时间…

设置接口请求前缀:根据开发、测试、生产环境的不同,前缀需要加以区分

请求头:来实现一些具体的业务,必须携带一些参数才可以请求(例如:会员业务)

状态码:根据接口返回的不同status ,来执行不同的业务,这块需要和后端约定好

请求方法:根据get、post等方法进行一个再次封装,使用起来更为方便

请求拦截器:根据请求的请求头设定,来决定哪些请求可以访问

响应拦截器:这块就是根据 后端返回来的状态码判定执行不同业务

31、Vue权限管理

接口权限

登录页面进行登录,登录完拿到token,将token存起来,通过axios请求拦截器进行拦截,每次请求的时候头部携带token,如果token失效,后端返回约定好的状态码,前端在axios响应拦截进行拦截,重新跳转到登录页进行登录

菜单权限

前端定义路由信息,路由的name字段都不为空,根据路由name字段与后端返回菜单列表做关联,后端返回的菜单信息中必须要包含name对应的字段才展示对应菜单项

32、Vue项目是如何解决跨域的

如果是通过vue-cli脚手架工具搭建项目,我们可以通过webpack为我们起一个本地服务器devServer作为请求的代理对象。通过该服务器转发请求至目标服务器,得到结果再转发给前端,但是最终发布上线时如果web应用和接口服务器不在一起仍会跨域在vue.config.js文件,新增以下代码

module.exports = {
  devServer: {
    host: '127.0.0.1',
    port: 8084,
    open: true, // vue项目启动时自动打开浏览器
    proxy: {
      '/api': { // '/api'是代理标识,用于告诉node, url前面是/api的就是使用代理的
      target: "http://xxx.xxx.xxx:8080", // 目标地址,一般是指后台服务器地址
      changeOrigin: true, // 是否跨域
      pathRewrite: { // pathRewrite 的作用是把实际Request Url中的"/api" 用''代替
        '^/api': ''
      }
    }
  }
}

}
通过axios发送请求中,配置请求的根路径

axios.defaults.baseURL = ‘/api’

33、Vue3有了解吗?说说跟vue2的区别?

优化

利用新的语言特性(es6)

解决架构问题

更小

vue2采用面向对象编程的思想,vue3则采用函数式编程的思想。充分利用函数式编程组合大于继承的优势,采用函数式编程更利于逻辑功能的复用,webpack打包时更有利于tree-shaking,更利于代码的压缩,更利于返回值类型校验,压缩后的文件体积更小。

更快

vue2需要diff所有的虚拟dom节点,而vue3参考了SVELTE框架的思想,先分层次,然后找不变化的层,针对变化的层进行diff,更新速度不会再受template大小的影响,而是仅由可变的内容决定。经过尤雨溪自己的测试,大概有6倍的速度提升。

加强typescript支持

vue3的源码开始采用了ts进行编写,给开发者也提供了支持ts的开发模式。

Api一致性

vue3最开始的版本可以完美兼容vue2的api。

提高可维护能力

从源码的层面上提供了更多的可维护能力。

开放更多底层功能

把更多的底层功能开放出来,比如render、依赖收集功能,我们可以更好的进行自定义化开发,可以写更多的高阶组件。

数据双向绑定方面的区别

vue2 采用了defineProperty,而vue3则采用了proxy。

1.使用proxy不污染源对象,会返回一个新对象,defineProperty是注入型的,会破坏源对象

2.使用proxy只需要监听整个源对象的属性,不需要循环使用Object.defineProperty监听对象的属性

3.使用proxy可以获取到对象属性的更多参数,使用defineProperty只能获取到监听属性的新值newvalue

34、Vue3为什么用proxy代替defineProperty

defineProperty缺点

检测不到对象属性的添加和删除

数组API方法无法监听到

需要对每个属性进行遍历监听,如果嵌套对象,需要深层监听,造成性能问题

Proxy优点

1.Proxy直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的

2.Proxy可以直接监听数组的变化(push、shift、splice)

3.Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等,这是Object.defineProperty不具备的

35、Vue中错误处理

后端接口错误

通过axios的interceptor实现网络请求的response先进行一层拦截

代码逻辑错误设置全局错误处理函数,errorHandler为全局钩子,使用Vue.config.errorHandler配置,2.6版本后可捕捉v-on与promise链的错误,可用于统一错误处理与错误兜底。

Vue.config.errorHandler = function (err, Vm, info) {
  // handle error
  // `info` 是Vue 特定的错误信息,比如错误所在的生命周期钩子
  //只在2.2.0+ 可用
}

设置生命周期钩子,errorCaptured是组件内部钩子,可捕捉本组件与子孙组件抛出的错误,接收error、vm、info三个参数,return false后可以阻止错误继续向上抛出。

errorCaptured(err, vm, info) {
console.log(cat EC: ${err.toString()}\n info:${info});
return false;
}

36、react和vue有哪些不同,说说你对这两个框架的看法

相同点

都有组件化思想

都支持服务器端渲染

都有Virtual DOM(虚拟dom)

数据驱动视图

都支持props进行父子组件间数据通信

都有支持native的方案:Vue的weex、React的React native

都有自己的构建工具:Vue的vue-cli、React的Create React App

区别

1.数据流向的不同:react从诞生开始就推崇单向数据流,而Vue是双向数据流

2.组件写法不同:组件写法React推荐JSX,就是把HTML和css全都写进JavaScript中,Vue推荐是webpack+vue+loader单文件组件格式,就是HTML、css、JavaScript都写进一个文件

3.数据变化的实现原理不同:react使用的是不可变数据,而Vue使用的是可变的数据

4.组件化通信的不同:react中我们通过使用回调函数来进行通信的,而Vue中子组件向父组件传递消息有两种方式:事件和回调函数

5.diff算法不同:react主要使用diff队列保存需要更新哪些DOM,得到patch树,再统一操作批量更新DOM。Vue 使用双向指针,边对比,边更新DOM

37、父组件和子组件生命周期钩子执行顺序

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

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

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

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

38、Router-link和a标签的区别

Router-link做了三件事:

1.有onclick那就执行onclick

2.click的时候阻止a标签默认事件(这样子点击123就不会跳转和刷新页面)

3.再取得跳转href(即是to),用history(前端路由两种方式之一,history & hash)跳转,此时只是链接变了,并没有刷新页面

39、Router路由

完整的导航解析流程

1.触发进入其它路由

2.调用要离开路由的组件守卫beforeRouteLeave

3.调用全局的前置守卫beforeEach

4.在重用的组件里调用 beforeRouteUpdate

5.在路由配置里调用 beforeEnter

6.解析异步路由组件

7.在将要进入的路由组件中调用beforeRouteEnter

8.调用全局的解析守卫beforeResolve

9.导航被确认

10.调用全局的后置钩子afterEach。

11.触发 DOM 更新mounted。

12.执行beforeRouteEnter守卫中传给 next的回调函数。

40、vue-router的两种模式histroy和hash的区别?

为什么要有hash 和history?

对于Vue这类渐进式前端开发框架,为了构建SPA(单页面应用),需要引入前端路由系统,这也就是Vue-Router存在的意义。前端路由的核心,就在于改变视图的同时不会向后端发出请求。

为了达到这一目的,浏览器当前提供了以下两种支持:

1.hash: 即地址栏URL中的 # 符号(此hash不是密码学里的散列运算)

比如这个URL:http://www.abc.com/#/hello, hash的值为#/hello.它的特点在于hash虽然出现在URL中,但不会被包括在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面

2.history:利用了HTML5 History API中新增的pushState()和replaceState()方法。(需要特定浏览器支持)

这两个方法应用于浏览器的历史记录栈,在当前已有的back、forward、go的基础上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的URL,但浏览器不会即向后端发送请求。因此可以说,hash模式和histoury模式都是属于浏览器自身的特性,Vue-Router只是利用了这两个特性(通过调用浏览器提供的接口)来实现前端路由

使用场景

一般情景下,hash和history都可以,除非你更在意颜值,#符号夹杂在URL里看起来确实有些不太美丽。如果不想要很丑的hash,我们可以用路由的history模式,这种模式充分利用history pushState API来完成URL跳转,无须重新加载页面。

调用history.pushState()相比于直接修改hash ,存在以下优势:

1.pushState()设置的新URL可以是与当前URL同源的任意URL;而hash只可修改#后面的部分,因此只能设置与当前URL同文档的URL;

2.pushState()设置的新URL可以与当前URL一模一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来不一样才会触发动作将记录添加到栈中;

3.pushState()通过stateObject参数可以添加任意类型的数据到记录中;而hash只可添加短字符串;

4.pushState()可额外设置title属性供后续使用。

当然history也不是样样都好。SPA虽然在浏览器里游刃有余,单真要通过URL向后端发起HTTP请求时,两者的差异就来了。尤其在用户手动输入URL后回车,或者刷新(重启)浏览器的时候。

1.hash 模式下,仅hash符号之前的内容会被包含在请求中,如http://www.abc.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回404错误。

2.history模式下,前端的URL必须和实际向后端发起请求的URL一致。如htttp://www.abc.com/book/id。如果后端缺少对/book/id 的路由处理,将返回404错误

Vue数据双向绑定原理

实现mvvm的数据双向绑定,是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来给各个属性添加setter,getter并劫持监听,在数据变动时发布消息给订阅者,触发相应的监听回调。就必须要实现以下几点:
1、实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
2、实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
3、实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图

$nextTick的理解

用法:
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
为什么?
Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。
所以为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。
使用场景
在你更新完数据后,需要及时操作渲染好的 DOM时

keep-alive了解吗

< keep-alive >是Vue的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。

< keep-alive > 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。

Vdom的理解

vdom 就是用 js 对象来描述真实 DOM,虚拟DOM的实现就是普通对象包含tag、data、children等属性对真实节点的描述。(本质上就是在JS和DOM之间的一个缓存);由于直接操作 DOM 性能低,但是 js 层的操作效率高,可以将 DOM 操作转化成对象操作,最终通过diff算法比对差异进行更新DOM (减少了对真实DOM的操作)。虚拟DOM不依赖真实平台环境从而也可以实现跨平台。

优点:

1、保证性能下限:可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能;
2、无需手动操作dom:极大提高我们的开发效率;
3、跨平台:虚拟 DOM 本质上是 JavaScript 对象;
缺点:

无法进行极致优化: 在一些性能要求极高的应用中虚拟DOM无法进行针对性的极致优化


总结

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值