前言
临近2019年的尾声,是不是该为了更好的2020年再战一回呢? ‘胜败兵家事不期,包羞忍耻是男儿。江东子弟多才俊,卷土重来未可知’,那些在秋招失利的人,难道就心甘情愿放弃吗!
此文总结2019年以来本人经历以及浏览文章中,较热门的一些面试题,涵盖从CSS到JS再到Vue再到网络等前端基础到进阶的一些知识。
总结面试题涉及的知识点是对自己的一个提升,也希望可以帮助到同学们,在2020年会有一个更好的竞争能力。

css篇- juejin.cn/post/684490…Javavscript篇- juejin.cn/post/684490…ECMAScript 6篇- juejin.cn/post/684490…
Module Four - Vue
「About Base」
讲一讲你对MVVM的理解?与MVC有什么不同?
MVC指的是Model-View-Controller,即模型-视图-控制器。- 使用
MVC的目的就是将模型与视图分离 MVC属于单向通信,必须通过Controller来承上启下,既必须由控制器来获取数据,将结果返回给前端,页面重新渲染
- 使用
MVVM指的是Model-View-ViewModel,即模型-视图-视图模型,「模型」指的是后端传递的数据,「视图」指的是所看到的页面,「视图模型」是MVVM的核心,它是连接View与Model的桥梁,实现view的变化会自动更新到viewModel中,viewModel中的变化也会自动显示在view上,是一种数据驱动视图的模型
区别:
MVC中的Control在MVVM中演变成viewModelMVVM通过数据来显示视图,而不是通过节点操作MVVM主要解决了MVC中大量的DOM操作,使页面渲染性能降低,加载速度慢,影响用户体验的问题
请说一下Vue响应式数据的原理?
Vue底层对于响应式数据的核心是object.defineProperty,Vue在初始化数据时,会给data中的属性使用object.defineProperty重新定义属性(劫持属性的getter和setter),当页面使用对应属性时,会通过Dep类进行依赖收集(收集当前组件的watcher),如果属性发生变化,会通知相关依赖调用其update方法进行更新操作
- 总结:
Vue通过数据劫持配合发布者-订阅者的设计模式,内部通过调用object.defineProperty()来劫持各个属性的getter和setter,在数据变化的时候通知订阅者,并触发相应的回调
关于底层源码,可以看看这篇文章:segmentfault.com/a/119000001…
Vue是如何实现响应式数据的?
Vue数据双向绑定是指:数据变化更新视图,视图变化更新数据,例如输入框输入内容变化时,data中的数据同步变化;data中的数据变化时,文本节点的内容同步变化
Vue主要通过以下4个步骤实现响应式数据
- 实现一个监听器「Observer」:对数据对象进行遍历,包括子属性对象的属性,利用
Object.defineProperty()在属性上都加上getter和setter,这样后,给对象的某个值赋值,就会触发setter,那么就能监听到数据变化 - 实现一个解析器「Compile」:解析
Vue模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新 - 实现一个订阅者「Watcher」:
Watcher订阅者是Observer和Compile之间通信的桥梁,主要任务是订阅Observer中的属性值变化的消息,当收到属性值变化的消息时,触发解析器Compile中对应的更新函数 - 实现一个订阅器「Dep」:订阅器采用发布-订阅设计模式,用来收集订阅者
Watcher,对监听器Observer和订阅者Watcher进行统一管理
Vue如何实现对象和数组的监听?
由于
Object.defineProperty()只能对属性进行数据劫持,而不能对整个对象(数组)进行数据劫持,因此Vue框架通过遍历数组和对象,对每一个属性进行劫持,从而达到利用Object.defineProperty()也能对对象和数组(部分方法的操作)进行监听
直接给一个数组项赋值,Vue能检测到变化吗?
由于JavaScript的限制,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 - 1)
讲一讲Vue是如何检测数组的变化?
- 核心思想:使用了函数劫持的方式,重写了数组的方法(
push,pop,unshift,shift···) Vue将data中的数组,进行了原型链的重写,指向了自己所定义的数组原型方法,当调用数组的API时,可以通知依赖更新,如果数组中包含着引用类型,会对数组中的引用类型再次进行监控
Vue如何通过vm.$set()来解决对象新增/删除属性不能响应的问题?
由于
JavaScript的限制,Vue无法检测到对象属性的添加或删除。这是由于Vue会在初始化实例时对属性的getter和setter进行劫持,所以属性必须在data对象上存在才能让Vue将它们转换为响应式数据。
Vue提供了Vue.set(object, propertyName, value) / vm.$set(object, propertyName, value)来实现为对象添加响应式属性,其原理如下:
- 如果目标是数组,直接使用数组的
splice方法触发响应式 - 如果目标是对象,会先判断对象属性是否存在,对象是否是响应式,最终如果要对属性进行响应式处理,则是通过调用
defineReactive()方法进行响应式处理(defineReactive()是Vue对Object.defineProperty()的二次封装)
为什么Vue要采取异步渲染?
因为如果不采用异步渲染,那么每次更新数据都会进行重新渲染,为了提高性能,Vue通过异步渲染的方式,在本轮数据更新后,再去异步更新视图
「译」Object.defineProperty有什么缺点?(为什么Vue3.0开始使用Proxy实现响应式)
Object.defineProperty只能劫持对象的属性,因此需要遍历对象的每个属性,而Proxy可以直接代理对象Object.defineProperty对新增属性需要手动进行观察,由于Object.defineProperty劫持的是对象的属性(第一点),所以新增属性时,需要重新遍历对象,对其新增属性再使用Object.defineProperty进行劫持 (正是这个原因导致我们在给data中的数组或对象新增属性时,需要使用$set才能保证视图可以更新)Proxy性能高,支持13种拦截方式
虚拟Dom的优缺点?
优点:
- 保证性能下限:框架的虚拟
Dom需要适配任何上层API可能产生的操作,它的一些Dom操作的实现必须是普遍适用的,所以它的性能并不是最优的,但比起粗暴的Dom操作要好很多,因此保证了性能的下限 - 无需手动操作
Dom:框架会根据虚拟Dom和数据的双向绑定,帮我们更新视图,提高开发效率 - 跨平台:虚拟
Dom本质上是JavaScript对象,而真实Dom与平台相关,相比下虚拟Dom可以更好地跨平台操作
缺点:
- 无法进行极致优化
虚拟Dom的实现原理?
虚拟Dom的实现原理主要包括以下三部分:
- 用
JavaScript对象模拟真实Dom树,对真实Dom进行抽象 - 通过
diff算法对两个虚拟Dom对象进行比较 - 通过
patch算法将两个虚拟Dom对象的差异应用到真实Dom树上
nextTick实现原理是什么? 在Vue中有什么作用
- 原理:
EventLoop事件循环- 正确的流程是依次去检测:Promise.then → MutationObserver的回调函数 → setImmediate → setTimeout 是否存在,找到存在的就去使用它,以此来确定回调函数队列是以哪个api来异步执行
- 在nextTick函数接受到一个
callback的时候,先不去调用它,而是把它push到一个全局的queue队列,等待下一个任务队列的时候再一次性把这个queue里的函数依次执行 - 这个队列可能是microTask队列,页可能是macroTask队列,前两个api属于微任务队列,后两个api属于宏任务队列
- 作用:在下次
dom更新循环结束后执行延迟回调,当我们修改数据之后立即使用nextTick()来获取最新更新的Dom
watch中的deep:true是如何实现的?
当用户指定了watch中的deep:true时,如果当前监控的值是数组类型(对象类型),会对对象中的每一项进行求值,此时会将当前watcher存入到对应属性的依赖中,这样数组中的对象发生变化也会通知数据进行更新
本质上是因为Vue内部对设置了deep的watch,会进行递归的访问(只要此属性也是响应式属性),而再此过程也会不断发生依赖收集
缺点:由于需要对每一项都进行操作,性能会降低,不建议多次使用deep:true
为什么v-if与v-for不建议连在一起使用?
v-for优先级高于v-if,如果连在一起使用的话会把v-if给每一个元素都添加上,重复运行于每一个v-for循环中,会造成性能浪费
「译」组件的data为什么要写成函数形式
在Vue中,组件都是可复用的,一个组件创建好后,可以在多个地方重复使用,而不管复用多少次,组件内的data都必须是相互隔离,互不影响的,如果data以对象的形式存在,由于Javascript中对象是引用类型,作用域没有隔离,当你在模板中多次声明这个组件,组件中的data会指向同一个引用,此时如果对某个组件的data进行修改,会导致其他组件里的data也被修改。因此data必须以函数的形式返回
- 总结:为了实现每个组件实例可以维护独立的数据拷贝,不会相互影响
❗ 小知识: new Vue根组件不需要复用,因此不需要以函数方式返回
「译」v-for中的key的作用是什么?
key是为每个vnode指定唯一的id,在同级vnode的Diff过程中,可以根据key快速的进行对比,来判断是否为相同节点,并利用key的唯一性生成map来更快的获取相应的节点,另外指定key后,可以保证渲染的准确性。
注:不建议将index作为key值,具体学习一下【晨曦时梦见兮】的文章:juejin.cn/post/684490…
Vue每个生命周期什么时候被调用?
beforeCreate→ 在实例初始化之后,数据观测(data observer)之前被调用created→ 实例已经创建完成之后被调用。在这里,实例已完成以下配置:- 数据观测(
data observer) - 属性和方法的运算
watch/event事件回调- 但这里还没有
$el
- 数据观测(
beforeMount→ 在挂载开始之前被调用,相关的render函数首次被调用mounted→$el被新创建的vm.$el替换,并挂载到实例上之后调用该钩子beforeUpdate→ 数据更新时调用,发生在虚拟DOM重新渲染和打补丁之前updated→ 由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子(该钩子在服务器端渲染期间不被调用)beforeDestroy→ 实例销毁之前调用,在这里,实例仍然完全可以使用destroyed→Vue实例销毁后调用。调用该钩子后,Vue实例指示的所有东西都会解绑,所有的事件监听器会被移除,所有的子实例也会被销毁(该钩子在服务器端渲染期间不被调用)

Vue每个生命周期内部可以做什么事?
created→ 实例已经创建完成,由于它是最早触发的,所以可以进行一些数据,资源的请求mounted→ 实例已经挂载完成,可以进行一些DOM操作beforeUpdate→ 可以在该钩子中进一步地更改状态,这不会触发附加的渲染过程updated→ 可以执行依赖于DOM的操作。但在大多数情况下,应避免在该钩子中更改状态,因为这可能导致更新无限循环destroyed→ 可以执行一些优化操作,例如清空定时器,清理缓存,解除事件绑定等
Vue组件有哪些声明生命周期钩子?
「beforeCreate」、「created」、「beforeMount」、「mounted」、「beforeUpdate」、「updated」、「beforeDestroy」、「destroyed」
❗ 小知识:
<keep-alive>拥有自己独立的钩子函数 activated | deactivated
activated→ 在被<keep-alive>包裹的组件中才有效,当组件被激活时使用该钩子deactivated→ 在被<keep-alive>包裹的组件中才有效,当组件被停止时使用该钩子
「译」Vue的父组件和子组件生命周期钩子执行顺序是什么?
- 理解渲染过程:
父组件挂载完成必须是等到子组件都挂载完成之后,才算父组件挂载完,所以父组件的
mounted肯定是在子组件mounted之后
So:「父」beforeCreate → 「父」created → 「父」beforeMount → 「子」beforeCreate → 「子」created → 「子」beforeMount → 「子」mounted → 「父」mounted
-
子组件更新过程(取决于对父组件是否有影响)
- 影响到父组件: 「父」beforeUpdate → 「子」beforeUpdate → 「子」updated → 「父」updated
- 不影响父组件: 「子」beforeUpdate → 「子」updated
-
父组件更新过程(取决于对子组件是否有影响)
- 影响到子组件: 「父」beforeUpdate → 「子」beforeUpdate → 「子」updated → 「父」updated
- 不影响子组件: 「父」beforeUpdate → 「父」updated
-
销毁过程
- 「父」beforeDestroy → 「子」beforeDestroy → 「子」destroyed → 「父」destroyed
父组件可以监听到子组件的生命周期吗?
比如有一个父组件Parent和子组件Child,如果父组件监听到子组件挂载
mounted就做一些逻辑处理、
- 方式一
// Parent.vue
<Child @mounted='doSomething' />
// Child.vue
mounted(){
this.$emit('mounted')
}
- 方式二(更简单 - 通过@hook来监听)
// Parent.vue
<Child @hook:mounted='doSomething' />
# @hook可以监听其他生命周期
怎么理解vue的单向数据流
- 在
vue中,父组件可以通过prop将数据传递给子组件,但这个prop只能由父组件来修改,子组件修改的话会抛出错误 - 如果是子组件想要修改数据,只能通过
$emit由子组件派发事件,并由父组件接收事件进行修改
为什么子组件不可以修改父组件传递的Prop?(为什么vue提倡单向数据流)
由于vue提倡单向数据流,即父级props的更新会流向子组件,但反过来则不行。这是为了防止意外的改变父组件的状态,使得应用的数据流变得难以理解。如果破坏了单项数据流,当应用复杂时,debug的成本将会非常高
组件间有哪些通信方式?
-
父子组件通信
props/event$parent/$childrenrefprovide/inject.sync
-
非父子组件通信
eventBus- 通过根实例
$root vuex$attr/$listenersprovide/inject
❗ 小知识: 关于.sync的使用
假设有一个组件 comp
<comp :foo.sync="bar"></comp>
传递foo值并用sync修饰,会被扩展成
<comp :foo="bar" @update:foo="val => bar = val"></comp>
当子组件comp需要更新foo的值时,它需要显示地触发一个更新事件
this.$emit('update:foo', newValue)
说一说vue的动态组件
多个组件通过同一个挂载点进行组件的切换,
is的值是哪个组件的名称,那么页面就会显示哪个组件
<div :is='xxx'></div>
讲一讲递归组件的用法
组件是可以在它们自己的模板中调用自身的,不过它们只能通过
name选项来做这件事
首先我们要知道,既然是递归组件,那么一定要有一个结束的条件,否则就会导致组件无限循环使用,最终出现
max stack size exceeded的错误,也就是栈溢出。所以,我们应该使用v-if = 'false'来作为递归组件的结束条件,当遇到v-if = 'false'时,组件将不会再进行渲染
自定义组件的语法糖v-model是怎么样实现的?(v-model如何实现双向绑定)
v-model本质是v-bind和v-on的语法糖,用来在表单控件或组件上创建双向绑定(仅仅只是语法糖,区分响应式数据)
原理:在表单元素上绑定vlue并且监听input事件
<input v-model='searchText'>
等价于
<input
v-bind:value='searchText'
v-on:input='searchText = $event.target.value'>
在一个组件上使用v-model,默认会为组件绑定名为value的prop和名为input的事件
「译」Vuex和单纯的全局对象有什么区别?
vuex和全局对象主要有两大区别:
vuex的状态存储是响应式的。当vue组件从store中读取状态时,若store中的状态发生变化,那么相应的组件也会得到高效更新- 不能直接改变
store中的状态,改变store中的状态唯一方法是显示地提交mutation(commit)。这样使得我们可以方便地跟踪每一个状态的变化
「译」为什么vuex的mutation中不能做异步操作?
vuex中所有的状态更新的唯一方式都是提交mutation,异步操作需要通过action来提交mutation(dispatch)。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地使用vuex
每个mutation执行完后都会对应得到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现time-travel了。
如果mutation支持异步操作,就没有办法知道状态是何时更新,无法很好的进行状态追踪,影响调试效率
v-show / v-if 的区别是什么?
v-if指如果条件不成立则不会渲染当前指令所在节点的Dom元素,会在切换过程中对条件块的事件监听器和子组件进行销毁和重建v-show只是基于css进行切换,不管条件是什么,都会进行渲染(切换display:block | none)
So:v-if切换的开销较大,而v-show初始化的开销较大,所以在需要频繁切换显示和隐藏的Dom元素时,使用v-show更合适,渲染后很少进行切换则使用v-if较合适
computed / watch 的区别是什么?
computed是依赖于其他属性的一个计算值,并且具备缓存,只有当依赖的值发生变化才会更新(自动监听依赖值的变化,从而动态返回内容)watch是在监听的属性发生变化的时候,触发一个回调,在回调中执行一些逻辑
(引用晨曦时梦见兮) 实际上computed会拥有自己的watcher,它具有一个dirty属性来决定computed的值是需要重新计算还是直接复用之前的值,例如这个例子:
computed: {
sum() {
return this.count + 1
}
}
- 在
sum第一次进行求值的时候会读取响应式属性count,收集到这个响应式数据作为依赖。并且计算出一个值来保存在自身的value上,把dirty设为false,接下来在模板里再访问sum就直接返回这个求好的值value,并不进行重新求值。 - 而 count 发生变化了以后会通知 sum 所对应的 watcher 把自身的 dirty 属性设置成 true,这也就相当于把重新求值的开关打开来了。这个很好理解,只有 count 变化了, sum 才需要重新去求值。
- 那么下次模板中再访问到 this.sum 的时候,才会真正的去重新调用 sum 函数求值,并且再次把 dirty 设置为 false,等待下次的开启
So:computed和watch区别在于用法上的不同,computed适合在模板渲染中,如果是需要通过依赖来获取动态值,就可以使用计算属性。而如果是想在监听值变化时执行业务逻辑,就使用watch
Vue中 v-html 有什么作用?会导致什么问题?
v-html可以用来识别HTML标签并渲染出去
导致问题: 在网站上动态渲染任意Html,很容易导致受到Xss攻击,所以只能在可信内容上使用v-html,且永远不能用于用户提交的内容上
keep-alive在vue中的作用是什么?
包裹在<keep-alive>里组件,在切换时会保存其组件的状态,使其不被销毁,防止多次渲染
- 一般结合路由和动态组件一起使用,用于缓存组件
keep-alive拥有两个独立的生命周期(activated|deactivated),使keep-alive包裹的组件在切换时不被销毁,而是缓存到内存中并执行deactivated钩子,切换回组件时会获取内存,渲染后执行activated钩子- 提供
include和exclude属性,两者都支持字符串或正则表达式include表示只有名称匹配的组件才会被缓存exclude表示任何名称匹配的组件都不会被缓存exclude优先级高于include
如何新增自定义指令?
- 创建局部指令
var app = new Vue({
el: '#app',
data: {
},
// 创建指令(可以多个)
directives: {
// 指令名称
dir1: {
inserted(el) {
// toDo
}
}
}
})
- 创建全局指令
Vue.directive('dir2', {
inserted(el) {
// inserted 表示元素插入时
// toDo
}
})
- 指令使用
<div id="app">
<div :dir1='..'></div>
</div>
如何自定义过滤器
- 创建局部过滤器
var app = new Vue({
el: '#app',
data: {
},
// 创建指令(可以多个)
filters: {
// 指令名称
newfilter:function(value){
// toDo
}
}
})
- 创建全局过滤器
Vue.filter('newfilter', function (value) {
// toDo
})
- 过滤器使用
<div>{{xxx | newfilter}}</div>
vue常用修饰符有哪些,有什么用?
.prevent:拦截默认事件.passive:不拦截默认事件.stop:阻止事件冒泡.self:当事件发生在该元素而不是子元素的时候会触发.capture:事件侦听,事件发生的时候会调用
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'
}
}
说说对SPA单页面的理解,以及它的优缺点是什么?
SPA(singlg-page application)仅在Web页面初始化时加载相应的Html,JavaScript,Css,一旦页面加载完成,SPA不会因为用户的操作而进行页面的重新加载或跳转,取而代之的是利用路由机制实现Html内容的变换,UI与用户的交互,避免页面的重新加载
- 优点:
- 用户体验好,加载速度快,内容改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染
- SPA相对对服务器压力小
- 前后端职责分离,架构更清晰,前端进行交互逻辑,后端负责数据处理
- 缺点:
- 首次加载耗时较多(为实现单页面Web应用功能及显示效果,需要在加载页面的时候将
JavaScript,Css统一加载,部分页面按需加载) - 由于单页面应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理
- 不利于SEO优化
- 首次加载耗时较多(为实现单页面Web应用功能及显示效果,需要在加载页面的时候将
如何让css仅在当前组件中起作用
在
<style>标签上写入scoped即可
「About Router」
关于路由,route / router 有什么区别?
route表示路由信息对象,包括path,params,hash,query,fullPath,matched,name等路由信息参数router表示路由实例对象,包括了路由的跳转方法,钩子函数等
「译」 vue-Router中有哪些导航守卫
- 「全局前置钩子」:
beforeEach,beforeResolve,afterEach - 「路由独享守卫」:
beforeEnter - 「组件内部守卫」:
beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave
导航解析流程:
- 导航被触发
- 在失活的组件里调用
beforeRouteLeave离开守卫 - 调用全局的
beforeEach守卫 - 在重用的组件里调用
beforeRouteUpdate守卫 - 在路由配置里调用
beforeEnter守卫 - 解析异步路由组件
- 在被激活的组件里调用
beforeRouteEnter守卫 - 调用全局的
beforeResolve守卫 - 导航被确认
- 调用全局的
afterEach守卫 - 触发
Dom更新 - 用创建好的实例调用
beforeRouteEnter守卫中传给next的回调
「译」vue-Router 中 hash / history 两种模式有什么区别?
hash模式会在url上显示'#',而history模式没有- 刷新页面时,
hash模式可以正常加载到hash值对应的页面,history模式没有处理的话,会返回404,一般需要后端将所有页面都配置重定向到首页路由 - 兼容性上,
hash模式可以支持低版本浏览器和IE
「译」 vue-router 中 hash / history 是如何实现的?
hash模式#后面hash值的变化,不会导致浏览器向服务器发出请求,浏览器不发出请求,就不会刷新页面,同时通过监听hashchange事件可以知道hash发生了哪些变化。根据hash变化来实现页面的局部更新
history模式history模式的实现,主要是Html5标准发布的两个Api(pushState和replaceState),这两个Api可以改变url,但是不会发送请求,这样就可以监听url的变化来实现局部更新
怎么定义 vue-router 的动态路由?怎么获取传过来的值
- 动态路由的创建,主要是使用
path属性过程中,使用动态路径参数,以冒号开头
{
path:'/details/:id',
name:'Details',
components:Details
}
# 访问`details`前缀下的路径,例如`details/1`,`details/2`等,都会映射到`Details`这个组件
- 当匹配到
/details下的路由时,参数值会被设置到this.$route.params下,所以通过这个属性可以获取动态参数
this.$route.params.id
vue-router 传参方式有哪些?
- 通过
params- 只能用
name,不能用path - 参数不会显示在
url上 - 浏览器强制刷新会清空参数
- 只能用
- 通过
query- 只能用
path,不能用name name可以使用path路径- 参数会显示在
url上 - 浏览器刷新不清空参数
- 只能用
「About Vuex」

vuex有什么优缺点?
- 优点
- 解决了非父子组件的消息传递(将数据存放在
state中) - 减少了
Ajax请求次数,有些情景可以直接从内存中的State获取
- 解决了非父子组件的消息传递(将数据存放在
- 缺点
- 刷新浏览器,
vuex中的State就会重新变回初始化状态
- 刷新浏览器,
vuex有哪几种属性?
State:vuex的基本数据,用来存储变量Getter:从基本数据state派生的数据,相当于state的计算属性Mutation:提交更新数据的方法,必须是同步的(需要异步则使用action)。每个mutation都有一个字符串的事件类型(type)和一个回调函数(handler)Action:和mutation的功能大致相同,不同在于action提交的是mutation,而不是直接变更状态action可以包含任意异步操作
Module:模块化vuex,可以让每一个模块拥有自己的state,mutation,action,getter,使得结构清晰,方便管理
vuex 中的 state 有什么特性?
vuex就是一个仓库,仓库里面放了很多对象,其中state就是数据源存放地state里面存放的数据是响应式的,Vue组件从store中读取数据,若是store中的数据改变,依赖这个数据的组件也会更新数据- 它通过
mapState把全局的state和getters映射到当前组件的computed计算属性中
vuex 中的 getters 有什么特性?
getters可以对state进行计算操作,可以把它看做store的computed计算属性- 虽然在组件中也可以做计算属性,但
getters可以在多个组件之间复用 - 如果一个状态只在一个组件内使用,是可以不用
getters
Vue 中对 Ajax 请求代码应该写在组件的 methods 中还是 vuex 的 actions 中?
- 如果请求的数据是不被其他组件公用的,仅仅在请求的组件内使用,就不需要放入
vuex的state里 - 如果被其他地方复用,可以将请求放入
action里,方便复用;如果不需要复用这个请求,直接写在Vue文件里会更方便
「About SSR」
使用过 SSR 吗?说一说对 SSR 的理解
Vue.js是构建客户端应用程序的框架,默认情况下,可以在浏览器中输出Vue组件,进行生成Dom和操作Dom。然而,也可以将同一个组件渲染为服务器的Html字符串,将它们直接发送给浏览器,最后将这些静态标记激活为客户端上完全可以交互的应用程序
即:
SSR大致意思就是vue在客户端将标签渲染成整个html片段的工作交给服务端完成,服务端形成的Html片段直接返回给前端,这个过程就叫做SSR(服务端渲染)
服务端渲染SSR的优点:
- 更好的SEO:由于
SPA页面的内容是通过Ajax获取,而搜索引擎爬取工具并不会等待Ajax异步完成后再抓取页面内容,所以在SPA中是抓取不到页面通过Ajax获取到的内容;而SSR是直接由服务器返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面 - 更快的内容到达事件(首屏加载更快):
SPA会等待所有Vue编译后的Js文件都下载完成后,才开始进行页面渲染,文件下载等需要一定时间,所以首屏渲染需要一定时间;而SSR直接由服务器渲染好页面直接返回显示,无需等待下载Js文件后再去渲染,所以SSR有更快的内容到达时间
服务端渲染SSR的缺点:
- 更多的开发条件限制:例如服务端渲染只支持
beforeCreate,created生命周期钩子,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序SPA不同,服务器渲染应用程序需要处于NodeJs环境下运行 - 更多的服务器负载:在
NodeJs中渲染完整的应用程序,显然会比仅仅提供静态文件的server更占用CPU资源