目录
1、说说你对 SPA 单页面的理解,它的优缺点分别是什么?
2、v-show 与 v-if 有什么区别?
3、怎样理解 Vue 的单向数据流?
4、computed 和 watch 的区别和运用的场景?
5、Vue 的父组件和子组件生命周期钩子函数执行顺序?
6、在哪个生命周期内调用异步请求?
7、在什么阶段才能访问操作DOM?
8、父组件可以监听到子组件的生命周期吗?
9、谈谈你对 keep-alive 的了解?
10、组件中 data 为什么是一个函数?
11、什么是 Vuex ?
12、Vuex 是通过什么方式提供响应式数据的?
13、$store 是如何是如何挂载到实例 this 上的?
14、在 Vue 组件中获得 Vuex 状态
15、使用过 Vue SSR 吗?说说 SSR?
16、vue-router 路由模式有几种?
17、能说下 vue-router 中常用的 hash 和 history 路由模式实现原理吗?
18、Vue 框架怎么实现对象和数组的监听?
19、Proxy 与 Object.defineProperty 优劣对比
20、Vue 怎么用 vm.$set() 解决对象新增属性不能响应的问题 ?
21、虚拟 DOM 的优缺点?
22、虚拟 DOM 实现原理?
23、Vue 中的 key 有什么作用?
24、你有对 Vue 项目进行哪些优化?
25、MVC 和 MVVM 区别
26、谈谈你对 Vue 生命周期的理解?
27、Vue 2.0 响应式数据的原理
28、Vue 如何检测数组变化
29、Vue 组件间通信有哪几种方式?
30、vue-router 路由钩子函数是什么?执行顺序是什么?
31、完整的导航解析流程:
32、Vuex 页面刷新数据丢失怎么解决?
33、Vuex 为什么要分模块并且加命名空间?
34、vue 中使用了哪些设计模式?
35、Vue.mixin 的使用场景和原理
36、Vue.extend 作用和原理
37、写过自定义指令吗?原理是什么?
38、Vue 修饰符有哪些?
39、Vue 模板编译原理
40、生命周期钩子是如何实现的
41、函数式组件使用场景和原理
42、diff 算法了解吗
43、webpack的作用
44、webpack怎么工作的过程
45、vue-cli提供的几种脚手架模板
46、vue-loader是什么?用途有哪些?
47、如何优化SPA应用的首屏加载速度慢的问题?
48、Vue的优点
49、 v-model的原理?
50、route和router有什么区别?
51、说说 vue 内置指令
52、Vue中常见内存泄漏
53、$nextTick有什么作用?
SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
优点:
用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
基于上面一点,SPA 相对对服务器压力小;
前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;
API 共享,同一套后端程序代码不用修改就可以用于Web界面、手机、平板等多种客户端
缺点:
不支持低版本的浏览器,最低只支持到IE9;
初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。
v-if 是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。
所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。
扩展补充:display:none 、 visibility:hidden 和 opacity:0 之间的区别?
三者公共点都是隐藏。不同点:
一、是否占据空间。
display:none,隐藏之后不占位置;visibility:hidden、opacity:0,隐藏后任然占据位置。
二、子元素是否继承。
display:none --- 不会被子元素继承,父元素都不存在了,子元素也不会显示出来。
visibility:hidden --- 会被子元素继承,通过设置子元素 visibility:visible 来显示子元素。
opacity:0 --- 会被子元素继承,但是不能设置子元素 opacity:0 来先重新显示。
三、事件绑定。
display:none 的元素都已经不存在了,因此无法触发他绑定的事件。
visibility:hidden 不会触发他上面绑定的事件。
opacity:0 元素上面绑定的事件时可以触发的。
四、过度动画。
transition对于display是无效的。
transition对于visibility是无效的。
transition对于opacity是有效的。
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。
computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;
运用场景:
当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:
加载渲染过程
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
子组件更新过程
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
父组件更新过程
父 beforeUpdate -> 父 updated
销毁过程
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。但是推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:
能更快获取到服务端数据,减少页面 loading 时间;
ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;
在钩子函数 mounted 被调用前,Vue 已经将编译好的模板挂载到页面上,所以在 mounted 中可以访问操作 DOM。
可以在子组件的生命周期手动通过 $emit 触发父组件的事件
可以在父组件引用子组件时通过 @hook 来监听即可,如:@hook:mounted="handleHookMounted", 当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,其有以下特性:
一般结合路由和动态组件一起使用,用于缓存组件;
提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高;
对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。
keep-alive 运用了 LRU 算法,选择最近最久未使用的组件予以淘汰。
因为组件是用来复用的,且 JS 里对象是引用关系,如果组件中 data 是一个对象,那么这样作用域没有隔离,子组件中的 data 属性值会相互影响,如果组件中 data 选项是一个函数,那么每个实例可以维护一份被返回对象的独立的拷贝,组件实例之间的 data 属性值不会互相影响;而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。
(1)Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
(2)改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。
主要包括以下几个模块:
State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。(提供一个响应式数据)
Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。(借助Vue的计算属性computed来实现缓存)
Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。(更改state方法)
Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。(触发mutation方法)
Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。(Vue.set动态添加state到响应式数据中)
Vuex的两大核心State和Getters都是响应式的,即当state或getter的某一个状态改变时,它是能驱动视图发生相应的改变。
Vue.js提供了Vue.use()方法用来给Vue.js安装插件,当调用Vue.use(Vuex)时,内部通过调用Vuex插件的install方法来进行插件的初始化。
由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在[计算属性]中返回某个状态。每当数据变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM。
Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,也可以将同一个组件渲染为服务端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。
即:SSR大致的意思就是vue在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的html 片段直接返回给客户端这个过程就叫做服务端渲染。
服务端渲染 SSR 的优缺点如下:
(1)服务端渲染的优点:
更好的 SEO:因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;
更快的内容到达时间(首屏加载更快):SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;
(2) 服务端渲染的缺点:
更多的开发条件限制:例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;
更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 ( high traffic ) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。
vue-router 有 3 种路由模式:hash、history、abstract:
hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器;
history : 依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式;
abstract : 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.
(1)hash 模式的实现原理
早期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。
hash 路由模式的实现主要是基于下面几个特性:
URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;
hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;
可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;
我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。
(2)history 模式的实现原理
HTML5 提供了 History API 来实现 URL 的变化。其中做最主要的 API 有以下两个:history.pushState() 和 history.repalceState()。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录。
history 路由模式的实现主要基于存在下面几个特性:
pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;
我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);
history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。
Vue 框架是通过遍历数组 和递归遍历对象,从而达到利用 Object.defineProperty() 也能对对象和数组(部分方法的操作)进行监听。
Proxy 的优势如下:
Proxy 可以直接监听对象而非属性;
Proxy 可以直接监听数组的变化;
Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;
Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;
Object.defineProperty 的优势如下:
兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。
受现代 JavaScript 的限制 ,Vue 无法检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。
但是 Vue 提供了 Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value) 来实现为对象添加响应式属性。
vm.$set 的实现原理是:
如果目标是数组,直接使用数组的 splice 方法触发相应式;
如果目标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理( defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法)
优点:
保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限;
无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;
跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。
缺点:
无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。
虚拟 DOM 的实现原理主要包括以下 3 部分:
用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
diff 算法 — 比较两棵虚拟 DOM 树的差异;
pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。
key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速。
更准确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。
更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快。
(1)代码层面的优化
对象层级不要过深,否则性能就会差。
不需要响应式的数据不要放在 data 中(可以使用 Object.freeze() 冻结数据)
异步路由
适当采用 keep-alive 缓存组件
防抖、节流的运用
v-if 和 v-show 区分使用场景
computed 和 watch 区分使用场景
v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
长列表性能优化
事件的销毁
图片资源懒加载
路由懒加载
第三方插件的按需引入
优化无限列表性能
服务端渲染 SSR or 预渲染
(2)Webpack 层面的优化
Webpack 对图片进行压缩
减少 ES6 转为 ES5 的冗余代码
提取公共代码
模板预编译
提取组件的 CSS
优化 SourceMap
构建结果输出分析
Vue 项目的编译优化
(3)基础的 Web 技术的优化
开启 gzip 压缩
浏览器缓存
CDN 的使用
使用 Chrome Performance 查找性能瓶颈
MVC:
MVC全名是 Model View Controller,时模型 - 视图 - 控制器的缩写,一种软件设计典范。
Model(模型):是用于处理应用程序数据逻辑部分。通常模型对象负责在数据库中存取数据。
View(视图):是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。
Controller(控制器):是应用程序处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
MVC的思想:一句话描述就是Controller负责将Model的数据用View显示出来,换句话说就是在Controller里面把Model的数据赋值给View。
MVVM:
MVVM新增了VM类。
Model 是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,对于前端来说就是后端提供的 api 接口。
View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建 。
ViewModel层:做了两件事达到了数据的双向绑定,一是将【模型】转化成【视图】,即将后端传递的数据转化成所看到的页面。 实现的方式时:数据绑定。二是将【视图】转化成【模型】,即将所看到的页面转换成后端的数据。实现的方式是:DOM事件监听。
MVVM与MVC的区别:
MVVM与MVC最大的区别就是:实现了View和Model的自动同步,也就是当Model的属性改变时,我们不用再手动操作Dom元素来改变View的显示。 而是改变属性后该属性对应的View层显示会自动改变(对应Vue数据驱动的思想)
整体看来,MVVM比MVC精简很多,不仅简化了业务与界面的依赖,还解决了数据频繁更新的问题,不用再用选择器操作DOM元素。因为在MVVM中,View不知道Model的存在,Model和ViewModel也察觉不到View,这种低耦合模式提高代码的可重用性。
严格的MVVM要求View不能和Model直接通信,而Vue提供了$refs这个属性,让Model可以直接操作View,违反了这一规定,所以是Vue没有完全遵循MVVM。
(1)生命周期是什么?
Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。
(2)各个生命周期的作用
beforeCreate:组件实例被创建之初,组件的属性生效之前。
created:组件实例已经完全创建,属性也绑定,但真实 dom 还没有生成,$el 还不可用,如果非要想与 DOM 进行交互,可以通过vm.$nextTick 来访问 DOM。
beforeMount:在挂载开始之前被调用:相关的 render 函数首次被调用。
mounted:el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。
beforeUpdate:组件数据更新之前调用,发生在虚拟 DOM 打补丁之前。可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
update:组件数据更新之后,当前阶段组件 Dom 已经完成更新。要注意的是避免在此期间更新数据,因为这个可能导致无限循环的更新,该钩子在服务器渲染期间不被调用。
activited:keep-alive 专属,组件被激活时调用
deactivated:keep-alive 专属,组件被销毁时调用
beforeDestory:组件销毁前调用。在这一步,实力仍然完全可用。我们可以在这时进行善后收尾工作,比如清除定时器。
destoryed:组件销毁后调用。调用后,Vue实例指示的东西都会解绑定,所有的事件监听器会被移除,左右的子实例也会被销毁,该钩子在服务器端渲染不被调用。
整体思路是数据劫持 + 观察者模式
对象内部通过 defineReactive 方法,使用 Object.defineProperty 将属性进行劫持(只会劫持已存在的属性),数组则是通过重写数组来实现。当页面使用对应属性时,每个属性都拥有自己的 dep 属性,存在它所依赖的 watcher (依赖收集)get,当属性变化后会通知自己对应的 watcher 去更新(派发更新)set。
1、Object.defineProperty 数据劫持
2、使用 getter 收集依赖 ,setter 通知 watcher派发更新。
3、watcher 发布订阅模式。
数组考虑性能原因没有用 defineProperty 对数组的每一项进行拦截,而是选择对7种数组(push,shift,pop,splice,unshift,sort,reverse)方法进行重写(AOP 切片思想)。
所以在 Vue 中修改数组的索引和长度无法监控到。需要通过以上7种变异方法修改数组才会触发数组对应的watcher进行更新。
Vue 组件间通信只要指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信。
(1)props / $emit 适用父子组件通信。
父组件向子组件传递数据是通过props传递的,子组件传递给父组件是通过$emit触发事件来做到的。
(2)ref 与children 适用父子组件通信
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
children:访问父 / 子实例
(3)EventBus (on) 适用于父子、隔代、兄弟组件通信。
这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。
(4)listeners 适用于隔代组件通信
(5)provide / inject 适用于隔代组件通信。
祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
(6)Vuex 适用于 父子、隔代、兄弟组件通信。
路由钩子的执行流程,钩子函数种类有:全局守卫、路由守卫、组件守卫。
全局守卫:前置守卫、后置钩子(beforeEach,afterEach)beforeResolve
路由守卫:beforeEnter
组件守卫:beforeRouteEnter(不能获取组件实例this)、beforeRouteUpdate、beforeRouteLeave
先执行beforRouteEnter,再执行组件周期钩子函数beforeCreate,可以通过 next 获取组件的实例对象,如:next( (vm)=>{} ),参数vm就是组件的实例化对象。
1、导航被触发。
2、在失活的组件里调用 beforeRouterLeave 守卫。
3、调用全局的 beforeEach 守卫。
4、在重用的组件调用 beforeRouterUpdate 守卫(2.2+)。
5、调用路由配置里面的 beforeEnter 守卫。
6、解析异步路由组件。
7、在被激活的组件里调用 beforeRouterEnter。
8、调用全局的 beforeResolve 守卫(2.5+)。
9、导航被确认。
10、调用全局的 afterEach 钩子。
11、触发 DOM 更新。
12、调用 beforeRouterEnter 守卫中传给next的回调函数,创建好的组件实例会作为回调函数的参数传入。
需要做 vuex 数据持久化,一般使用本地储存的方案来保存数据,可以自己设计存储方案,也可以使用第三方插件。
推荐使用 vuex-persist 插件,它是为 Vuex 持久化储存而生的一个插件。不需要你手动存取 storage,而是直接将状态保存至 cookie 或者 localStorage中。
模块: 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能会变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。
命名空间: 默认情况下,模块内部的 action、mutation、getter是注册在全局命名空间的 --- 这样使得多个模块能够对同一 mutation 或 action 做出响应。如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced:true 的方式使其成为带命名的模块。当模块被注册后,他所有 getter、action、及 mutation 都会自动根据模块注册的路径调整命名。
1、工厂模式 - 传入参数即可创建实例
虚拟 DOM 根据参数的不同返回基础标签的 Vnode 和组件 Vnode。
2、单例模式 - 整个程序有且仅有一个实例
vuex 和 vue-router 的插件注册方法 install 判断如果系统存在实例就直接返回掉。
3、发布-订阅模式。(vue 事件机制)
4、观察者模式。(响应式数据原理)
5、装饰器模式(@装饰器的用法)
6、策略模式,策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案 - 比如选项的合并策略。
在日常开发中,我们经常会遇到在不同组件中经常用到一些相同或者相似的代码,这些代码的功能相对独立,可以通过vue 的 mixin 功能抽离公共的业务逻辑,原理类似“对象的继承”,当组件初始化时会调用 mergeOptions 方法进行合并,采用策略模式针对不同的属性进行合并。当组件和混入对象含有相同名选项时,这些选项将以恰当的方式进行“合并”。
官方解释:Vue.extend 使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
其实就是一个子类构造器,是Vue组件的核心api。实现思路就是使用原型继承的方法返回了 vue 的子类,并且利用 mergeOptions 把传入组件的 options 就和父类的 options 进行了合并。
指令本质上是装饰器,是 vue 对 HTML 元素的扩展,给 HTML 元素添加自定义功能。vue 编译 DOM 时,会找到指令对象,执行指令的相关方法。
自定义指令有五个生命周期(也叫钩子函数),分别是 bind、inserted、update、componentUpdated、unbind
1、bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
2、inserted:被绑定元素插入父节点时调用。
3、update:被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较前后的绑定值。
4、componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
5、unbind:只调用一次,指令与元素解绑时调用。
原理:
1、在生成 ast 语法树时,遇到指令会给当前元素添加 directives 属性
2、通过 genDirectives 生成指令代码
3、在 patch 前将指令的钩子提取到 cbs 中,在 patch 过程中调用对应的钩子。
4、当执行指令对应钩子函数时,调用对应指令定义方法。
事件修饰符
.stop 阻止事件继续传播
.prevent 阻止标签默认行为
.capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理
.self 只当在 event.target 是当前元素自身时触发处理函数
.once 事件只会触发一次
.passive 告诉浏览器你不想阻止事件的默认行为
v-model 的修饰符(一般修饰符)
.lazy 通过这个修饰符,转变为在 change 事件再同步
.number 自动将用户输入值转化为数值类型
.trim 自动过滤用户输入的收尾空格
键盘事件修饰符
.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right
系统修饰符
.ctrl
.alt
.shift
.meta
鼠标按钮修饰符
.left
.right
.middle
Vue 的编译过程就是将 template 转化为 render 函数的过程,分为以下三步:
第一步是将 模板字符串转换成 element ASTs(解析器)
第二步是对 AST 进行静态节点标记,主要用来做虚拟 DOM 的渲染优化(优化器)
第三步是 使用element ASTs 生成 render 函数代码字符串(代码生成器)
Vue 的生命周期钩子核心实现是利用发布订阅模式先把用户传入的生命周期钩子订阅好(内部采用数组的方法存储)然后在创建组件实例的过程中会一次执行对应的钩子方法(发布)
函数式组件与普通组件的区别
1、函数式组件需要在声明组件时指定 functional:true
2、不需要实例化,所以没有this,this通过render函数的第二个参数context代替
3、没有生命周期钩子函数,不能使用计算属性,watch
4、不能通过$emit对外暴露事件,调用事件只能通过context.listeners.click的方式调用外部传入的事件
5、因为函数组件时没有实例化的,所以在外部通过ref去引用组件时,实际引用的是HTMLElement
6、函数式组件的props可以不用显示声明,所以没有在props里面声明的属性都会被自动隐式解析为prop,而普通的组件所有未声明的属性都解析到$attrs里面,并自动挂载到组件根元素上(可以通过inheritAttrs属性禁止)
优点:
1.由于函数组件不需要实例化,无状态,没有生命周期,所以渲染性要好于普通组件
2.函数组件结构比较简单,代码结构更清晰
使用场景:
一个简单的展示组件,作为容器组件使用 比如 router-view 就是一个函数式组件。 “高阶组件”---用于接受一个组件为参数,返回一个被包装过的组件。
diff算法采用同级比较。
1、tag 标签不一致直接新节点替换旧节点。
2、tag 标签一样。
先替换属性
对比子元素
1.新老都有子元素,采用双指针方式进行对比
sameVnode 判断tag和key完全相同为同一节点,进行节点复用
头和头相等对比
尾和尾相等对比
头和尾相等对比
sameVnode 的时候传入两个新老子节点patch(oldChild,newChild)
乱序情况 -- 上面的都不符合,先遍历旧子节点数组形成 key值映射的map对象。
然后根据新子节点数组循环 按照key值和位置关系移动以及新增节点 最后删除多余的旧子节点 如果移动旧节点同样需要patch(oldChild,newChild)
2.新的有子元素,老的没有子元素。-- 直接将子元素虚拟节点转化成真实节点插入即可。
3.新的没有子元素,老的有子元素。 -- 直接清空 innerHtml
3、无 tag 标签 -- 文本节点直接比较内容是否一致
依赖管理:方便引用第三方模块、让模块更容易复用,避免全局注入导致的冲突、避免重复加载或者加载不需要的模块。会一层一层的读取依赖的模块,添加不同的入口;同时,不会重复打包依赖的模块。
合并代码:把各个分散的模块集中打包成大文件,减少HTTP的请求链接数,配合UglifyJS(压缩代码)可以减少、优化代码的体积。
各路插件:统一处理引入的插件,babel编译ES6文件,TypeScript,eslint 可以检查编译期的错误。
总结:webpack 的作用就是处理依赖,模块化,打包压缩文件,管理插件。
一切皆为模块,由于webpack只支持js文件,所以需要用loader 转换为webpack支持的模块,其中plugin 用于扩张webpack 的功能,在webpack构建生命周期的过程中,在合适的时机做了合适的事情。
解析配置参数,合并从shell(npm install 类似的命令)和webpack.config.js文件的配置信息,输出最终的配置信息;
注册配置中的插件,让插件监听webpack构建生命周期中的事件节点,做出对应的反应;
解析配置文件中的entry入口文件,并找出每个文件依赖的文件,递归下去;
在递归每个文件的过程中,根据文件类型和配置文件中的loader找出对应的loader对文件进行转换;
递归结束后得到每个文件最终的结果,根据entry 配置生成代码chunk(打包之后的名字);
输出所有chunk 到文件系统。
vue-cli 的脚手架项目模板有browserify 和 webpack;
解析.vue文件的一个加载器。
用途:js可以写es6、style样式可以scss或less、template可以加jade等。
将公用的JS库通过script标签外部引入,减小 app.bundel 的大小,让浏览器并行下载资源文件,提高下载速度;
在配置 路由时,页面和组件使用懒加载的方式引入,进一步缩小 app.bundel 的体积,在调用某个组件时再加载对应的js文件;
加一个首屏loading图,提升用户体验;
减小入口文件积
静态资源本地缓存
UI框架按需加载
图片资源的压缩
组件重复打包
开启GZip压缩
使用SSR
轻星级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十kb ;
简单易学:国人开发,中文文档,不存在语言障碍,易于理解和学习;
组件化:保留了react的优点,实现了html的封装和重用,在构建单页面应用方面有着独特的优势;
双向数据绑定:保留了angular的特点,在数据操作方面更为简单;
视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;
虚拟DOM::dom操作是非常耗费性能的,不再使用原生的dom操作节点,极大解放dom操作,但具体操作的还是dom不过是换了另-种方式;
运行速度更快:相比较于react而言,同样是操作虚拟dom ,就性能而言,vue 存在很大的优势。
v-model 本质上不过是语法糖,v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
text 和 textarea 元素使用 value 属性和 input 事件;
checkbox 和 radio 使用 checked 属性和 change 事件;
select 字段将 value 作为 prop 并将 change 作为事件。
route:代表当前路由信息对象,可以获取到当前路由的信息参数如name ,path,params,query等
router:代表路由实例的对象,包含了路由的跳转方法,钩子函数等
v-once - 定义它的元素或组件只渲染一次,包括元素或组件的所有节点,首次渲染后,不再随数据的变化重新渲染,将被视为静态内容。
v-cloak - 这个指令保持在元素上直到关联实例结束编译 -- 解决初始化慢到页面闪动的最佳实践。
v-bind - 绑定属性,动态更新HTML元素上的属性。例如 v-bind:class。
v-on - 用于监听DOM事件。例如 v-on:click v-on:keyup
v-html - 赋值就是变量的innerHTML -- 注意防止xss攻击
v-text - 更新元素的textContent
v-model - 1、在普通标签。变成value和input的语法糖,并且会处理拼音输入法的问题。2、再组件上。也是处理value和input语法糖。
v-if / v-else / v-else-if。可以配合template使用;在render函数里面就是三元表达式。
v-show - 使用指令来实现 -- 最终会通过display来进行显示隐藏
v-for - 循环指令编译出来的结果是 -L 代表渲染列表。优先级比v-if高最好不要一起使用,尽量使用计算属性去解决。注意增加唯一key值,不要使用index作为key。
v-pre - 跳过这个元素以及子元素的编译过程,以此来加快整个项目的编译速度。
1、意外的全局变量
函数中意外的定义了全局变量,每次执行该函数都会生成该变量,且不会随着函数执行结束而释放。
2、未清除的定时器
定时器没有清除,它内部引用的变量,不会被释放。
3、脱离DOM的元素引用
一个dom容器删除之后,变量未置为null,则其内部的dom元素则不会释放。
4、持续绑定的事件
函数中addEventListener绑定事件,函数多次执行,绑定便会产生多次,产生内存泄漏。
5、绑在EventBus的事件没有解绑
6、闭包引起内存泄漏
比如事件处理回调,导致DOM对象和脚本中对象双向引用。
7、使用第三方库创建,没有调用正确的销毁函数
8、单页应用时,页面路由切换后,内存未释放
Vue在观察到数据变化时,并不是直接更新DOM,而是开启一个队列,并且缓存同一轮事件循环中的所有数据改变。在缓冲时会除去重复的操作,等到下一轮事件循环时,才开始更新。$nextTick就是用来告知DOM什么时候更新完,当DOM更新完毕后,nextTick方法里面的回调就会执行。