临近2019年的尾声,是否是该为了更好的2020年再战一回呢? ‘胜败兵家事不期,包羞忍耻是男儿。江东子弟多才俊,卷土重来未可知’,那些在秋招失利的人,难道就心甘情愿放弃吗!css
此文总结2019年以来本人经历以及浏览文章中,较热门的一些面试题,涵盖从CSS到JS再到Vue再到网络等前端基础到进阶的一些知识。html
总结面试题涉及的知识点是对本身的一个提高,也但愿能够帮助到同窗们,在2020年会有一个更好的竞争能力。前端
css篇
- juejin.cn/post/684490…Javavscript篇
- juejin.cn/post/684490…ECMAScript 6篇
- juejin.cn/post/684490…MVC
指的是Model-View-Controller
,即模型-视图-控制器。
MVC
的目的就是将模型与视图分离MVC
属于单向通讯,必须经过Controller
来承上启下,既必须由控制器来获取数据,将结果返回给前端,页面从新渲染MVVM
指的是Model-View-ViewModel
,即模型-视图-视图模型,「模型」指的是后端传递的数据,「视图」指的是所看到的页面,「视图模型」是MVVM
的核心,它是链接View
与Model
的桥梁,实现view
的变化会自动更新到viewModel
中,viewModel
中的变化也会自动显示在view
上,是一种数据驱动视图的模型区别:vue
MVC
中的Control
在MVVM
中演变成viewModel
MVVM
经过数据来显示视图,而不是经过节点操做MVVM
主要解决了MVC
中大量的DOM
操做,使页面渲染性能下降,加载速度慢,影响用户体验的问题Vue
底层对于响应式数据的核心是object.defineProperty
,Vue
在初始化数据时,会给data
中的属性使用object.defineProperty
从新定义属性(劫持属性的getter
和setter
),当页面使用对应属性时,会经过Dep类进行依赖收集(收集当前组件的watcher
),若是属性发生变化,会通知相关依赖调用其update方法进行更新操做node
Vue
经过数据劫持配合发布者-订阅者的设计模式,内部经过调用object.defineProperty()
来劫持各个属性的getter
和setter
,在数据变化的时候通知订阅者,并触发相应的回调关于底层源码,能够看看这篇文章:segmentfault.com/a/119000001…面试
Vue数据双向绑定是指:数据变化更新视图,视图变化更新数据,例如输入框输入内容变化时,data中的数据同步变化;data中的数据变化时,文本节点的内容同步变化正则表达式
Vue主要经过如下4个步骤实现响应式数据算法
Object.defineProperty()
在属性上都加上getter
和setter
,这样后,给对象的某个值赋值,就会触发setter
,那么就能监听到数据变化Vue
模板指令,将模板中的变量都替换成数据,而后初始化渲染页面视图,并将每一个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变更,收到通知,调用更新函数进行数据更新Watcher
订阅者是Observer
和Compile
之间通讯的桥梁,主要任务是订阅Observer
中的属性值变化的消息,当收到属性值变化的消息时,触发解析器Compile
中对应的更新函数Watcher
,对监听器Observer
和订阅者Watcher
进行统一管理因为
Object.defineProperty()
只能对属性进行数据劫持,而不能对整个对象(数组)进行数据劫持,所以Vue框架经过遍历数组和对象,对每个属性进行劫持,从而达到利用Object.defineProperty()
也能对对象和数组(部分方法的操做)进行监听vue-router
因为JavaScript
的限制,Vue
不能检测到如下数组的变更vuex
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)
复制代码
push,pop,unshift,shift···
)Vue
将data
中的数组,进行了原型链的重写,指向了本身所定义的数组原型方法,当调用数组的API
时,能够通知依赖更新,若是数组中包含着引用类型,会对数组中的引用类型再次进行监控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
经过异步渲染的方式,在本轮数据更新后,再去异步更新视图
Object.defineProperty
只能劫持对象的属性,所以须要遍历对象的每一个属性,而Proxy
能够直接代理对象Object.defineProperty
对新增属性须要手动进行观察,因为Object.defineProperty
劫持的是对象的属性(第一点),因此新增属性时,须要从新遍历对象,对其新增属性再使用Object.defineProperty
进行劫持 (正是这个缘由致使咱们在给data
中的数组或对象新增属性时,须要使用$set
才能保证视图能够更新)Proxy
性能高,支持13种拦截方式优势:
Dom
须要适配任何上层API可能产生的操做,它的一些Dom
操做的实现必须是广泛适用的,因此它的性能并非最优的,但比起粗暴的Dom
操做要好不少,所以保证了性能的下限Dom
:框架会根据虚拟Dom
和数据的双向绑定,帮咱们更新视图,提升开发效率Dom
本质上是JavaScript
对象,而真实Dom
与平台相关,相比下虚拟Dom
能够更好地跨平台操做缺点:
虚拟Dom
的实现原理主要包括如下三部分:
JavaScript
对象模拟真实Dom
树,对真实Dom
进行抽象diff
算法对两个虚拟Dom
对象进行比较patch
算法将两个虚拟Dom
对象的差别应用到真实Dom
树上EventLoop
事件循环
callback
的时候,先不去调用它,而是把它push到一个全局的queue队列,等待下一个任务队列的时候再一次性把这个queue里的函数依次执行dom
更新循环结束后执行延迟回调,当咱们修改数据以后当即使用nextTick()
来获取最新更新的Dom当用户指定了watch
中的deep:true
时,若是当前监控的值是数组类型(对象类型),会对对象中的每一项进行求值,此时会将当前watcher
存入到对应属性的依赖中,这样数组中的对象发生变化也会通知数据进行更新
本质上是由于Vue内部对设置了deep的watch,会进行递归的访问(只要此属性也是响应式属性),而再此过程也会不断发生依赖收集
缺点:因为须要对每一项都进行操做,性能会下降,不建议屡次使用deep:true
v-for
优先级高于v-if
,若是连在一块儿使用的话会把v-if
给每个元素都添加上,重复运行于每个v-for
循环中,会形成性能浪费
在Vue
中,组件都是可复用的,一个组件建立好后,能够在多个地方重复使用,而无论复用多少次,组件内的data
都必须是相互隔离,互不影响的,若是data
以对象的形式存在,因为Javascript
中对象是引用类型,做用域没有隔离,当你在模板中屡次声明这个组件,组件中的data会指向同一个引用,此时若是对某个组件的data进行修改,会致使其余组件里的data也被修改。所以data
必须以函数的形式返回
❗ 小知识: new Vue
根组件不须要复用,所以不须要以函数方式返回
key
是为每一个vnode
指定惟一的id
,在同级vnode
的Diff
过程当中,能够根据key
快速的进行对比,来判断是否为相同节点,并利用key
的惟一性生成map
来更快的获取相应的节点,另外指定key
后,能够保证渲染的准确性。
注:不建议将index做为key值,具体学习一下【晨曦时梦见兮】的文章:juejin.cn/post/684490…
beforeCreate
→ 在实例初始化以后,数据观测(data observer
)以前被调用created
→ 实例已经建立完成以后被调用。在这里,实例已完成如下配置:
data observer
)watch/event
事件回调$el
beforeMount
→ 在挂载开始以前被调用,相关的render
函数首次被调用mounted
→ $el
被新建立的vm.$el
替换,并挂载到实例上以后调用该钩子beforeUpdate
→ 数据更新时调用,发生在虚拟DOM
从新渲染和打补丁以前updated
→ 因为数据更改致使的虚拟DOM
从新渲染和打补丁,在这以后会调用该钩子(该钩子在服务器端渲染期间不被调用)beforeDestroy
→ 实例销毁以前调用,在这里,实例仍然彻底可使用destroyed
→ Vue
实例销毁后调用。调用该钩子后,Vue
实例指示的全部东西都会解绑,全部的事件监听器会被移除,全部的子实例也会被销毁(该钩子在服务器端渲染期间不被调用)created
→ 实例已经建立完成,因为它是最先触发的,因此能够进行一些数据,资源的请求mounted
→ 实例已经挂载完成,能够进行一些DOM
操做beforeUpdate
→ 能够在该钩子中进一步地更改状态,这不会触发附加的渲染过程updated
→ 能够执行依赖于DOM
的操做。但在大多数状况下,应避免在该钩子中更改状态,由于这可能致使更新无限循环destroyed
→ 能够执行一些优化操做,例如清空定时器,清理缓存,解除事件绑定等「beforeCreate
」、「created
」、「beforeMount
」、「mounted
」、「beforeUpdate
」、「updated
」、「beforeDestroy
」、「destroyed
」
❗ 小知识:
<keep-alive>
拥有本身独立的钩子函数 activated
| deactivated
activated
→ 在被<keep-alive>
包裹的组件中才有效,当组件被激活时使用该钩子deactivated
→ 在被<keep-alive>
包裹的组件中才有效,当组件被中止时使用该钩子父组件挂载完成必须是等到子组件都挂载完成以后,才算父组件挂载完,因此父组件的
mounted
确定是在子组件mounted
以后
So:「父」beforeCreate → 「父」created → 「父」beforeMount → 「子」beforeCreate → 「子」created → 「子」beforeMount → 「子」mounted → 「父」mounted
子组件更新过程(取决于对父组件是否有影响)
父组件更新过程(取决于对子组件是否有影响)
销毁过程
好比有一个父组件Parent和子组件Child,若是父组件监听到子组件挂载
mounted
就作一些逻辑处理、
// Parent.vue <Child @mounted='doSomething' /> // Child.vue mounted(){ this.$emit('mounted') } 复制代码
// Parent.vue <Child @hook:mounted='doSomething' /> # @hook能够监听其余生命周期 复制代码
vue
中,父组件能够经过prop
将数据传递给子组件,但这个prop
只能由父组件来修改,子组件修改的话会抛出错误$emit
由子组件派发事件,并由父组件接收事件进行修改因为vue
提倡单向数据流,即父级props
的更新会流向子组件,但反过来则不行。这是为了防止意外的改变父组件的状态,使得应用的数据流变得难以理解。若是破坏了单项数据流,当应用复杂时,debug的成本将会很是高
父子组件通讯
props
/ event
$parent
/ $children
ref
provide
/ inject
.sync
非父子组件通讯
eventBus
$root
vuex
$attr
/ $listeners
provide
/ 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) 复制代码
多个组件经过同一个挂载点进行组件的切换,
is
的值是哪一个组件的名称,那么页面就会显示哪一个组件
<div :is='xxx'></div> 复制代码
组件是能够在它们本身的模板中调用自身的,不过它们只能经过
name
选项来作这件事
首先咱们要知道,既然是递归组件,那么必定要有一个结束的条件,不然就会致使组件无限循环使用,最终出现
max stack size exceeded
的错误,也就是栈溢出。因此,咱们应该使用v-if = 'false'
来做为递归组件的结束条件,当遇到v-if = 'false'
时,组件将不会再进行渲染
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
的状态存储是响应式的。当vue
组件从store
中读取状态时,若store
中的状态发生变化,那么相应的组件也会获得高效更新store
中的状态,改变store
中的状态惟一方法是显示地提交mutation
(commit
)。这样使得咱们能够方便地跟踪每个状态的变化vuex
中全部的状态更新的惟一方式都是提交mutation
,异步操做须要经过action
来提交mutation
(dispatch
)。这样使得咱们能够方便地跟踪每个状态的变化,从而让咱们可以实现一些工具帮助咱们更好地使用vuex
每一个mutation
执行完后都会对应获得一个新的状态变动,这样devtools
就能够打个快照存下来,而后就能够实现time-travel
了。
若是mutation
支持异步操做,就没有办法知道状态是什么时候更新,没法很好的进行状态追踪,影响调试效率
v-if
指若是条件不成立则不会渲染当前指令所在节点的Dom
元素,会在切换过程当中对条件块的事件监听器和子组件进行销毁和重建v-show
只是基于css
进行切换,无论条件是什么,都会进行渲染(切换display:block | none
)So:v-if
切换的开销较大,而v-show
初始化的开销较大,因此在须要频繁切换显示和隐藏的Dom
元素时,使用v-show
更合适,渲染后不多进行切换则使用v-if
较合适
computed
是依赖于其余属性的一个计算值,而且具有缓存,只有当依赖的值发生变化才会更新(自动监听依赖值的变化,从而动态返回内容)watch
是在监听的属性发生变化的时候,触发一个回调,在回调中执行一些逻辑(引用晨曦时梦见兮) 实际上computed
会拥有本身的watcher
,它具备一个dirty
属性来决定computed的值是须要从新计算仍是直接复用以前的值,例如这个例子:
computed: { sum() { return this.count + 1 } } 复制代码
sum
第一次进行求值的时候会读取响应式属性count
,收集到这个响应式数据做为依赖。而且计算出一个值来保存在自身的value
上,把dirty
设为false,接下来在模板里再访问sum
就直接返回这个求好的值value
,并不进行从新求值。So:computed
和watch
区别在于用法上的不一样,computed
适合在模板渲染中,若是是须要经过依赖来获取动态值,就可使用计算属性。而若是是想在监听值变化时执行业务逻辑,就使用watch
v-html
能够用来识别HTML标签并渲染出去
致使问题: 在网站上动态渲染任意Html
,很容易致使受到Xss
攻击,因此只能在可信内容上使用v-html
,且永远不能用于用户提交的内容上
包裹在<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>
复制代码
.prevent
:拦截默认事件.passive
:不拦截默认事件.stop
:阻止事件冒泡.self
:当事件发生在该元素而不是子元素的时候会触发.capture
:事件侦听,事件发生的时候会调用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(singlg-page application)
仅在Web页面初始化时加载相应的Html
,JavaScript
,Css
,一旦页面加载完成,SPA
不会由于用户的操做而进行页面的从新加载或跳转,取而代之的是利用路由机制实现Html
内容的变换,UI
与用户的交互,避免页面的从新加载
JavaScript
,Css
统一加载,部分页面按需加载)在
<style>
标签上写入scoped
便可
route
表示路由信息对象,包括path
,params
,hash
,query
,fullPath
,matched
,name
等路由信息参数router
表示路由实例对象,包括了路由的跳转方法,钩子函数等beforeEach
,beforeResolve
,afterEach
beforeEnter
beforeRouteEnter
,beforeRouteUpdate
,beforeRouteLeave
导航解析流程:
beforeRouteLeave
离开守卫beforeEach
守卫beforeRouteUpdate
守卫beforeEnter
守卫beforeRouteEnter
守卫beforeResolve
守卫afterEach
守卫Dom
更新beforeRouteEnter
守卫中传给next
的回调hash
模式会在url
上显示'#',而history
模式没有hash
模式能够正常加载到hash
值对应的页面,history
模式没有处理的话,会返回404,通常须要后端将全部页面都配置重定向到首页路由hash
模式能够支持低版本浏览器和IEhash
模式
#
后面hash
值的变化,不会致使浏览器向服务器发出请求,浏览器不发出请求,就不会刷新页面,同时经过监听hashchange
事件能够知道hash
发生了哪些变化。根据hash
变化来实现页面的局部更新history
模式
history
模式的实现,主要是Html5
标准发布的两个Api(pushState
和replaceState
),这两个Api能够改变url
,可是不会发送请求,这样就能够监听url
的变化来实现局部更新path
属性过程当中,使用动态路径参数,以冒号开头{ path:'/details/:id', name:'Details', components:Details } # 访问`details`前缀下的路径,例如`details/1`,`details/2`等,都会映射到`Details`这个组件 复制代码
/details
下的路由时,参数值会被设置到this.$route.params
下,因此经过这个属性能够获取动态参数this.$route.params.id 复制代码
params
name
,不能用path
url
上query
path
,不能用name
name
可使用path
路径url
上state
中)Ajax
请求次数,有些情景能够直接从内存中的State
获取vuex
中的State
就会从新变回初始化状态State
:vuex
的基本数据,用来存储变量Getter
:从基本数据state
派生的数据,至关于state
的计算属性Mutation
:提交更新数据的方法,必须是同步的(须要异步则使用action
)。每一个mutation
都有一个字符串的事件类型(type
)和一个回调函数(handler
)Action
:和mutation
的功能大体相同,不一样在于
action
提交的是mutation
,而不是直接变动状态action
能够包含任意异步操做Module
:模块化vuex
,可让每个模块拥有本身的state
,mutation
,action
,getter
,使得结构清晰,方便管理vuex
就是一个仓库,仓库里面放了不少对象,其中state
就是数据源存放地state
里面存放的数据是响应式的,Vue
组件从store
中读取数据,如果store
中的数据改变,依赖这个数据的组件也会更新数据mapState
把全局的state
和getters
映射到当前组件的computed
计算属性中getters
能够对state
进行计算操做,能够把它看作store
的computed
计算属性getters
能够在多个组件之间复用getters
vuex
的state
里action
里,方便复用;若是不须要复用这个请求,直接写在Vue
文件里会更方便
Vue.js
是构建客户端应用程序的框架,默认状况下,能够在浏览器中输出Vue
组件,进行生成Dom
和操做Dom
。然而,也能够将同一个组件渲染为服务器的Html
字符串,将它们直接发送给浏览器,最后将这些静态标记激活为客户端上彻底能够交互的应用程序
即:
SSR
大体意思就是vue
在客户端将标签渲染成整个html
片断的工做交给服务端完成,服务端造成的Html
片断直接返回给前端,这个过程就叫作SSR
(服务端渲染)
服务端渲染SSR的优势:
SPA
页面的内容是经过Ajax
获取,而搜索引擎爬取工具并不会等待Ajax
异步完成后再抓取页面内容,因此在SPA
中是抓取不到页面经过Ajax
获取到的内容;而SSR
是直接由服务器返回已经渲染好的页面(数据已经包含在页面中),因此搜索引擎爬取工具能够抓取渲染好的页面SPA
会等待全部Vue
编译后的Js
文件都下载完成后,才开始进行页面渲染,文件下载等须要必定时间,因此首屏渲染须要必定时间;而SSR
直接由服务器渲染好页面直接返回显示,无需等待下载Js
文件后再去渲染,因此SSR
有更快的内容到达时间服务端渲染SSR的缺点:
beforeCreate
,created
生命周期钩子,这会致使一些外部扩展库须要特殊处理,才能在服务端渲染应用程序中运行;而且与能够部署在任何静态文件服务器上的彻底静态单页面应用程序SPA
不一样,服务器渲染应用程序须要处于NodeJs
环境下运行NodeJs
中渲染完整的应用程序,显然会比仅仅提供静态文件的server
更占用CPU资源