# Vue 自测基础面试题
# 什么是 SPA, 有什么优缺点
# SPA
SPA(single-page application) 单页面应用程序,其仅在WEB 页面初始化时加载对应的 HTML、JavaScript、CSS 等资源,一旦页面加载完成,就不由用户的操作而对也页面进行重复加载,而是通过路由跳转实现 HTML 内容的互换,避免重复加载。
# 优点与缺点
- 优点
- 用户体验好,网页整体加载速度快,WEB 网页的内容不需要加载整个页面,避免的资源的重复加载。
- 可以减轻对服务器的压力
- 前后端职责分离,构架清晰,服务端侧重于数据处理,客户端侧重于页面的交互逻辑
- 缺点
- 首次加载较慢:由于其但单页面的特性,需要在首次加载网页时对于资源的同一进行加载。
- 前进后退路由管理:由于 SPA 在一个页面中显示所有的内容,无法使用浏览器的前进后退功能,具体表现是点击前进后退会重新刷新页面,所有的页面都需要建立自己的堆栈管理
- SEO 难度较大:由于所有的内容都需要在一个页面中动态的替换显示。
# 怎样理解 Vue 中的单向数据流
# 理解
在 Vue 的组件树中,父组件向子组件传递数据,这个数据称为单向数据流,
prop
可以被多个子组件接收,子组件通过props
选项接收父组件传递过来的prop
。这就说明一旦父组件中的关联的数据更改,其下与此数据关联的子组件中的prop
也会随之更改。但是,通过子组件更改
prop
从而修改父组件中的对应数据是不被允许的,原因是一但父组件中的这个数据更改,下面所有子组件的prop
都会更改,这就导致应用的数据流向难以理解。
# 子组件变相修改 prop
由于单项数据流,所以在子组件中修改 prop
是不被允许的,但是可以变相修改,下面提供两个方案:
- 将
prop
值放在data
选项中存储
props: ['givenData'];
data () {
return {
value: this.givenData
}
};
2
3
4
5
6
- 使用计算属性
props: ['givenData'];
computed: {
givenData () {
return this.givenData.toUpperCase();
}
}
2
3
4
5
6
# Vue 无法检测变化的数组操作
这里我之前写过一篇文章有过解释,点击传送门
# 说一下Vue的生命周期
# 简述什么是生命周期
Vue 实例有一个完整的生命周期,即从创建实例开始、初始化实例数据、编译模板、挂载 Dom ==> 更新 ==> 卸载
# 生命周期示意图
# 父组件和子组件生命周期钩子函数执行顺序
分为以下四个情况:
1. 加载渲染过程 父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
2. 子组件更新过程 父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
3. 父组件更新过程 父 beforeUpdate -> 父 updated
4. 销毁过程 父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
# 在哪个阶段可以放置异步请求
在created beforeMount mounted
里可以放置异步请求,原因是在此三个阶段中数据选项 data methods
都已经创建,可以将服务端返回的数据进行赋值
推荐在 created
中放置异步请求,原因见下
- 能更快获取到服务端数据,减少页面 loading 时间
- ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性
# 在什么阶段才能访问操作DOM
在 mounted
中可以操作 DOM, 原因是在进行到 mounted
时,Vue 已经将编译好的模板挂载到了页面上。
# 父组件可以监听到子组件的生命周期吗?
可以的,举个例子,在子组件生命周期进行到 mounted
后父组件做出一定处理,提供两个方案
1. 方案一
使用 $emit()
进行 子 => 父 组件之间的通信
<Child @speakToFa="watchChildMounted"><>
// 子组件 使用钩子函数
mounted () {
this.$emit('speakToFa');
}
// 父组件
methods: {
watchChildMounted () {
console.log('监听到子组件钩子函数 mounted 执行');
}
}
2
3
4
5
6
7
8
9
10
2. 方案二
使用 hook
解释
hook
常用于在父组件监听子组件的种种生命周期
写法:
可以用v-on
指令绑定事件hook:生命周期
<Child @hook:mounted="watchChildMounted"><>
// 父组件
methods: {
watchChildMounted () {
console.log('监听到子组件钩子函数 mounted 执行');
}
}
2
3
4
5
6
# 谈谈你对 keep-alive 的了解
keep-alive
是 Vue 内置的一个组件,用于组件缓存,避免重复组件渲染。有以下特性:
一般结合路由和动态组件一起使用,用于缓存组件。
有三个属性
include
与 exclude
两者都支持字符串或正则表达式
include
只有名称匹配的组件会被缓存。exclude
任何名称匹配的组件都不会被缓存。 其中当两者同时出现时exclude
的优先级比include
高;max
数字。最多可以缓存多少组件实例。
- 被
keep-alive
包裹的组件拥有两个钩子函数activated
和deactivated
,当组件被激活时,触发activated
,当组件被移除时,触发deactivated
。
# Vue 中子组件的 data 选项为什么是函数
Vue 的子组件中,data
选项为一个函数 return
一个对象的形式。
原因:
- 为什么不用对象的形式: 由于 Vue 中的组件是会被复用的,使用对象,如果使用对象,作用域无法隔离,各个组件之间的
data
数据会互相产生影响。 - 为什么使用函数: 使用函数形式并
return
一个对象,每个实例都会保存一份被返回对对象的独立的拷贝,这样就保证了各个组件之间的data
数据的独立性。
# 了解 v-model 吗?说一下其原理
v-model
本质上是语法糖,逻辑上绑定表单元素的属性、注册事件监听来起到数据双向绑定的特点。
# v-model 绑定表单元素
详见:传送门 Vueのapi
举几个例子:
- 作用于
text
或textarea
元素时,绑定其value
属性并注册input
事件监听。 - 作用于
checkbox
或radio
元素时,绑定其checked
属性并注册change
事件监听。 - 作用于
select
元素时,绑定其value
属性并注册change
事件监听。
# v-model 作用于组件上
v-model
默认会利用名为 value
的 prop
和名为 input
的事件。
# Vue 组件间通信有哪几种方式?
在之前的 blog 中我已经总结了几种方式,点击传送门
简单罗列一下:
- 使用
props
与$emit
: 适用于父子组件之间通信
- 绑定自定义属性使用
props
接收父组件传递过来的数据。 - 绑定自定义事件使用
$emit
传递触发的事件到父组件,由于可以传参,所以可以传递数据
- 使用
$emit
与$on
: 适用于所有组件之间的通信
- 创建一个全局的空的 Vue 实例。
- 在传递数据方调用该空实例的
$emit
方法来触发当前实例上的事件,并将需要传递的数据利用参数存储。 - 在接受数据方调用该空实例的
$on
方法来监听当前实例上的自定义事件。
本质上就是创建了一个空的 Vue 实例作为事件总线,来在它身上触发($emit
)和监听($on)事件
- 使用
$attrs
与$listeners
: 适用于隔代组件之间的通信
- 向下级通信: 子组件
$attrs
属性可以自动接收父组件传递过来但是没被props
接收的数据。这样就可以在子组件中通过v-bind="$attrs"
的方式继续向下一层组件传递。 - 向上级通信: 子组件通过
$emit()
触发的事件,都会记录在父组件的$listeners
中,在父组件中就可以通过v-on=$listeners
的方式继续向上一层组件传递。
- 使用
ref
与$refs
: 适用于父子组件之间通信
- 使用
ref
给子组件一个独立的标识,而$refs
这个对象就存储着标识后的子组件,并且以键值对的形式,key
为ref
时给的独立标识。 - 当需要子组件的数据,直接用
$refs.key.数据
的形式指代子组件取里面数据。
- 使用
$parent
与$childrens
: 适用于父子组件之间通信
- 拿下级组件数据:
$childrens
属性,这个数组里面存储着该组件所有的直接下属组件,当需要子组件的数据时,直接用$childrens[index].数据
就可以指代对应的子组件并取里面的数据。 - 拿上级组件数据:
$parent
属性,里面就是该组件的父组件,当需要父组件数据时,直接使用$parent.数据
就可以指代父组件并取里面的数据。
- 使用
productor
与reject
: 适用于父组件将数据共享给所有子孙组件
productor
为一个对象或函数返回一个对象的形式,用于放置父组件欲要共享的数据。reject
为一个数组存字符串或者对象的形式,用于子孙组件取得共享的数据。
- 使用 vuex
# 知道 SSR 吗?
了解一点 SSR
# 什么是 SSR
SSR(server side render)
服务端渲染,Vue
原本是在客户端将标签渲染成整个 HTML 片段,而通过 SSR 将这个工作转交给了服务端,再将渲染完成后的整个 HTML 片段返回给客户端的过程就是 SSR。
# 优点
- 有利于 SEO:
- 传统的 SPA 中,SPA 是通过发送 ajax 请求加载的,而搜索引擎爬取页面内容不会等到 ajax 异步请求完成。
- 使用 SSR 后,直接在服务端将渲染完成的页面返回,所以搜索引擎可以爬取到。
- 加载速度更快:
- 传统的 SPA 中,是等到 Vue 编译的 js 文件都加载完再渲染页面,以首屏加载需要一定的时间。
- 而 SSR, 是在服务端会将渲染完的页面直接返回到客户端,加载速度更快。
# 缺点
- 更高的开发条件
- SSR 中只支持
beforeCreate
与created
两个钩子,这就导致有些第三方扩展库需要特殊处理才可使用在上面。 - 服务端渲染应用程序,需要在 Node.js server 环境下运行
- 加大服务器负载
- 会加大服务器 CPU 的损耗,如果在高流量环境 ( high traffic ) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。
# vue-router 有几种路由模式
hash
使用 URL Hash 值作为路由,支持所有浏览器。history
依赖于 HTML5 history APIabstract
支持所有的 JavaScript 环境,包括 Node.js 端,如果没有浏览器 API, 会强制进入此路由模式
# 说一下 vue-router 中的 hash 与 history 两种路由模式的实现
# hash
hash 值,就是 URL 尾部 #...
那一部分。
vue-router 中的 hash 是根据如下特性实现的:
- 当用户点击一个
a
标签,并设置href
属性,实现 hash 值的变化;或者通过 JavaScript 来改变location.hash
来变更 hash 值。 - hash 值的改变,都会在浏览器访问历史中留下记录,所以可以通过浏览器的前进和回退操作 hash 值切换。
- 可以通过
hashChange
事件来监听 hash 值的变化,从而实现页面的跳转。 - URL 中的 hash 值只是客户端的一种状态,当想服务端发送请求时,
#
后面的内容不会发送到服务端。
# history
HTML5 提供了 history API 来实现 URL 的变化。最主要的是使用了其中两个 API:
history.pushState()
以及 history.replaceState()
,详见 MDN
这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录
history.pushState()
往当前浏览器历史堆栈中添加一个新的状态。history.replaceState()
替换历史堆栈中的状态。
再介绍一个东西 window.popState
, 这是一个事件,当活动历史记录条目更改时,将触发 popstate
事件。
下面来看下 history 根据哪些特性实现:
- 利用
pushState()
以及replaceState()
操控 URL 变化。 - 使用
popState
事件来监听 URL 变化,实现页面跳转。 pushState()
以及replaceState()
不会触发popState
所以我们要手动触发页面跳转。
# 简述 MVVM
MVVM 是一种软件架构设计模式,它分为三层,分别是:
- M =>
Model
层
- 也称 数据层,在前端看来泛指后端提供的 api 接口。
- V =>
View
层
- 也称 视图层,也就是用户界面,前端主要由 HTML 以及 CSS 来构建。
- VM =>
ViewModel
层
- 也称视图数据层,这一层的出现让
Model
层与View
层解耦(前后端分离思想)。 - 是前端开发者写代码的地方,处理
Model
层的数据,并完整的描述View
层。
值得一提的是,MVVM 实现了数据与视图的双向数据绑定,ViewModel
的内容会实时展现在 View
层这样前端开发者不必操作 DOM, 只需要处理和维护 ViewModel
,更新数据视图就会自动得到相应更新。
# 说一下 Vue 中的双向数据绑定
# 什么是双向数据绑定
- 当数据变化更新视图
- 当视图变化更新数据
# Vue 中的实现方式
- 实现一个监听器
Observer
,用来劫持并监听所有属性,如果属性发生变化,就通知订阅者;- 劫持原理:遍历数据对象,包括子属性对象的属性,利用
Object.defineProperty()
对属性都加上 存值器setter
和 取值器getter
。 - 获取数据时,触发取值器;修改数据时,触发存值器。从而监听数据变化。
- 劫持原理:遍历数据对象,包括子属性对象的属性,利用
- 实现一个订阅器 Dep,用来收集订阅者,对监听器 Observer 和 订阅者 Watcher 进行统一管理;
- 实现一个订阅者 Watcher,可以收到属性的变化通知并执行相应的方法,从而更新视图;
- 实现一个解析器 Compile,可以解析每个节点的相关指令,对模板数据和订阅器进行初始化
如果想深入研究,请移步我的另一篇文章 造轮子 - 双向数据绑定,这里不多赘述。
# Vue 框架怎么实现对象和数组的监听
在 Vue 中使用 Object.defineProperty()
对对象中的属性添加观测,这就导致了属性内部为引用类型时,就无法检测内部变化了。
Vue 中的解决方案,只是部分,也有无法监听的情况,详见我的另一个篇 Blog: Vue 无法检测的数组改动问题
使用递归,检测属性的类型,如果是对象或数组,再次调用 observe
类,递归使其可观测。
// 数组
let childOb = !shallow && observe(val) // observe 功能为监测数据的变化
// 对象
if (typeof val === 'Object') {
new observer(val);
}
2
3
4
5
6
# 谈一下 Proxy 与 Object.defineProperty 优劣对比
# Proxy
优势如下:
- 可以直接监听对象与数组的变化,而
Object.defineProperty
并不能将所有修改二者的方法都观测到; - 拥有多达13种拦截方式,不限于
apply
、ownKeys
、deleteProperty
、has
等等是Object.defineProperty
不具备的; - Proxy 返回的是一个新对象,直接操作新对象达到目的;而
Object.defineProperty
只能遍历对象属性并直接修改; - Proxy 作为信标准收到浏览器厂商重点性能优化,拥有新标准性能红利。
# Object.defineProperty
兼容性好,支持 IE9,而 Proxy 要在 Vue3.0 才能使用 Proxy。
# 虚拟 DOM 优缺点
# 优点
1. 保证性能下线
虽然它需要适配上层 API,性能不是最优的,但也要比直接操纵 DOM 好很多,保证性能下线。
2. 不用手动操作 DOM
前端开发者不必再手动操作 DOM,只需着重关注 ViewModel 层的逻辑,且虚拟 DOM 与 数据双向绑定,更新视图,提高开发效率。
3. 跨平台
虚拟 DOM 本质上是 JavaScript 对象,而真实 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。
# 缺点
无法进行极致优化
虽然虚拟 DOM 有合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。