一、什么是SPA,及他的优缺点
SPA:single page application 单页面应用 仅在web页面初始化时加载相应的HTML、JavaScript和CSS。一旦加载完成,不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是路由机制实现内容的变换、UI与用户的交互,避免页面的重新加载。
优点:【1】体验好、快 【2】相对服务器压力小 【3】前后端职责分离
缺点:【1】首屏首次加载慢 【2】不利于SEO(搜索引擎优化)
二、new Vue() 发生了什么
创建vue实例,它内部执行了根实例的初始化过程
1.initMixin(Vue) //实现init函数 [1].合并配置项,如路由,状态管理,渲染函数等;[2].initLifecycle(vm)//初始化$parent,$root,$children,$refs;初始化生命周期 [3].initEvents(vm)//处理父组件传递的监听器; 初始化事件 [4].initRender(vm)//slots,$scopedSlots,_c(),$createElement(); 初始化渲染 [5].callHook(vm, 'beforeCreate'); 调用生命周期钩子函数 [6].initInjections(vm)//获取注入数据,隔代传值;初始化Injections [7].initState(vm)//初始化props,data,methods,computed,watch; [8].initProvide(vm)//提供注入数据 初始化Provide [8]//callHook(vm, 'created') 调用生命周期钩子函数
初始化工作完成后,判断用户是否传入el选项,如果传入调用$mount函数进入模板编译与挂载阶段;如果没有传入el选项,则不进入下一个生命周期阶段,需要用户手动执行
总结:主要逻辑:合并配置,调用一些初始化函数,触发生命周期钩子函数,调用$mount开启下一个阶段
三、Vue.use是干啥的,原理是啥
用来使用插件的,可以在插件中扩展组件、指令、方法等
原理:
1.检查是否已经注册,如果已经注册,则跳出
2.处理入参
3.执行注册方法,调用定义好的install方法,传入处理好的参数,如果没有install方法并且插件本身为function则直接进行注册
四、请说一下响应式数据的理解
核心是观察者模式。数据是被观察的一方,发生改变时,通知所有的观察者。
1.Object:通过Object.defineProperty方法来实现的
【1】.data通过observe(Object.defineProperty方法)转换成getter/setter的形式来追踪变化
【2】.当外界通过watcher(观察者)读取数据时,触发getter从而将watcher添加到依赖中
【3】.当数据发生变化时,触发setter,从而向Dep中的依赖(watcher)发送通知
【4】.watcher接受到通知后,会向外界发送通知,从而触发视图更新等。
2.数组同上,不同点在于它没有Object.defineProperty方法,它的setter方法是通过重写方法来实现的
注意:Proxy与Object.defineProperty 优劣对比
Proxy优点:1.可以直接监听对象而非属性(Object.defineProperty只能监听属性) 2.可以直接监听数组的变化(Object.defineProperty不能监听数组变化) 3.有多种拦截方法(Object.defineProperty不具备拦截方法) 4.返回的是一个新的对象,我们可以直接操作这个新的对象(Object.defineProperty只能遍历属性修改)
Object.defineProperty优点:兼容性好
五、虚拟DOM
1.简介:通过js描述的DOM结点,
2.why: 操作真实的DOM很耗性能
3.vue 通过vnode类来实例化不同的dom结点。通过补缴差异化的结点来渲染,以节约性能
4.Dom-Diff算法 patch过程---》以新的VNode为基准,改造旧的oldVNode使之成为跟新的VNode一样,这就是patch过程要干的事
5.虚拟DOM过程就是render函数结果VNode到视图显示的过程
六、模板编译
把用户写的模板进行编译,就会产生VNode。模板编译过程就是把用户写的模板经过一系列处理最终生成render函数的过程。
AST:抽象语法树
实施流程:模板解析阶段------>优化阶段------>代码生成阶段
模板解析阶段:<template></template>标签内的模板使用正则等方式解析成抽象语法树(AST) 包括(1)parseHTML HTML解析器(2)parseText 文本解析(3)parseFilters 过滤器解析
优化阶段:(1)在AST中找到所有静态结点并打上标记 (2)在AST中找到所有静态根结点并打上标记
代码生成阶段:根据模板对应的抽象语法树AST生成一个函数,通过调用这个函数就可以得到模板对应的虚拟DOM
七、Vue的生命周期方法有哪些?一般在哪一步发起请求及原因
初始化阶段:为Vue实例上初始化一些属性,事件以及相应式数据 包含的方法: new Vue() beforeCreate create
模板编译阶段:将模板编译成渲染函数
挂载极端:将实例挂载到指定的DOM上,及将模板渲染到真实的DOM上 包含的方法:beforeMount mounted beforeUpdate update
销毁阶段:将实例自身从父组件中删除,并消除依赖追踪及事件监听器 包含的方法:beforeDestory destoryed
总共分为8个阶段:创建前/后,载入前/后,更新前/后,销毁前/后。
1.创建前/后:
[1].beforeCreate阶段:vue实例的挂载el和数据对象data都是undefined,还没有初始化 data、methods、computed、watch上的数据和方法不能被访问
[2].created阶段:vue实例的数据对象data有了,el还没有 可以做一些初始数据的获取
2.载入前/后:
[1].beforeMount阶段:vue实例的el和数据对象data都初始化了,还没有挂载之前为虚拟的dom结点 当前阶段虚拟Dom已经创建完成,即将开始渲染。在此时也可以对数据更改,不会触发updated。
[2].mounted阶段:vue实例挂载完成,data.message成功渲染。 真实的Dom挂载完毕,数据完成双向绑定,可以访问到Dom结点,使用$refs属性对Dom进行操作。
3.更新前/后:
[1].beforeUpdate阶段:数据更新时调用,虚拟DOM打补丁之前,更新之前访问现有的DOM,比如手动一处已添加的事件监听器。 可以更改数据,不会造成重渲染
[2].updated阶段:虚拟DOM重新渲染和打补丁后,避免在这个阶段操作数据,防止死循环。
4.销毁前/后:
[1].beforeDestory阶段:实例销毁前调用,还可以用,this能获得实例,用于销毁定时器,解绑事件
[2].destoryed阶段:实例销毁后调用,调用后所有事件监听会被移除,所有的子实例都会被销毁
第一次页面加载顺序:beforeCreate ----> created ----> beforeMounted ----> mounted
可以在created中请求数据,如果需要页面加载完成之后的条件的话,可以在mounted中请求
八、生命周期钩子是如何实现的
生命周期钩子函数就是回调函数,当创建组件实例的过程中会调用对应的钩子函数
九、Vue的父组件和子组件生命周期执行顺序
第一次页面加载顺序:beforeCreate ----> created ----> beforeMounted ----> mounted
父子mount: 父beforeCreate ----> 父created ----> 父beforeMounted ----> 子beforeCreate ----> 子created ----> 子beforeMounted ----> 子mounted ----> 父mounted
父子update: 父beforeUpdate ----> 子beforeUpdate ----> 子update ----> 父update
父/子update: 父/子beforeUpdate ----> 父/子update
父子destory: 父beforeDestory ----> 子beforeDestory ----> 子destoryed ----> 父destoryed
总结:父组件等待子组件完成后,才执行自己对应完成的钩子
十、数据相关的方法
1.$watch方法
data () {a: 1,b: 2,c: {d: 3,e: 4}}
两种监听方法:
[1].第一种:
this.$watch(() => this.a + this.b,(newVal, oldVal) => {console.log('newVal:', newVal, ' oldVal:', oldVal)})
或者
this.$watch('a',(newVal, oldVal) => {console.log('newVal:', newVal, ' oldVal:', oldVal)})
setTimeout(() => {this.a = 111}, 1000)
[1].第二种:
watch: {a (val) {console.log('a:', val)}},
2.$set方法
this.c.d=100 => this.$set(this.c, 'd', 100)
3.$delete方法
this.$delete(this.c, 'd')
十一、事件相关方法
vm.$on(event,callback) 监听当前实例的自定义事件
vm.$emit(eventName,[...args]) 触发当前实例上的事件
vm.$off([event,callback]) 移除自定义事件监听 1.如果没有参数,移除所有的监听 2.如果只提供事件,则移除该事件所有监听 3.如果同时提供了事件与回调,则只移除这个回调的监听器
vm.$once(event,callback) 监听一个自定义事件,只触发一次。一旦触发,监听器就移除
十二、生命周期相关方法
1.vm.$mount([elementOrSelector])
作用:如果Vue实例在实例化时没有收到el选项,则它处于“未挂载”状态,没有关联的DOM元素。可以使用vm.$mount()手动地挂载一个未挂载的实例。如果没有提供elementOrSelector参数,模板将被渲染为文档之外的元素,并且你必须使用原生DOM API把它插入文档中。这个方法返回实例本身,因此可以链式调用其他实例方法。
2.vm.$forceUpdate()
作用:迫使Vue实例重新渲染。注意它仅仅影响实例本身和插槽内容的子组件,而不是所有子组件
3.vm.$nextTick([callback])
用法:将回调延迟到下次DOM更新循环后执行。在修改数据后立即使用它,然后等待DOM更新
JS的运行机制:JS执行的是单线程,它是基于事件循环的。
1.所有同步任务都在主线程上执行,形成一个执行栈。
2.主线程之外,还存在一个“任务队列”。只要异步任务有了结果,就在“任务队列”之中放置一个事件。
3.一旦“执行栈”中所有的同步任务执行完毕,系统就会读取“任务队列”。那些对应的异步任务,结束等待状态,进入执行栈,开始执行。
4.主线程不断重复上面第三部。
4.vm.$destroy()
用法:完全销毁一个实例。清理它与其他实例的连接,解绑它的全部指令及事件监听器。
十三、Vue中的组件的data 为什么是一个函数
组件中的data写成一个函数,数据以函数返回值形式定义,这样复用一次组件,就返回一份新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件维护自己的数据。而单纯的写成对象形式,就使得所有组件实例公用一份data,就会造成一个变,全都会变得结果。
十四、组件间的通信有哪几种
父子组件通信、隔代组件通信、兄弟组件通信
1.props/$emit 适用于父子间通信
2.ref于$parent/$children 适用于父子间通信
3.EventBus($emit/$on) 适用于父子、隔代、兄弟间通信
4.$attrs/$listeners 适用于隔代通信
5.provide/inject 适用于隔代通信
6.vuex 适用于父子、隔代、兄弟间通信
十五、组件中写name选项有哪些好处
1.可以通过递归找到组件
2.通过name属性实现缓存功能(keep-alive)
3.通过name识别组件(跨组件通信)
十六、keep-alive 平时使用在哪里?原理?
主要用于组件缓存,采用LRU算法。最近最久未使用法。
常用include/exclude,允许组件有条件缓存
通过生命周期activated/deactivated,得知当前是否是处于活跃状态
十七、Vue.minxin的使用场景和原理
Vue.minxin作用是抽离公共业务逻辑,采用策略模式进行合并,如果混入数据冲突,采用就近原则。
十八、全局API分析
1.Vue.extend(options)
使用基础Vue构造器,创建一个“子类”。参数是一个高寒组件选项的对象。
2.Vue.directive(id,[definition])
自定义指令,如同v-model、v-show、v-html
注册或获取全局指令
// 注册 及钩子函数
Vue.directive('my-directive',{bind:function(){},inserted:function(){},update:function(){},componentUpdated:function(){},unbind:function(){}})
// 注册 (指令函数)
Vue.directive('my-directive',function(){// 这里将会被 `bind` 和 `update` 调用})
// getter,返回已注册的指令
var myDirective=Vue.directive('my-directive')
3.Vue.filter(id,[definition])
作用:注册或获取全局过滤器
4.Vue.component(id,[definition])
作用:注册或获取全局组件。注册还会自动使用给定的id设置组件的名称
5.Vue.compile(template)
作用:在render函数中编译模板字符串。只在独立构建时有效
6.Vue.observable(object)
用法:让一个对象可响应。vue内部会用它来处理data函数返回的对象。返回的对象可以直接用于渲染函数和计算属性内,并且会在反生改变时触发相应的更新。也可以作为最小化的夸组件状态存储器。
十九、Vue-router有几种钩子函数?具体是什么及执行流程是怎样的?
Vue-router钩子函数的作用:用来拦截导航,让它完成跳转或取消
钩子的分类:全局守卫 路由独享守卫 局部守卫
全局守卫:beforeEach//路由跳转前触发 beforeResolve//路由跳转前触发,beforeEach之后,afterEach之前 afterEach//路由跳转后触发
路由独享守卫:只能在这个路由下起作用 beforeEnter//beforeEach前调用
局部守卫:组件内执行的钩子函数,相当于为配置路由的组件添加的生命周期钩子函数。beforeRouteEnter ---> beforeRouteUpdate ---> beforeRouteLeave
路由A ---> 路由B 流程:
A组件的 beforeRouteLeave ---> 全局的beforeEach ---> 复用组件的beforeRouteUpdate ---> B路由的beforeEnter ---> B组件的beforeRouteEnter ---> 全局的beforeResolve ---> 导航确认 ---> 全局的afterEach ---> 触发DOM更新 ---> 用创建好的实例用beforeRouteEnter
二十、vue-router两种模式的区别
vue-router有三种模式:hash、history、abstract
1.hash模式:hash + hashChange
hash虽然在URL中,但不被包括在HTTP请求中。hash不会重加载页面,通过监听hash(#)的变化来执行js代码从而改变页面。
window.addEventListener('hashchange',function(){self.urlChange()})
使用URL hash值来作路由,支持所有浏览器,包括不支持HTML5 History Api的浏览器;
2.history模式:history + popState
HTML5推出history API,由pushState()记录操作历史,监听popstate事件来监听到状态变更;
依赖HTML5 History Api和服务器配置。
3.abstract
支持所有JavaScript运行环境,如Node.js服务器端。如果发现没有浏览器的API,路由会自动强制进入这个模式。
二十一、Vue中key的作用和公众原理,及理解
例如:v-for="(item,itemIndex) in tabs" :key="itemIndex"
key的作用主要是为了高效的更新虚拟DOM,其原理是vue在patch过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使得整个patch过程更加高效,减少DOM操作量,提高性能。
二十二、Vue中的diff原理
vue的diff算法是平级比较,不考虑跨级比较的情况。内部采用深度递归的方式+双指针的方式进行比较。
二十三、v-if 与 v-for的优先级
1.v-for优先于v-if被解析
2.如果同时出现,每次渲染都是先执行循环条件。比较浪费性能
3.要避免出现这种情况。可以在外层嵌套template,用v-if判断。
4.如果条件出现在循环内部,通过计算提前过滤掉
二十四、v-if与v-show的区别
v-if 条件变成真时,才渲染。
v-show 不管是否是否为真,都要渲染,每次条件改变,只是基于css的display进行切换
所以:v-if用于不需要频繁切换的场景;v-show适用于频繁切换条件
二十五、computed和watch的区别和运用场景
computed:计算属性。依赖其他属性值,并且computed的值有缓存,只有它依赖的属性值发生改变,下一次获取computed的值时才会重新计算computed的值;
watch:监听数据的变化。每当监听数据变化时都会执行回调进行后续操作。
运用场景:
1.当我们需要进行数值计算,并且依赖于其他数据时,应该使用computed,因为可以利用computed的缓存特性,避免每次获取值时,都要重新计算;
2.当我们需要在数据变化时执行异步或开销较大的操作时,应该使用watch,使用watch选项允许我们执行异步操作,限制我们执行该操作的评率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
二十六、如何理解自定义指令?
从编译原理 => 代码生成 => 指令钩子实现 来概述
1.在生成ast语法树时,遇到指令会给当前元素添加directives属性
2.通过genDirextives生成指令代码
3.在patch前将指令的钩子提取到cbs中,在patch过程中调用对应的钩子
4.当执行指令对应的钩子函数时,调用对应指令定义的方法
二十七、V-model的原理是什么?
v-model本质就是一个语法糖,可以看成是value+input方法的语法糖。可以通过model书信的prop和event书信来进行自定义。元素的v-model,会根据标签的不同生成不同的事件和属性。
v-model在内部为不同的输入元素使用不同的属性并抛出不同的事件:
1.text和textarea元素使用value属性和input事件
2.checkbox和radio元素使用checked属性和change事件
3.select字段将value作为prop并将change作为事件
二十八、Vue性能优化
1)编码阶段
尽量减少data中的数据,data中的数据会增加getter和setter,会收集对应的watcher
使用key:有利于将旧虚拟结点列表与新虚拟结点相同的结点更新。
使用冻结对象:可以优化页面渲染
使用计算属性:计算属性有缓存,可以减少计算,提高性能
使用v-show代替v-if:避免频繁渲染
SPA页面采用keep-alive缓存组件
使用路由懒加载、异步组件
防抖、节流
第三方模块按需导入
长列表滚动到可视区域动态加载
图片懒加载
2)用户体验
骨架屏
可以使用缓存