官方:VUE 是一个用于构建用户界面的 渐进式框架,核心是只关注视图层,采用数据驱动;
1、声明式框架
命令式:JQ 时代编写代码就是命令式的,重点是关注代码的过程(获取节点、操作节点);
声明式:关注结果,命令式代码被封装到 vuejs 当中了(只需要关注数据,节点的操作、渲染由vue框架处理);
2、MVVM模式(vue不是完全的MVVM模式,只是借鉴了这个模式思想)
MVC:MVC 即 Model-View-Controller 的简写。即模型-视图-控制器。M是数据、V是界面、C是核心负责页面的业务逻辑;单向通信:也就是 View 跟 Model,必须通过 Controller 来承上启下;
MVVM:MVVM 即 Model-View-ViewModel 的简写。即模型-视图-视图模型。M是数据、V是界面、VM是 mvvm 模式的核心,它是连接 view 和 model 的桥梁。它有两个方向:一是通过数据绑定的方式将模型(Model)转化成视图(View);二是通过DOM 事件监听方式将视图(View)转化成模型(Model)。这种模式我们称之为数据的双向绑定;
MVC和MVVM的区别:并不是VM完全取代了C,只是在MVC的基础上增加了一层VM,只不过是弱化了C的概念,ViewModel 存在目的在于抽离 Controller 中展示的业务逻辑,而不是替代 Controller,其它视图操作业务等还是应该放在Controller中实现。也就是说MVVM实现的是业务逻辑组件的重用,使开发更高效,结构更清晰,增加代码的复用性。
3、采用 vdom
1、vdom 是一个对象,用来描述真实的 dom,由于直接操作 dom 的性能低而 js 层的操作效率高;可以将 dom 操作转化为对象操作,最终通过 diff 比对差异更新 dom,减少真实 dom 操作;
2、vdom 是不依赖真实的平台环境的,所以可以实现跨平台;
vdom的生成:template 模板—>编译为 render 函数—>挂载时返回 vdom — >patch 转化为 dom;
diff 比对的新旧vdom是怎么来的?
在挂载之后会调用 render 生成一个 vdom(oldDom),在数据发生变化的时候会调用组件的 render 函数生成新的 vdom(newDom);
优点:保证性能下限、无需手动操作 DOM、跨平台
缺点:无法进行针对性的极致优化、首次渲染大量DOM时,由于多了一层虚拟 DOM 的计算,会比 innerHTML 插入慢;
4、组件化
组件化是对 ui 的封装,模块化是对业务逻辑的封装;
vue 的组件都有一个渲染函数(watcher 或者 effect),数据发生变化会调用渲染函数来重新渲染组件;如果不区分组件那么数据变化整个页面都会重新渲染;如果过分拆分组件,又会导致渲染函数过多而造成性能的浪费;所以要按照需求合理拆分组件;
总结:
1、合理的划分组件,有助于提升应用性能;
2、组件遵循高内聚、低耦合、可组合、单向数据流原则;
3、组件化能大幅度提高应用程序的开发效率、测试性、复用性;
4、降低更新范围,只重新渲染变化的组件;
spa:只有一个 HTML 页面,并且提供一个挂载点,打包后会对在页面中引入对应的资源;页面渲染全部是由js动态渲染,切换页面是通过监听路由变化来渲染对应的界面;(客户端渲染)
mpa:有多个 HTML 页面(服务端渲染好完整的HTML并返回),每个页面必须重复加载 js、css 等资源;页面跳转需要刷新整个页面;
优缺点:
spa:只有一个页面,局部刷新、页面切换速度快、用户体验好、比较容易维护;无法使用 SEO 搜索引擎优化;首屏加载慢,容易出现白屏;
mpa:多个页面,整体刷新、页面切换慢用、户体验差、不容易维护;完全支持 SEO 搜索引擎优化;
针对单页面首屏白屏问题:
1、对于静态、变化不多的网站可以使用静态页面预渲染(ssg):构建时生成完整的HTML页面,返回给浏览器;
2、采用 ssr + csr 的方式,首屏使用服务端渲染,后续使用客户端渲染(nuxtjs)
响应式数据:就是当数据发生改变时,UI页面做出响应;主要是数据劫持+观察者模式
vue2:
检测数据变化利用的是 Object.defineProperty() 方法来定义 属性的 get 和 set 方法来实现;
数组则是通过重写数组的原型(push、pop、shift、unshift、splice、sort、reserve)方法来实现;
多层对象的话则是通过递归来实现劫持;
当页面使用对应属性时,每个属性都拥有自己的 dep 属性,存放他所依赖的 watcher(依赖收集),当属性变化后会通知自己对应的 watcher 去更新(派发更新);
vue3:proxy 来代理对象;通过一个 Map 结构将属性和 effect 映射起来;
vue2的缺陷:1、递归劫持对象属性比较消耗性能;2、无法监测属性新增和删除的变化;3、无法检测到数组下标和长度的变化;
Object.defineProperty() 的问题主要有三个:递归劫持对象属性比较消耗性能;无法监测属性新增和删除的变化;无法检测到数组下标和长度的变化;
Proxy的优势如下:
针对整个对象,而不是对象的某个属性;
不需要对数组的方法进行重载;
第二个参数可以有 13 种拦截方:不限于apply、ownKeys、deleteProperty、has等;
返回的是一个新对象,我们可以只操作新的对象达到目的;
作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利
vue 设计的是每一个组件一个 watcher,而不是对每一个属性对应一个 watcher;如果一个属性对应一个 watcher 会导致大量的 watcher 产生从而出现内存的浪费,而且粒度过细也得导致更新不准确的问题(属性互相依赖,查找会很麻烦);所以采用了组件级别的 watcher 结合 diff 算法来实现更新;
vue2:
1、在 vue 实例挂载的时候会进行组件的挂载,组件挂载的时候会对每一个组件生成一个 watcher;
2、每一个 watcher 都会调用 render 函数,render 函数在执行的时候会去取页面上对应的属性(这个时候将 watcher 暴露在全局上,方便下面取用);
3、每一个属性都有自己的 dep 属性用来记录 watcher;在读取属性的时候会触发属性的 getter 方法将刚才的 watcher 记录在属性的 dep 属性中;
4、数据发生更新的时候触发 setter 方法,拿到这个属性的 dep 找到并通知刚才记录的 watcher 去重新渲染;
dep 和 watcher 是多对多的关系;
vue3:
流程跟 vue2 差不多,区别是 vue3 是通过 Map 结构将属性和 effect 映射起来;
1、初始化 new Vue()
创建 Vue 实例,它内部执行了根实例的初始化过程;
2、建立更新机制
template 的作用是模板占位符,可帮助我们包裹元素,但在循环过程当中,template不会被渲染到页面上;
在 vue 实例初始化 $mount 的时候,先调用 render 函数如果 render 函数不存在,则调用 template 进行编译得到 render 函数。如果没有 template 则会调用 el 来获取 template。
1、第一步:解析器:获取模板,并将模板通过 parser 编译器编译成 AST 语法树;
2、第二步:优化器:是对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化;
3、第三步:代码生成器:把得到的 AST 通过 generate 转化成 render 渲染函数,render 函数的返回值是 vdom;
解析器(parser):模板字符串会扔到 while 中去循环,然后 一段一段 的截取,把截取到的 每一小段字符串 进行解析,当所有字符串都截取完之后也就解析出了一个完整的 AST 语法树;
优化器(optimizer):目标是找出那些静态节点并打上标记,而静态节点指的是DOM不需要发生变化的节点;好处是:每次重新渲染的时候不需要为静态节点创建新节点;在 Virtual DOM 中 patching 的过程可以被跳过;
代码生成器(generator):用 with 包裹,通过递归去拼一个函数执行代码的字符串,递归的过程根据不同的节点类型调用不同的生成方法, 最后拼出一个完整的 render 函数代码。
参考:https://zhuanlan.zhihu.com/p/362128744
vue2
beforeCreate:实例初始化之后;数据观测和事件配置之前调用,组件的选项对象还没有创建,el挂载和data都没有初始化,无法访问方法和数据。
created:实例创建完成之后调用;已经完成了数据观测,属性方法的运算,watch/event 事件回调,data数据初始化已经完成,el挂载还没有开始。(服务端渲染可以在这个钩子里面获取数据)
beforeMount:挂载之前调用;el初始化已经完成,vdom已经完成data和模板的结合生成html,但是还没有挂载到html页面里。
mounted:挂载完成之后调用;模板中的html渲染到html页面里。
beforeUpdate:数据更新之前调用;发生在vdom重新渲染和打补丁之前,可以进一步更改状态,不会触发重新渲染。
updated:数据更新,dom渲染之后调用;要避免在这里更改状态,防止更新导致的无线循环。
beforeDestory:实例销毁之前调用;还可以获取到this,一般用于清除定时器和监听事件等。
destoryed:实例销毁之后调用;所有监听事件被清除,子实例被销毁。
activated:(keep-alive的生命周期)页面第一次进入的时候,钩子触发的顺序是created->mounted->activated
deactivated:(keep-alive的生命周期)页面退出的时候会触发deactivated,当再次前进或者后退的时候只触发activated
errorCaptured:捕获一个来自子孙组件的错误时被调用
vue3
与 vue2 基本相同,只不过在 beforeCreate 之前调用了 setup ;
销毁前后的钩子名称变了:beforeUnmount、unmount;
新增了三个调试钩子:renderTracked Dev(依赖收集时调用)、renderTriggered Dev(依赖触发时调用)、serverPreFetch(组件在服务端渲染时调用)
vue3 新增组合 api 使用 on 开头在 setup 中调用其他生命周期钩子(不包括 onBeforeCreate、onCreated);
父组件监听子组件生命周期:通过在对应生命周期通过 $emit 方法调用父组件自定义事件来监听;
Vue的生命周期钩子就是回调函数,内部主要是使用callHook方法来调用对应的方法。核心是一个发布订阅模式,将钩子函数订阅好(内部采用数组的方式存储),在创建组件实例的过程中会调用对应的钩子方法,并提供给开发者使用。
1)、加载过程:父beforeCreate->父cerated->父beforeMount->子beforeCreate->子cerated->子beforeMount>子mounted->父mounted
2)、子组件更新:父beforeUpdate->子beforeUpdate->子updated->父updated
3)、父组件更新:父beforeUpdate->父updated
4)、销毁过程:父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
Vue.mixin的作用扩展组件,将公共的业务逻辑抽离,原理类似“对象的继承”;在使用 mixin 的组件中引入后,mixin 中的方法和属性也就并入到该组件中,可以直接使用;当组件初始化时会调用 mergeOptions 方法进行合并,采用策略模式针对不同的属性进行合并,如果混入的数据和本身组件中的数据冲突,会采用“就近原则”以组件的数据为准。
一般全局混入是用来编写插件,局部复用的话是用来抽离公共逻辑;
//全局引用 main.js
import mixin from './mixin'
Vue.mixin(mixin)
//在vue文件中引用
import '../mixin'; // 引入mixin文件
export default {
mixins: [mixin]
}
同名选项合并:
缺点:命名冲突、数据来源不清晰;
vue3 采用了 composition API 来提取公共逻辑;
1、vue 是一个单例模式,不会有任何的合并操作,所以根实例不会校验data一定是一个函数;
2、组件的data必须是一个函数,因为组件是用来复用的,且 JS 里对象是引用关系,如果组件中 data 是一个对象,那么这样作用域没有隔离,组件之间的 data 属性值会相互污染;
3、如果组件中 data 选项是一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间,组件实例之间的 data 属性值不会互相影响;
作用:vue 更新 dom 是异步更新的,数据变化,dom 的更新不会马上完成;nextTick 的回调是在下次 DOM 更新循环结束之后执行的延迟回调。多次调用 nextTick 会被合并;
实现:(源码:src/core/util/next-tick.js)
原理是使用异步方法处理任务,vue 会根据当前环境优先使用 promise.then、MutationObserver 、setImmediate,如果都不支持就使用 setTimeout 把函数延迟到 DOM 更新之后再使用。(原因是宏任务消耗大于微任务,优先使用微任务,最后使用消耗最大的宏任务)
区别:
1、都是观察数据变化的(相同);
2、计算属性将会混入到 vue 的实例中,所以需要监听自定义变量;watch 监听 data 、props 里面数据的变化;
3、computed 有缓存,它依赖的值变了才会重新计算,watch 没有;
4、watch 支持异步,computed 不支持;
5、watch 是监听的属性发生变化触发;computed 是监听属性依赖的属性发生变化触发重新计算;
watch的原理:
watch 本质上是为每个监听属性 setter 创建了一个 watcher,当被监听的属性更新时,调用传入的回调函数。常见的配置选项有 deep 和 immediate,对应原理如下:
deep:深度监听
immediate :组件加载立即触发回调函数执行
onClearup:清空依赖,方便用户使用,解决了清理问题(vue3)
watch 的 deep实现
当用户指定了 watch 中的deep属性为 true 时,为对象的每一个属性创建一个 watcher,从而确保对象的每一个属性更新时都会触发传入的回调函数;同时也会引入判断机制,确保在多个属性更新时回调函数仅触发一次,避免性能浪费;
computed缓存原理
vue2:
1、初始化的时候会对每一个计算属性用计算属性 watcher 包装起来 ,这里面会维护一个 dirty 属性,默认为true;
2、挂载的时候会调用 computed 属性触发计算属性的 getter 进行求值,求值会调用依赖属性触发依赖属性的 getter ,这个时候依赖属性就会把计算属性的 watcher 收集起来;
3、通过 Object.definedproperty 将计算属性定义到实例上,所以计算属性不能直接监听 data、prop 里面的属性;
4、当用户取值的时候会触发 getter 方法, 拿到计算属性对应的 watcher ,看 dirty 是否为 true,为 true 就会重新计算,计算之后会将 dirty 值变为 false,并将值缓存起来(this.value);
5、如果计算属性依赖是属性在模板里面(页面上),则会让依赖的属性收集最外层的渲染 watcher,依赖的属性值变了就可以触发计算属性更新 dirty,同时触发界面更新;
vue3:
跟 vue2 差不多,区别在于计算属性自己来收集最外层的渲染 effect;
computed依赖收集过程
也是通过 watcher 来进行依赖收集的;在读取计算属性的时候会触发计算属性的 getter 去读取所依赖的
watch 和 watchEffect 函数都是监听器,在写法和用法上有一定区别,是同一功能的两种不同形态,底层都是一样的;
区别:
watch
需要指定依赖数据,依赖数据更新时执行回调函数;
具有一定的惰性lazy 第一次页面展示的时候不会执行,只有数据变化的时候才会执行(设置immediate: true时可以变为非惰性,页面首次加载就会执行);
监视ref定义的响应式数据时可以获取到原值;
既要指明监视的属性,也要指明监视的回调;
watchEffect
watchEffect 自动收集依赖数据,依赖数据更新时重新执行自身;
立即执行,没有惰性,页面的首次加载就会执行;
无法获取到原值,只能得到变化后的值;
不用指明监视哪个属性,监视的回调中用到哪个属性就监视哪个属性;
参考:https://blog.csdn.net/weixin_52148548/article/details/125073998
起因:因为 ES5 的限制,vue 无法检测到对象属性的添加和删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的;
用法:Vue.set(target, key, value)
向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性。(对象不可以是 Vue 实例)
执行流程:
1、判断传入的 target 如果是undefined、null 、原始类型或者是只读属性,则直接抛出错误;
2、target 如果是个数组,并且更新的是索引的话,那么就取当前数组长度与 key 这两者的最大值作为数组的新长度,然后使用数组的 splice 方法将传入的索引 key 对应的 val 值添加进数组,进行重写数组(所以数组的更新直接可以使用splice来操作);
3、如果修改的是 vue 根实例上的数据则会抛错;
4、如果传入的 target 不是数组,那就当作是对象来处理,不是响应式数据则直接赋值然后返回;是响应式数据则将属性定义成响应式的 defineReactive(ob.value, key, val);
5、使用 ob.dep.notify() 触发更新视图;
Vue.set( ) 是将 set 函数绑定在 Vue 构造函数上,this.$set() 是将 set 函数绑定在 Vue原型上;
Vue 是基于 vdom 进行更新的,diff 的核心是比较两个vdom的差异;Vue 的 diff 采用平级比较,不考虑跨级的情况,内部采用深度递归+双指针的方式进行比较;
流程:
1、先比较两个节点是否是相同节点(key tag ),不相同则销毁老节点,新增新节点;
2、相同节点比较属性,并复用老节点;
3、比较子节点:老的没子,新的有子则新增子节点;老的有子,新的没子则删除子节点;子节点都是文本则直接更新文本节点;子节点都是列表则 updateChildren;
4、优化比较:头头、尾尾、头尾、尾头;
5、对比查找进行复用(以新节点为准,按下标顺序到老节点中查找对应元素,找到了就将老节点对应节点移动到指定下标位置,原来位置置空;如果找不到则在对应下标位置创建新节点;最后老节点中多余的节点删除掉);
vue3 在步骤 4、5 通过最长递增子序列来实现 diff 算法;
1、头和头,尾和尾比较,如果旧数据比较完毕,新数据多余的说明是新增的;如果新数据比较完毕,旧数据多余的说明是删除的;
2、如果都没有比较完毕,根据新vnode未比对节点的key和index的映射创建一个Map结构 keyToNewIndexMap;
3、根据未比较完的新数据长度,建一个填充 0 的数组 [0,0,0,0,0],用来做新旧节点的对应关系的;
4、遍历老vdom中剩余数据,到 keyToNewIndexMap查找,找不到则直接删除,找到了就根据keyToNewIndexMap对应index将老节点放到数组对应下标位置;
5、从后往前遍历数组,0则表示要新增元素,其他的节点使用最长递增子序列算法进行位移和复用;
使用最长递增子序列可以最大程度的减少 DOM 的移动,达到最少的 DOM 操作
作用:key 是 Vue中的 VNode 标记的唯一id;key 的主要功能是提高 vdom 的更新速度;因为 vue 在 patch (补丁)过程中的 diff 算法对新旧节点比对时是可以通过 key 精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使 patch 过程更高效;(如果不设置key,它的值就是undefined,则可能永远认为这是两个相同节点)
在子节点数组操作的时候,不管怎么操作 index 都是 0, 1, 2…这样排列,导致 Vue 会复用错误的旧子节点,做很多额外的工作;
1、vue2中组件确实只能有一个根,但vue3中组件已经可以多根节点了。
2、之所以需要这样是因为vdom是一颗单根树形结构,patch方法在遍历的时候从根节点开始遍历,它要求只有一个根节点。组件也会转换为一个vdom;
3、vue3中之所以可以写多个根节点,是因为引入了Fragment的概念,这是一个抽象的节点,如果发现组件是多根的,就创建一个Fragment节点,把多个根节点作为它的children。将来patch的时候,如果发现是一个Fragment节点,则直接遍历children创建或更新
渲染:
把模板解析成 render 函数 —> 组件 watcher 执行 render —> render 函数执行读取属性触发依赖收集 —> render 执行完成生成 vdom —> vdom 通过 pacth 补丁生成真是 dom;
更新:
修改data,触发setter 并获取到依赖 watcher 重新执行render函数,生成newVnode —> patch(vnode,newVnode);
如果一个组件比较大,影响了页面的渲染,这个时候可以把组件声明成异步组件;通常使用在大组件的拆分、路由;
异步组件的写法:
1、回调写法(不推荐)
components: {
'async-component':(resolve,reject)=>{
setTimeout(function(){
resolve({
render(h){
return h('div','hello')
}
})
},2000)
}
}
2、Promise 写法(推荐)
components: {
'async-component': ()=>import('./my-async-component'),
}
3、对象写法
const async-component = {
component:import('./my-async-component'),
// loading状态中显示的组件
loading: LodingComponent,
// 加载失败显示的组件
error: ErrorComponent,
// 加载组件时的延迟时间,默认值为 200ms
delay: 200,
// 加载组件超时时间,超时后默认使用 error 组件
timeout: 3000
}
原理:
1、组件加载前渲染异步组件占位符节点;
2、异步组件加载成功之后调用 $forceUpdate 强制更新,渲染加载完毕后的组件;
函数式组件的特性:无状态、无生命周期、无this。因此性能会高一点;在 vue3 中组件都不用在 new 了,所以函数式组件在性能上也就没有了优势;(使用场景:router-view)
函数式组件与普通组件的区别:
1、普通组件都是通过 Vue.extend 构造出来的,组件挂载的时候回去 new 这个构造函数;
2、函数式组件就是一个普通函数,没有 new 的过程,将返回的 vdom 转化为真是 dom 替换对应的组件;
3、函数式组件需要在声明组件时指定 functional,函数式组件不会记录在组件的父子关系中;
4、不需要实例化,所以没有this,this通过render函数的第二个参数来代替;
5、没有生命周期钩子函数,不能使用计算属性和watch;
6、不能通过$emit 对外暴露事件,调用事件只能通过context.listeners.click的方式调用外部传入的事件;
7、因为函数式组件是没有实例化的,所以在外部通过ref去引用组件时,实际引用的是HTMLElement;
1、props / $emit
适用 父子组件通信
2、ref 与 $parent / $children
(vue3废弃) 适用 父子组件通信
3、EventBus ($emit / $on
) 适用于 父子、隔代、兄弟组件通信
4、$attrs / $listeners
(vue3废弃) 适用于 隔代组件通信
5、provide / inject 适用于 隔代组件通信
6、Vuex 适用于 父子、隔代、兄弟组件通信
1、可以通过名字找到对应的组件( 递归组件:组件自身调用自身 )
2、可以通过 name 属性实现缓存功能 (keep-alive)
3、可以通过 name 来识别组件(跨级组件通信时非常重要)
4、使用 vue-devtools 调试工具里显示的组见名称是由 vue 中组件 name 决定的
1、它是 vue 的一个构造器;根据用户传入的一些选项,创建出一个子类;
2、子类继承了 vue 的构造函数,在 new 的时候可以进行初始化和挂载,这样就可以将这个子类挂载到指定的元素上;
3、vue 的所有组件默认都是通过 Vue.extend 来生成的;
4、可以通过 Vue.extend 来解析字符串模板(需要使用编译时,性能差)
// 创建构造器
var Profile = Vue.extend({
template: '{{firstName}} {{lastName}} aka {{alias}}
',
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount('#mount-point')
这样div里面就有了下面内容
Walter White aka Heisenberg
常见的组件扩展方法有:mixins,slots,extends;vue3 提供了 composition api
1、mixins是分发 Vue 组件中可复用功能的非常灵活的方式。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项;
2、插槽主要用于vue组件中的内容分发,也可以用于组件扩展;如果要精确分发到不同位置可以使用具名插槽,如果要使用子组件中的数据可以使用作用域插槽;
3、不太常用的选项extends,也可以起到扩展组件的目的;
4、composition api,可以很好解决混入引起的命名冲突和数据来源不清晰的问题,利用独立出来的响应式模块可以很方便的编写独立逻辑并提供响应式的数据,然后在setup选项中组合使用,增强代码的可读性和维护性;
组件之间是单向数据流:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态引起其他复用子组件的变化;
虽然我们不能直接修改一个传入的对象或者数组类型的prop,但是我们还是能够直接改内嵌的对象或属性;
递归所指的是程序自己调用自身,而vue中的递归组件就是组件自身调用自身。(Tree、Menu)
实现方法如下:准备一个父组件存放递归数据,再创建一个子组件作为递归调用的组件,从而实现递归。
1、组件优势:
降低整个系统的耦合度,在保持接口不变的情况下,我们可以替换不同的组件快速完成需求
调试方便,由于整个系统是通过组件组合起来的,在出现问题的时候,可以用排除法直接移除组件
提高可维护性,由于每个组件的职责单一,并且组件在系统中是被复用的,所以对代码进行优化可获得系统的整体升级
2、插件优势:插件通常用来为 Vue 添加全局功能
区别:
1、编写:
vue中每一个单文件都可以看成是一个组件,也可以使用 Vue.component 引入 template 来定义;
插件的实现应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象
2、注册:
ue组件注册主要分为全局注册与局部注册:全局注册通过Vue.component方法;局部注册只需在用到的地方通过components属性注册一个组件;
插件的注册通过Vue.use()的方式进行注册
3、使用:
组件 (Component) 是用来构成你的 App 的业务模块
插件就是指对Vue的功能的增强或补充
Vue 是组件级更新,如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染,所以为了性能, Vue 会在本轮数据更新后,在异步更新视图;
v-if:根据条件渲染指定的 dom 元素;可以阻断内部代码执行;切换时组件会触发渲染(true)和销毁操作(false)
v-show:切换当前 dom 的显示和隐藏;主要是控制样式的显示隐藏;适合频繁切换操作;切换不会触发生命周期;
vue2:先解析 v-for 再去解析 v-if,需要尽量把 v-if 放在外层,防止循环后的无用渲染;
vue3:v-if 的优先级比 v-for 更高一些,一起使用的话,vue3 会默认把 v-if 放到外层;
不管是在 vue2还是在 vue3 中,尽量不要把这两个指令放在同一层使用;
原生事件绑定是通过 addEventListener 绑定给真实元素的;组件事件绑定是通过 Vue 自定义的$on 实现的。如果要在组件上使用原生事件,需要加.native 修饰符,这样就相当于在父组件中把子组件当做普通 html 标签,然后加上原生事件;
$on、$emit
是基于发布订阅模式的,维护一个事件中心,on 的时候将事件按名称存在事件中心里,称之为订阅者,然后 emit 将对应的事件进行发布,去执行事件中心里的对应的监听器;
Vue 在创建真是 dom 时会调用 createElm ,默认会调用 invokeCreateHooks 会遍历当前平台下相对的属性处理代码,其中就有 updateDOMListeners 方法,内部会传入 add 方法;
概念:Vue 双向绑定是通过 v-model 指令,绑定一个动态值到视图上,同时修改视图能改变对应的数据(能修改视图的只有表单组件); v-model 可以理解是 value + input 的语法糖;
原理:
1、表单元素中的 v-model:内部根据不同的标签解析成不同的语法;
1)text 和 textarea 元素解析成 value 属性和 input 事件;
2)checkbox 和 radio解析成 checked 属性和 change 事件;
3)select 字段将 value 作为 prop 并将 change 作为事件。
2、组件上的 v-model:
组件上的 v-model 会用名为 value 的 prop 和 input 事件,对于组件来说 v-model 就是 input + value 的语法糖;可用于组件中数据的双向绑定;
// 父组件
// 子组件
想绑定多个属性的话:
vue2 使用 .sync 修饰符,vue3 里面支持下面方法进行绑定:
可以对 prop 进行双向绑定,v-model 默认只能双向绑定一个属性,.sync 修饰符可以绑定多个属性;
// 父组件
// 子组件
该修饰符在 vue3 中被废除;
slot 是一个占位符,在使用组件时,组件内部的内容会被分发到对应的 slot 中;
种类:
1、匿名插槽:只能有一个
2、实名插槽:可以有多个,在使用时必须使用name属性来标识
原理:父组件渲染完成之后直接替换掉子组件对应插槽的内容;
1、父组件先解析,把插槽当作子组件的子元素处理;
2、子组件解析,solt 作为一个占位符,会被解析成一个函数;
3、函数传入参数执行,拿到第一步解析得到的插槽节点,并返回;
3、作用域插槽:父组件获取子组 件solt 中携带的数据
为了让 user 在父级的插槽内容中可用,我们可以将 user 作为 元素的一个 attribute 绑定上去:
//子组件
绑定在元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:
//父组件
{{ slotProps.user.firstName }}
我们选择将包含所有插槽 prop 的对象命名为 slotProps,但你也可以使用任意你喜欢的名字。
原理:父组件渲染的内容编译成函数,子组件来调用函数并且传递数据,返回值替换占位符;
1、父组件先解析,把插槽包装成一个函数保存给节点;
2、子组件解析,solt作为一个占位符,会被解析成一个函数;
3、函数传入父组件解析的函数节点作为参数执行,跟普通插槽一样。但是会多出来插槽传递出来的数据。
本题参考:https://segmentfault.com/a/1190000019652989
keep-alive 是 vue 的内置组件,能在组件切换过程中缓存组件的实例;组件再次被激活的时候会从缓存中拿到之前的DOM 进行渲染,无需重新生成节点;一般用在动态组件、router-view组件上。
1、常用属性: include(要缓存的)/ exclude(不要缓存的)/ max(最大缓存数量),允许组件有条件的进行缓存;都可以用逗号分隔字符串、正则表达式或一个数组来表示;
2、生命周期:它自己有两个生命周期activated / deactivated,用来得知当前组件是否处于活跃状态;
3、缓存实现过程:
1、在 vue 实例创建的时候,created 钩子会创建一个 cache 对象,用来作为缓存容器,保存 vdom 节点。创建 keys 数组用来记录用户缓存过那些组件;
2、在mounted钩子的 render 函数中会先获取 keep-alive 里面组件的名称,然后根据 include 和 exclude 来匹配看看是否要缓存,如果没有匹配到则不需要缓存,直接返回 新的vdom ;否则会根据 key 在 this.cache 缓存器里面查找,如果存在则说明之前已经缓存了,直接返回缓存的 vdom 覆盖当前的 vdom ;否则就会将当前的 vdom 以键值对的方式(key:vdom)存储在 cache 中;
3、key 值一般是从虚拟节点上拿,如何虚拟节点上没有则会自动生成一个 key;
4、最后将该组件实例的 keepAlive 设置为 true;
5、destroyed钩子则在组件被销毁的时候清除cache缓存中的所有组件实例;
4、补充:
1、keep-alive 组件本身没有什么具体的含义,不会记录到组件的父子关系中(abstract:true);
2、命中缓存时,keep-alive 会将命中的组件从缓存中删除,然后再将这个组件放在缓存数组的最后面;
3、缓存的时候会根据最大缓存数 max 来判断当前缓存个数,如果超过了则会将 chahe 中下标为 0 的移除;
4、keep-alive 会监控 include、exclude 的变化,根据这两个属性的变化来更新缓存;
5、数据更新问题
beforeRouterEnter:在 view-router 项目中每次进入路由时执行获取数据的方法;
actived:在缓存组件被激活时,会触发这个钩子,在钩子函数中执行获取数据的方法;
1、跳转方式:
编程式(js跳转)this.$router.push(‘/’)
声明式(标签跳转)
2、动态路由
很多时候,我们需要将给定匹配模式的路由映射到同一个组件,这种情况就需要定义动态路由
routes: [
// 动态路径参数 以冒号开头
{ path: "/user/:id", component: User },
],
组件复用导致路由参数失效怎么办?
1、通过 watch 监听路由参数再发请求
2、用 :key 来阻止“复用”
3、router-link和router-view是如何起作用的
vue-router中两个重要组件router-link和router-view,分别起到导航作用和内容渲染作用;
router-link默认生成一个a标签,设置to属性定义跳转path;内部根据custom属性判断如何渲染最终生成节点,用户点击之后实际调用内部提供导航方法navigate,去修改响应式的路由变量,然后重新去routes匹配出数组结果;
router-view是要显示组件的占位组件,可以嵌套,对应路由配置的嵌套关系,配合name可以显示具名组件,起到更强的布局作用;
4、history 的方法
新增的 history.pushState() 和 history.repalceState() 这两个API可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录;
window.history.pushState
window.history.replaceState(state,title,url)
window.history.back() :后退
window.history.forward():前进
window.history.go(1) :前进或者后退几步
5、路由守卫(路由保护的方式)
全局守卫:每次导航时都会触发
beforeEach(to,from,next):路由跳转前触发;
beforeResolve(to,from,next):导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后调用;
afterEach(to,from):路由跳转完成后触发;
单个路由守卫:在路由上配置
beforeEnter(to, from, next):进入路由时触发;
组件路由守卫:组件内部定义
beforeRouterUpdate(to, from, next):当前路由改变,但是该组件被复用时调用;
beforeRouterEnter(to, from, next):在导航确认前被调用,不能访问 this;
beforeRouterLeave(to, from, next):导航离开该组件的对应路由时调用;
执行流程:
1、导航被触发
2、在失活的组件里调用 beforeRouteLeave 守卫
3、调用全局的 beforeEach 守卫
4、在重用的组件里调用 beforeRouteUpdate 守卫
5、在路由配置里调用 beforeEnter
6、解析异步路由组件
7、在被激活的组件里调用 beforeRouteEnter 守卫
8、调用全局的 beforeResolve 守卫
9、导航被确认
10、调用全局的 afterEach 守卫
11、触发 DOM 更新
12、调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入
5、路由懒加载
当打包应用时,JavaScript 包会变得非常大,影响页面加载;利用路由懒加载我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样会更加高效,是一种优化手段;
const UserDetails = () => import('./views/UserDetails')
hash、history、abstract
区别:
1、 url 展示上,hash 有“#” 不美观,history 没有;
2、hash 可以支持低版本浏览器和 IE8 ,history 只兼容到 IE10;服务端无法获取 hash,不利于 SEO 优化;
3、history 模式需要后端配合将所有访问都指向 index.html,否则用户刷新页面,会导致404错误
4、abstract 不支持浏览器 API 环境使用,通常在 node 中;
原理:
hash:通过 onpopstate 或者 onhashchange 事件监听 hash 变化,然后根据 hash 的变化更新页面部分内容(hash变化不会触发浏览器请求,但是会触发 onhashchange 事件)。
history:主要是 H5 新增的两个 API(pushState、replaceState);他们可以改变url,但是不会发送请求,这样就可以通过 onpopstate 监听 url 变化来实现页面部分内容更新。
补充:pushState、replaceState 这两个方法应用于浏览器的历史记录栈,在当前已有的back、forward、go的基础上,他们提供了对当前浏览器进行修改的功能,只是当它们被修改时,虽然浏览器的URL发生
vuex 是 vue 应用程序的 状态管理工具;核心是解决数据共享;由 state 、action、mutation、getter、module (复杂应用中)这几个模块组成;
state 的修改只能通过:mutation 来 commit 修改;action 来 dispatch 修改对应的 mutation 然后由 mutation 修改 state;
1、缺点:
模块和状态命名冲突;
数据不够扁平化,调用时书写很长;
更改状态时 action、mutation选取,统一考虑通常会搭配 action 来修改,有些操作是无意义的;
模块必须添加 nameSpace;
对 TS 支持不友好;
页面刷新数据会丢失;(解决:缓存本地、获取数据时判断数据是否存在,不存在就重新请求)
2、原理:
vuex3:核心是通过 new Vue() 创建了一个 vue 的实例,进行数据共享;
vuex4:核心是通过 reactive()创建一个响应式对象进行数据共享;
3、mutation和action区别
action可以处理异步逻辑,只能通过mutation来修改state;
action可以多次commit操作,也可以调用action;
严格模式下载非mutation中修改state,会抛出异常;
dispatch 时action会被包装成一个promise,mutation则没有;
4、vuex的action为什么可以处理异步
每个mutation执行完毕之后,可以得到对应的状态,使用devtools可以跟踪状态的变化;如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难;
异步操作通过 Action 来提交 mutation实现,这样使得我们可以方便地跟踪每一个状态的变化;
5、如何监听 vuex 里面数据的变化
vuex 里面的数据是响应式的,可以使用 watch 监听,可以使用 vuex 提供的订阅的API:store.subscribe();
6、为什么要模块,命名空间
当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块;
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名;
pinia就是Vuex的升级版;vue2 vue3中都可以使用;
pinia中只有state、getter、action,抛弃了Vuex中的Mutation;
pinia中action支持同步和异步,Vuex不支持
良好的Typescript支持,毕竟我们Vue3都推荐使用TS来编写,这个时候使用pinia就非常合适了
pinia中每个store都是独立的,互相不影响,无需再创建各个模块嵌套
体积非常小,只有1KB左右。
pinia支持插件来扩展自身功能
支持服务端渲染
SSR也就是服务端渲染,也就是将 Vue 在客户端把标签渲染成 HTML 的工作放在服务端完成,然后再把 html 直接返回给客户端;
优点:更好的 SEO、并且首屏加载速度更快
缺点:开发条件会受到限制,服务器端渲染只支持 beforeCreate 和 created 两个钩子,当我们需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于 Node.js 的运行环境。服务器会有更大的负载需求;
原理:
app.js 作为客户端与服务端的公用入口,导出 Vue 根实例,供客户端 entry 与服务端 entry 使用。客户端 entry 主要作用挂载到 DOM 上,服务端 entry 除了创建和返回实例,还进行路由匹配与数据预获取。
webpack 为客服端打包一个 Client Bundle ,为服务端打包一个 Server Bundle 。
服务器接收请求时,会根据 url,加载相应组件,获取和解析异步数据,创建一个读取 Server Bundle 的 BundleRenderer,然后生成 html 发送给客户端。
客户端混合,客户端收到从服务端传来的 DOM 与自己的生成的 DOM 进行对比,把不相同的 DOM 激活,使其可以能够响应后续变化,这个过程称为客户端激活 。为确保混合成功,客户端与服务器端需要共享同一套数据。在服务端,可以在渲染之前获取数据,填充到 stroe 里,这样,在客户端挂载到 DOM 之前,可以直接从 store里取数据。首屏的动态数据通过 window.__INITIAL_STATE__发送到客户端
1、表单修饰符:lazy、trim、number
2、事件修饰符:stop(阻止冒泡)、prevent(阻止默认行为)、self(当前元素自身触发,相当于阻止事件冒泡)、once(触发一次)、capture(使用捕获模式)、passive(不阻止默认行为)、native(绑定原生事件)
3、鼠标按键修饰符:left、right、middle
4、键值修饰符:onkeyup,onkeydown,以及对 keyCode 处理(enter、tab、delete、space、esc、up、down、left、right)
5、.sync 修饰符
vue 除了内置指令之外,还允许用户自定义指令来进行扩展,指令的目的在于将操作 dom 的逻辑进行复用;
1、指令的生命周期
bind:只调用一次,指令第一次绑定到元素上调用;执行一些初始化操作;
inserted:绑定指令的节点插入到页面中(仅保证父节点存在,但不一定已被插入文档中);
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前;指令的值可能发生了改变,也可能没有;
componentUpdate:指令所在的节点及其子节点的Vnode 全部更新后调用;
unbind:只调用一次,指令和元素解绑时调用;
2、常见场景
添加水印、按钮权限控制、图片懒加载、拖拽指令、复制粘贴
3、原理
指令本质上是装饰器,是 vue对 HTML 元素的扩展,给 HTML 元素增加自定义功能;vue 编译 DOM 时,会找到指令对象,执行指令的相关方法。
在生成 ast 语法树时,遇到指令会给当前元素添加 directives 属性
通过 genDirectives 生成指令代码
在 patch 前将指令的钩子提取到 cbs 中,在 patch 过程中调用对应的钩子函数,调用对应指令定义的方法
1、数据绑定发生变化:vue2 是 ES5 的 Object.definePropert(),处理对象的属性;vue3 是 ES6 的 proxy 代理整个对象;
2、vue3 支持碎片:模板中可以有多个根节点(template不需要在用一个div包裹);
3、组合式 api:Vue2使用选项类型API(Options API)对比Vue3合成型API(Composition API);vue2 是以属性的方式来分组,vue3以函数的方式来分割,有利于 tree-shaking 优化,减少代码体积;
4、vue3 注重模块上的拆分,vue2 无法单独使用部分模块,需要引入完整的vue实例;vue3 模块直接耦合度低,模块可以单独使用;
5、vue3 使用 ts 重新,对 ts 更加友好;
6、生命周期:vue3 新增了3个测试用的生命周期钩子,以 setup 方法作为入口替代了create两个钩子,可以使用 on 的形式调用生命周期,销毁钩子名称换了;
7、v-model 改写,可以在组件中绑定多个属性;
8、v-if、v-for优先级区别:vue2 for优先级高,vue3 if优先级高;
9、diff 算法重写:在递归比对的时候采用了 最长递增子序列算法;
10、vue3 对模板编译优化,采用动态节点标记的方式;
11、新增很多新特性:v-memo;
12、废弃了一些 api:过滤器、.sync;
1、Performance:重写vdom以及模板编译;性能比vue2.x块1.2~2倍;
2、Tree shaking support:核心API都支持tree-shaking,支持按需编译,体积更小;
3、Composition API:使用纯函数分割复用代码;组合API,类似React Hooks;
4、Custom Renderer API:定义了虚拟DOM的渲染规则,暴露了自定义渲染API;
5、新增组件:Fragment(多个根节点),Teleport(提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案),Suspense(让你的组件在渲染之前进行“等待”,并在等待时显示 fallback 的内容)
6、Better TypeScript support:采用TypeScript重写,更好的支持TS
参考:https://blog.csdn.net/weixin_36774307/article/details/127603127
1、编译阶段
diff 算法优化:vue2 是全量对比,vue3新增了静态标记(PatchFlag),只对比带有 flag 的动态节点节点;
静态提升:vue3里面静态节点会被复用,将静态节点提升到父级作用域缓存起来,多个相邻的会被合并,拿空间换时间;
事件监听缓存:同一个函数直接缓存起来进行复用;
SSR优化:静态节点直接输出,绕过了vdom;动态节点,还是需要动态渲染;
2、源码体积变小
相比vue2,vue3整体体积变小了,除了移出一些不常用的API,再重要的是Tree shanking
vue3根据不同的情况,引入不同的API,不会全部引用
3、响应式系统
vue3采用proxy重写了响应式系统,因为proxy可以对整个对象进行监听,所以不需要深度遍历
1、使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改,功能不能独立出来,如果功能特别多就显得很乱;compositionAPI是把一个功能的所有状态、方法、都封装到一个函数里面,方便统一管理;
2、vue2中所有的属性都是通过 this 访问的,存在 this 指向问题;compositionAPI 都是一个个函数,不存在 this 问题;
3、vue2 中很多方法或者属性没有被使用,打包时任然会被打包起来,而且所有的全局 API 都是公开的;compositionAPI 是以导入的形式来使用,有利于 tree-shaking 优化;
4、compositionAPI 抽取公共逻辑更方便,解决 mixin 里面数据来源不明、命名冲突问题;
5、简单的组件任然可以采用 OptionsAPI,对于复杂的业务逻辑使用 compositionAPI 编写;
这两个是 vue3 里面数据响应式的重要概念;
reactive:用于处理对象类型的数据响应式,采用的是 new Proxy() 代理;假如用一个新对象替换了原来的旧对象,那么原来的旧对象会失去响应性;
ref:用于处理基础类型数据响应式,采用的是 Object.definedProperty() 来实现的;也可以定义对象,内部会自动通过reactive转为代理对象;
可能会导致 xss 攻击
delete只是被删除的元素变成了 empty/undefined 其他的元素的键值还是不变。
Vue.delete直接删除了数组 改变了数组的键值。
var a=[1,2,3,4]
var b=[1,2,3,4]
delete a[0]
console.log(a) //[empty,2,3,4]
this.$delete(b,0)
console.log(b) //[2,3,4]
v-once 是 vue 内置的指令,只渲染元素和组件一次,在重新渲染时标记 v-once 的元素、组件和所有子节点都会被视为静态内容而被跳过;可以用于优化更新性能;
{{value}}
本质上是一个 chche 缓存,缓存当前标记的元素的 vdom;
缺点:一旦标记上之后就会走缓存,如何标记的元素上有动态数据,数据变化依然会拿缓存中的数据;
vue3 新增了一个指令 v-memo ,可以通过依赖列表的方式进行渲染;
{{value}}
类似计算属性,依赖的 value1、value2 发生变化了就重新渲染 p 元素;
作用:当 style 标签里面有 scoped 属性时,它的 css 只作用于当前组件的元素,实现私有化,不会污染全局样式;
原理:style 标签中添加 scoped 属性后,vue 就会为当前组件中的 DOM 元素添加一个唯一的自定义属性,从而达到样式私有化;
这种样式如何修改:使用深度选择器(>>> 、/deep/)
其他:
1、规范类名,不同使用不同前缀类名,使所有类名不重复;
2、将 css 模块化:webpack 在构建时将处理css的css-loader模块开启 module(设置为true),在 < style >标签添加 module 属性;打包工具会将类名编译为带哈希的字符串,这样,就产生了独一无二的class;(:local 与 :global 区分是否需要编译::global(.title):{color:red})
3、直接将样式写在行内;
4、样式写在 js 里面
const style = {
'color': 'red',
'fontSize': '46px'
};
1、vue-loader是用于处理单文件组件(SFC,Single-File Component)的webpack loader
2、因为有了vue-loader,我们就可以在项目中编写SFC格式的Vue组件,我们可以把代码分割为、
创建一个全局的响应式对象;在业务逻辑比较简单的项目中用来实现跨组件之间的传值;类似 vuex,使用简单,但是修改过于随意不推荐使用;
过滤器本质上不会改变原始数据,只会对数据进行加工处理后返回,过滤后的数据在进行调用;可以理解成是一个普通函数;
常见场景:单位转化、千分符、文本格式转化、时间格式转化等;
在vue3 中被废弃,可以使用普通的方法来替代;
axios 是一个轻量的 HTTP客户端;基于 XMLHttpRequest 服务来执行 HTTP 请求,支持丰富的配置,支持 Promise,支持浏览器端和 Node.js 端
1、设置请求超时时间;
2、根据项目环境设置请求路径;
3、设置请求拦截,添加 Token;
4、设置响应拦截,对响应状态码或者数据进行格式化;
5、添加请求队列,实现请求loading;
6、维护取消请求token,页面切换时通过路由守卫取消上个页面正在发送的请求;
作用:用来注册使用插件或者组件的方法。
原理:
1、检测组件是否注册,避免重复注册;
2、处理入参,将参数(除了第一个)整理成数组,再将 Vue 对象添加到这个数组的起始位置;
3、第一个参数是对象就执行对象里面的install方法,是方法则直接执行这个方法,然后缓存插件到已添加的数组中;
4、执行 install 方法时会将参数集合传入,第一个参数是 Vue 对象, install 方法会将安装的插件挂载到 Vue原型链上;
5、返回 Vue 对象;
可以采取分页的方式获取,避免渲染大量数据
vue-virtual-scroller (opens new window)等虚拟滚动方案,只渲染视口范围内的数据
如果不需要更新,可以使用v-once方式只渲染一次
通过v-memo (opens new window)可以缓存结果,结合v-for使用,避免数据变化时不必要的VNode创建
可以采用懒加载方式,在用户需要的时候再加载数据,比如tree组件子树的懒加载
1、组件里面处理
通过 errorCaptured 钩子函数来捕获来自后代组件的错误;如果errorCaptured 钩子返回 false 则会中断传递;
2、全局处理
通过 Vue.config.errorHandler 配置函数来处理;
3、接口处理
通过 promise.reject 来抛出异常错误;
1、使用路由懒加载,异步组件,实现组件拆分,减少入口文件的体积大小;
2、抽离公共代码,采用 splitChunk 进行代码分割;
3、组件、工具采用按需加载的方式;
4、静态资源缓存,http 缓存、本地缓存;
5、图片资源压缩,对小图片进行 base64 减少 http 请求;
6、打包时开启 gzip 压缩;
7、静态资源采用 CDN 提速;
8、采用 SSR 对首屏做服务端渲染(直接返回渲染好的首页);
1、代码层面
v-show、v-if 选取;
合理设置 key 值;
数据层级不易过深(vue递归设置响应式,数据层级深,性能会很差),合理设置响应式数据;
使用数据时缓存结果,不频繁取值;(针对循环中的响应式数据)
自定义事件及时销毁
图片资源懒加载
控制组件的粒度—VUE 是采用的组件级更新
采用函数式组件—函数式组件开销比较低;(vue2)
使用 kee-alive 、v-once缓存组件;
组件、插件按需引入
路由懒加载
服务端渲染
分页、虚拟滚动、时间分片;(数据过多)
2、webpack
对图片进行压缩
减少 ES6 转为 ES5 的冗余代码
提取公共代码
模板预编译
提取组件的 CSS
优化 SourceMap
构建结果输出分析
3、web技术优化
开启 gzip 压缩
浏览器缓存
CDN 的使用
使用 Chrome Performance 查找性能瓶颈
1、登录鉴权:用户登录后将 token 缓存本地,发送请求时携带 token,后端对 token 进行校验;
2、访问权限:根据用户是否登录判断用户是否可以访问页面,在路由守卫中判断;
3、页面权限:客户端维护权限路由,根据接口返回权限列表,筛选出有权限的路由通过 addRouters 动态添加到路由表中;
4、按钮权限:一般通过指令实现,根据接口返回页面按钮权限列表,在按钮上使用指令控制显示隐藏;
1、动态节点标记:由于 diff 算法无法避免新旧虚拟 DOM 中无用的比较操作,Vue.js 3.0 引入了 patchFlag,用来标记动态内容。在编译过程中会根据不同的属性类型打上不同的标识,从而实现了快速 diff 算法;
2、 属性 diff 优化策略:vue3会充分利用 patchFlag和 dynamicChildren做优化。如果确定只是某个局部的变动,比如 style改变,那么只会调用 hostPatchProp并传入对应的参数 style做特定的更新(靶向更新);如果有 dynamicChildren,会执行 patchBlockChildren做对比更新
3、静态提升:编译后的代码会提升静态元素的createVNode,这样就不会在每次更新组件的时候重新创建VNode;
4、预字符串化:大型静态资源可以直接通过innerHTML设置,性能更好,减少大量VNode的创建,降低内存消耗;
5、函数缓存:每次调用 render 的时候要创建新函数,开启函数缓存 cacheHandlers 选项后,函数会被缓存起来,后续可以直接使用;
1、cancelToken.source
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('https://mdn.github.io/dom-examples/abort-api/sintel.mp4', {
cancelToken: source.token
}).catch(function (thrown) {
// 捕捉:判断请求是否已中止
if (axios.isCancel(thrown)) {
// 参数 thrown 是自定义的信息
console.log('Request canceled', thrown.message);
} else {
// 处理错误
}
});
//触发中断请求
source.cancel('interrupt');
2、通过传递 executor 函数到 CancelToken 的构造函数来创建一个 cancel token
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c;
})
});
// 触发中断操作
cancel('interrupt')
拓展:
1、页面跳转中断上一页未完成的请求
在请求拦截的时候定义一个全局的变量 $httpRequestList,用来收集请求所有请求的请求信息;在路由跳转的时候拿到这个全局变量,遍历执行用来中断所有未完成的请求;然后再响应拦截里面对中断请求进行处理;
//axios
const CancelToken = axios.CancelToken;
Vue.$httpRequestList=[];
// 在请求拦截器里面 统一添加 取消请求
request.interceptors.request.use(config => {
// 强行中断请求要用到的,记录请求信息
config['cancelToken'] = new CancelToken(function executor(cancel) {
Vue.$httpRequestList.push(cancel) //存储cancle
})
config.headers = { ...config.headers, ...config.config.headers }
... ...
return config;
});
// response拦截器
// 在响应拦截器里面 统一添加 处理取消请求
request.interceptors.response.use(response => {
... ...
}, error => {
if(error.message === 'interrupt') {
console.log('请求中断');
return new Promise(() => {});
}
// Vue.$httpRequestList = [];
return Promise.reject(error)
});
//路由变化
router.beforeEach((to, from, next) => { //路由切换检测是否强行中断,
if(Vue.$httpRequestList.length>0){ //强行中断时才向下执行
Vue.$httpRequestList.forEach(item=>{
item('interrupt');//给个标志,中断请求
})
}
next();
});
2、三个请求,一个成功之后中断其他两个请求
思路:定义一个变量 list 存放这三个请求的 cancel ,任何一个执行成功之后立即变量 list 执行其他两个的 cancel 方法;
数据和界面完全分离,不需要js创建页面元素等操作;数据变化时界面自动发生改变