参考
https://juejin.cn/post/6844903918753808398#comment
说说你对单页面应用(spa)的理解,它的优缺点是什么
SPA(single-page-application)仅在Web页面初始化时加载相应的HTML、JavaScript和CSS。一旦页面加载完成,不会因为用户的操作对页面进行重新加载或跳转;取而代之的是使用路由机制来实现html内容的变换。
优点:
- 用户体验好,内容的改变不会重新加载页面,避免了不必要的加载和重复渲染
- spa对服务器的压力小
- 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理
缺点: - 首次加载时间较长,因为需要在首次加载时把显示web功能和效果的html、JavaScript和css都统一加载,部分页面按需加载
- SEO难度较大:所有内容都在页面中动态替换显示,所以SEO具有天然的劣势
SPA 和 SSR的区别
- 单页面应用(spa)
- 概念: 整个web项目只有一个页面,使用路由机制进行组件之间的切换
- 优势:客户端渲染、数据传输量小、减小服务器端压力、交互/响应速度快、先后端彻底分离;
- 缺点:首屏加载慢、对SEO不友好,不利于百度,360等搜索引擎收录快照;
- 应用场景:对项目性能要求高、页面加载速度快、要求客户端渲染、对SEO要求低;
- 传输数据:传递JSON对象,由浏览器渲染视图
- 服务端渲染(SSR)
- 概念:将组件或页面经过服务器端生成HTML字符串,再发送到浏览器端渲染;
- 优势:对于SEO友好、首屏加载速度快;
- 缺点:页面重复加载次数高、开发效率低、数据传输量大、服务器压力大;
- 应用场景:对项目SEO要求高、首次打开响应速度快;
- 传输数据:传递完成HTML返回给浏览器渲染
mvc和mvvm
- MVC:control负责调度,model和view没有直接联系(分层,职责明确、可重用但业务逻辑无法重用)
- 1.view(负责跟用户交互的页面)
- 2.control(接受请求->调用模型->根据结果派发页面并经过模型处理返回相应的数据)
- 3.model(完成业务逻辑)
- MVVC:M:MODLE,V:VIEW,vm:ViewModle,只要Modal发生了变化,View就会自动更新,不需要我们认为的再去写如何操作DOM更新的过程了
- 1.view 可以引用viewModel,但反过来却是不行
- 2.viewModel 可以引用model,但是反过来也不行
- 3.如果我们违背了上述规则,那么我们将会无法正常使用MVVM
vue 指令
- v-bind: —》 : 单向绑定
- v-modle:value —》 vue-modle 双向绑定
- v-on: -->@ 绑定事件对象
- @click只是提供了一种模版语法,使冒泡、阻止默认行为更加方便,本质上还是和onClick用的一样的事件监听方式。
- 使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名;
- 事件的回调需要配置在methods对象中,最终会在vm上;
- methods中配置的函数,不要用箭头函数!否则this就不是vm了;
- methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象;
- @click=“demo” 和 @click=“demo($event)” 效果一致,但后者可以传参;
- 事件修饰符
- 使用:@click.stop=“demo”
- prevent:阻止默认事件(常用);
- stop:阻止事件冒泡(常用);
- once:事件只触发一次(常用);
- capture:使用事件的捕获模式;
- self:只有event.target是当前操作的元素时才触发事件;
- passive:事件的默认行为立即执行,无需等待事件回调执行完毕;如绑定wheel事件时,可先进行滚动再执行程序
- v-for
- 用于循环渲染
- v-if
- v-if v-else-if v-else 需要连在一起!
- v-else-if
- v-else
- v-show
- v-text
- 1.作用:向其所在的节点中渲染文本内容。
- 2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。
- v-html
- 1.作用:向指定节点中渲染包含html结构的内容。
- 2.与插值语法的区别:
(1).v-html会替换掉节点中所有的内容,{{xx}}则不会。
(2).v-html可以识别html结构。 - 3.严重注意:v-html有安全性问题!!!!
(1).在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
(2).一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
- v-once
- 1.v-once所在节点在初次动态渲染后,就视为静态内容了。
- 2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
- v-pre
- 1.跳过其所在节点的编译过程。
- 2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
- v-cloak
- 1.本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
- 2.使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。
- 当vue还没有被挂载时,相应插值语法让其不显示,挂载后才显示
Class 与 Style 如何动态绑定?
Class可以使用对象语法和数组语法进行动态绑定
- 字符串写法
<div class='class-basic' v-bind:class="calss"></div>
data: {
class:'active'
}
- 对象语法:
- 场景:要绑定的class对象个数确定,名字也确定,但动态决定是否使用
<div v-bind:class="classObj"></div>
data: {
classObj:{
active:true
text-danger:false
}
}
- 数组语法
- 场景:要绑定的样式个数和名字都不确定
<div v-bind:class="classArray"></div>
data: {
classArray:['active','text-danger']
}
Style 也可以通过对象语法和数组语法进行动态绑定:
- 对象语法:
<div v-bind:style="styleObj"></div>
data: {
styleObj:{
//这里的key为style上的属性,同时改为驼峰
fontSize:'40px'
}
}
- 数组语法:
- 场景:同时运用两个对象
<div v-bind:style="[styleColor, styleSize]"></div>
data: {
styleColor: {
color: 'red'
},
styleSize:{
fontSize:'23px'
}
}
组件中的data为什么是一个函数
Object是引用数据类型,如果不用function返回,每个组件的data都是内存的同一个地址,一个数据改变了其他也改变了;
JavaScript只有函数构成作用域(注意理解作用域,只有函数{}构成作用域,对象的{}以及if(){}都不构成作用域),data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响。
使用对象
function obj ={
name='a'
age =22
}
function Obj(){
return obj
}
let obj1 =new Obj();
let obj2 = new Obj();
obj1.name='b'
console.log(obj1); //{name:"b",age:22}
console.log(obj2);//{name:"b",age:22}
使用函数
<script>
function person() {
return {
name:"a",
age:22
}
};
var obj1 = person();
var obj2 = person();
obj1.name = 'b';
console.log(obj1); //{name:"b",age:22}
console.log(obj2);//{name:"a",age:22}
</script>
VUE 双向绑定
vue.js采用数据劫持结合发布者-订阅者模式的方式,通过object.defineProperty()
来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
要实现mvvm的双向绑定,要实现以下几点:
1、实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
2、实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
3、实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
4、mvvm入口函数,整合以上三者
数据代理
vue 中的data数据是存储在_data中的,如果不做数据代理,那视图层中获取数据需要_data.name,非常的繁琐,而做了数据代理之后只需要使用name ,从而简化了开发
数据代理原理
object.defineProperty(vm,'name',{
getter(){
return this._data.name
}
setter(newVal){
this.name = this._data.name
}
})
Observer 数据劫持
1.vue2 利用Object.defineProperty()来监听属性变动,实现数据劫持
var data ={name:'asdasd'}
observe(data)
data.name='11111111'
function observe(data){
if(!data || typeof data !=== 'object') return
Object.key(data).forEach((key)=>{
defineReactive(data,key,data[key])
})
}
function defineReactive(data,key,val){
observe(data) //监听子属性
Object.defineProperty(data,key,{
enumerable:true,
configurable: false //不能再定义
get: function(){
return val
}
set:function(newVal){
console.log('监听值变化了',val,'-->',newVal)
val = newVal
}
})
}
vue3 使用Proxy来实现数据劫持
// 判断是否为对象和数组
function isObjectOrArray (obj) {
let type = Object.prototype.toString.call(obj)
return (type === '[object Object]' || type === '[object Array]')
}
// 使用proxy劫持
class ObserveByProxy {
constructor(obj, handle = {
// 拦截设置属性值或添加新属性
set (targe, prop, value) {
console.log(`检测到${prop}值由${targe[prop]}变为${value}`)
//使用Reflect可以返回操作是否成功,其会返回布尔值
return Reflect.set(targe, prop, value)
}
// 拦截读取属性值
get (target, prop) {
return Reflect.get(target, prop)
},
// 拦截删除属性
deleteProperty (target, prop) {
return Reflect.deleteProperty(target, prop)
}
}) {
if (!isObjectOrArray(obj)) throw new TypeError('targe不是数组也不是对象')
this._obj = JSON.parse(JSON.stringify(obj)) //深拷贝,防止引用改变数据
this._handle = handle
return new Proxy(this._observe(this._obj), this._handle)
}
_observe (observeObj) {
//遍历对象中每一项
Object.keys(observeObj).forEach((key) => {
//判断是否为对象和数组
if (isObjectOrArray(observeObj[key])) {
//递归
this._observe(observeObj[key])
//转为proxy代理
observeObj[key] = new Proxy(observeObj[key], this._handle)
}
})
return observeObj
}
}
const o = {
a: [1, 2],
c: {
a: 1,
b: 2,
c: [
[1, 2, {
d: 3
}]
]
},
b: 2
}
const ob = new ObserveByProxy(o);
ob.a.push(3); // 检测到2值由undefined变为3 检测到length值由3变为3
ob.c.a = 2; // 检测到a值由1变为2
ob.c.c[0][2].d = 6; // 检测到d值由3变为6
ob.b = 44; // 检测到b值由2变为44
参考
https://segmentfault.com/a/1190000006599500
https://zhuanlan.zhihu.com/p/50547367
Proxy 与 Object.defineProperty 优劣对比Proxy 可以直接监听对象而非属性;
Proxy 的优势如下:
- Proxy 可以直接监听数组的变化;
- Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
- Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;
- Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;
Proxy 的劣势如下:
- 由于是新特新浏览器兼容性差
Object.defineProperty 的优势如下: - 兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。
Object.defineProperty 的劣势如下:
- 只能监听属性不能监听对象
- 不能监听属性的添加和删除
- 不能检测数组索引和长度的变更
VUE2和VUE3 的区别
- 双向绑定
- VUE2
- 使用object.defineProperty()来实现数据响应式,
- 数组:使用索引修改值以及长度方式改变时,数据劫持不到
- 对象:删除,添加对象属性数据劫持不到
- VUE3
- reactive函数是使用Proxy来实现Vue3的响应式,用于对象类型
- ref函数使用defineProperty()来说实现,用于基本类型
- VUE2
- 生命钩子函数
- VUE3采用ts编写
- VUE3使用组合式API(Composition),setup(),this不指向当前组件 VUE2使用选项类型API(Options API)
- VUE3在differ算法上做出了改进
如何解决vue2数据劫持不到问题?
- 对象
- 添加对象属性:使用this.$set(对象,对象属性)或使用Vue $set(对象,对象属性)
- 删除对象属性:使用this.$delete(对象,对象属性)或使用Vue. $delete(对象,对象属性)
- 数组
- 使用索引修改值时:使用this.$set(数组名,索引,改变的值)或Vue. $set(数组名,索引,改变的值),还可以使用
arr.splice(索引,1,改变的值)
- 使用索引修改值时:使用this.$set(数组名,索引,改变的值)或Vue. $set(数组名,索引,改变的值),还可以使用
vue differ算法
vue2
- 只比较同一层级,不跨级比较
- 比较 key
- 比较标签名
vue3 对differ
参考:https://juejin.cn/post/7010594233253888013#heading-1
documentFragment 和一次性渲染有什么不同
- documentFrament 首先是虚拟的,它的节点增加与删除肯定不会引起 dom 的变化的,所以如果增加节点或者删除节点使用 createDocumentFrament 的话会减少回流的操作。
- Vue 也是使用了 CreateDocumentFragment 的。如果初次渲染,使用 documentFragment会多一个步骤,也就是创建这个documentFragment的步骤,所以一般首次加载是很慢的。
VUE 中的keep-alive
-
diff算法是指对新旧虚拟节点进行对比,并返回一个patch对象,用来存储两个节点不同的地方,最后利用patch记录的消息局部更新DOM
-
diff 算法
-
diff算法是虚拟节点的比较
-
先进行key值的比较
-
先进行同级比较,
-
然后再比较是不是一方有儿子,一方没儿子
- 如果是这样,直接在旧节点中插入或删除儿子即可
-
在比较两方都有儿子的情况
- 情况一:旧:ABCD,新:ABCDE;从头向尾比较,最后插入即可
- 情况二:旧 :ABCD,新:EABCD;从尾向头比较,最后插入即可
- 情况三:旧:ABCD,新:DABC;头和尾先进行一次比对,发现D 时,把 D 移至前面,再继续从头向尾比较,
- 情况四:旧:ABCD,新 BCDA;从头向尾比较后发现不对,就会从尾向头比,把 A 移至最后,再继续比较
- 情况五:旧 :ABCD,新CDME;从头向尾比较,把 CD 移至前面,最后 新建 ME,再把 CD 至为空
-
递归比较子节点
-
概念
keep-alive 是 Vue 的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
作用
在组件切换过程中将状态保存在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验
原理
在 created 函数调用时将需要缓存的 VNode 节点保存在 this.cache 中/在 render(页面渲染) 时,如果 VNode 的 name 符合缓存条件(可以用 include 以及 exclude 控制),则会从 this.cache 中取出之前缓存的 VNode 实例进行渲染。
VNode:虚拟DOM,其实就是一个JS对象
Props
- include - 字符串或正则表达式。只有名称匹配的组件会被缓存。其值为组件名,不是路由名!!!
- exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
- max - 数字。最多可以缓存多少组件实例。
生命周期函数
-
activated
- 在 keep-alive 组件激活时调用
- 该钩子函数在服务器端渲染期间不被调用
-
deactivate
- 在 keep-alive 组件停用时调用
- 该钩子在服务器端渲染期间不被调用
参考:https://blog.csdn.net/fu983531588/article/details/90321827
VUE中组件传值
VUE 生命周期
生命周期 | 描述 |
---|---|
beforeCreate | 初始化了一个空的Vue组件实例对象,只拥有一些默认的生命周期函数和默认的事件,data和methods都还没有初始化 |
created | 组件实例创建完成,data和methods都以初始化完成,但真实dom还没有生成,在这里可以向服务端获取数据、操作data中的数据,使用methods方法 |
beforeMount | 组件挂载之前,这时模板已经在内存中构建完成,但没有挂载到真实dom树上 |
mounted | 将模板挂载到真实dom树上 。vm实例中已经添加完成$el了,已经替换掉那些DOM元素了(双括号中的变量),这个时候可以操作DOM了 |
beforeUpdate | data改变之后,对应的组件重新渲染之前,这时data值是新的,而页面上的值是旧的 |
updated | data改变之后,对应组件重新渲染之后,这时页面和data数据保持同步了 |
beforeDestroy | 在组件实例摧毁之前,此时实例中的所有方法和data都是可以使用的,主要用户关闭连接,清除定时器,但不能执行修改数据操作,页面将不会继续渲染(如果执行数据修改操作的话,那就又回到了beforeUpdate生命周期) |
destroyed | 组件实例摧毁之后,实例不可以使用 |
父组件和子组件生命周期狗子函数执行顺序
- 加载渲染过程
父beforeCreate-》父created-》父beforeMounte -》子beforeCreate-》子created-》子beforeMounte-》子mounted-》父亲mounted - 子组件更新过程
父beforeUpdate-》子beforeUpdate-》子updated-》父updated - 父组件更新过程
父beforeUpdate-》父updated- - 销毁过程
父beforeDestroy-》子beforeDestroy-》子destroyed-》父destroyed
在什么周期中什么时候发送异步请求?
- 在created、beforeMounte、mounted 中都可以调用
- 推荐在created中进行调用,可以更快的获取服务端数据,减少loading时间,服务端渲染(srr)不支持beforeMoute和mounted钩子函数,所以使用created有助于一致性。
在生命周期中什么时候才能访问操作dom?
- 在mounted之后才能,因为mounted阶段,刚把模板的虚拟dom挂载到真实的dom树上,vm实例添加完成了$el
父组件可以监听到子组件的生命周期吗
可以使用自定义事件
Vue中的nextTick
- nextTick
- 解释:在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,可以获得更新后的dom。
- 应用
- 如想要在created生命周期函数中操作dom,可以使用Vue.nextTick()回调函数
- 如有操作想要在数据和dom结构都改变后再执行,这时候就需要用到nextTick函数
Vue中的nextTick是微任务还是宏任务?
nextTick的内部实现如果支持 promise 那就使用 promise,没有就用MutationObserver(微任务),在没有就用 setImmediate(宏任务),还没有就用 setTimeOut;所以nextTick 有可能是宏任务,也有可能是微任务
参考:https://juejin.cn/post/6875492931726376974
computed 、watch、method的区别
-
computed
- 计算属性,当依赖值发送改变下一次获取的computed值也会发生改变,computed的值具有缓存
- 返回值不能调用异步函数,因为会把返回值交给js引擎,不会交给vm对象
-
fullName:{ //其返回值不返回给fllName,反而返回给了seiTImeout对象 setTimeout(()=>{ return 'compute' },1000) }
-
watch
- 监听,当监听的数据发生变化时会执行回调进行后续操作,该回调为异步回调
- 深度监听 后加
deep:true
- 监听完立马运行,后面加
immediate:true
- 监听中可以使用异步函数
-
method
- 在使用method的时候,
{{fun(x)}}
,当渲染之后如果没有发生变化也会执行,而计算属性由于拥有缓存,其依赖值不改变,不会执行
- 在使用method的时候,
v-if 和 v-show 的区别
- 手段
- v-if 是动态的向DOM树内添加或者删除DOM元素
- v-show是通过设置DOM元素的display样式属性来控制显影
- 编译过程
- v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;
- v-show只是简单的基于css切换;
- 编译条件
- v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译(编译被缓存?编译被缓存后,然后再切换的时候进行局部卸载);
- v-show是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且DOM元素保留;
- 性能消耗
- v-if有更高的切换消耗
- v-show有更高的初始渲染消耗
- 使用场景
- v-if主要使用在条件不大变化
- v-show主要使用在频繁切换的场景下
- v-
参考:https://www.cnblogs.com/wmhuang/p/5420344.html
vue 中key的作用
diff 算法需要比对虚拟 dom 的修改,然后异步的渲染到页面中,当出现大量相同的标签时,vnode 会首先判断 key 和标签名是否一致,如果一致再去判断子节点一致,使用 key 可以帮助 diff 算法提升判断的速度,在页面重新渲染时更快消耗更少
- key 的作用主要是为了更高效的更新虚拟 DOM,因为它可以非常精确的找到相同节点,因此 patch 过程会非常高效
- Vue 在 patch 过程中会判断两个节点是不是相同节点时,key 是一个必要条件。比如渲染列表时,如果不写 key,Vue 在比较的时候,就可能会导致频繁更新元素,使整个 patch 过程比较低效,影响性能
- 应该避免使用数组下标作为 key,因为 key 值不是唯一的话可能会导致上面图中表示的 bug,使 Vue 无法区分它他,还有比如在使用相同标签元素过渡切换的时候,就会导致只替换其内部属性而不会触发过渡效果
- 从源码里可以知道,Vue 判断两个节点是否相同时主要判断两者的元素类型和 key 等,如果不设置 key,就可能永远认为这两个是相同节点,只能去做更新操作,就造成大量不必要的 DOM 更新操作,明显是不可取的
VUEX
用于实习集中式状态管理的一个VUEX
state
- 用于存储数据
- 获取数据时建议使用getters
- 硬要直接使用的话在计算属性中使用,从而实现响应式 ,this.$state.state.count
- mapState 辅助函数可以简化在计算属性中的写法,要与计算属性的局部变量共同使用时使用…mapState
getter
- 获取state中的数据,同时进行处理
- 参数中可以获取state和其他getter
- 也可使用mapGetters 先引用,放在compute中,
...mapGetters(['方法名','方法名'])
- 也可使用mapGetters 先引用,放在compute中,
Mutation
- 更改store中状态的唯一方法提交 mutation
- mutation 必须是同步函数
- this.$store.commit(“方法名”,数据)
为什么一定要是同步的?
Aciton
action类似于Mutation,不同点在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
- 异步操作数据的
- this.$store.dispatch(“方法名”,数据)
- 也可使用mapActions ,使用方法和以上一样
modlue
- 板块,里面可以放多个vuex
vue组件之间通信方式
- 父传子
- 子组件设置props+父组件设置v:bind:/
- 子传父
- 子组件使用$emit,父组件使用v-on/@自定义事件
- 任意组件通信,建立一个空的VUE对象,利用emit发送,on接收
- Vue.prototype.Event=new Vue();
- Event.$emit(事件名,数据);
- Event.$on(事件名,data => {});
- 父组件通过v-bind:/:传值,子组件通过this.$attrs获取
- 父传子
- 当子组件没有设置props的时候可以使用
- this.$attrs获取到的是一个对象(所有父组件传过来的集合)
- Vuex
- 全局事件总线
- 可以用于任意组件之间的通信
- 安装全局任意组件
new Vue({ ..... beforeCreate(){ vue.prototype.$bus= this //$bus就是当前应用的vm } })
- 使用事件总线
- 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){ demo(data){........} } mounted(){ this.$bus.$on('xxxxx',this.demo) }
- 提供数据:
this.$bus.emit('xxxx',数据)
- 在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件
- 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
组件自定义事件
-
一种组件间通信的方式,适用于:子组件 ===> 父组件
-
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
-
绑定自定义事件:
-
第一种方式,在父组件中:
<Demo @atguigu="test"/>
或<Demo v-on:atguigu="test"/>
-
第二种方式,在父组件中:
<Demo ref="demo"/> ...... mounted(){ this.$refs.xxx.$on('atguigu',this.test) }
-
若想让自定义事件只能触发一次,可以使用
once
修饰符,或$once
方法。
-
-
触发自定义事件:
this.$emit('atguigu',数据)
-
解绑自定义事件
this.$off('atguigu')
-
组件上也可以绑定原生DOM事件,需要使用
native
修饰符。 -
注意:通过
this.$refs.xxx.$on('atguigu',回调)
绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
全局事件总线
-
一种组件间通信的方式,适用于任意组件间通信。
-
安装全局事件总线:
new Vue({ ...... beforeCreate() { Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm }, ...... })
-
使用事件总线:
-
接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){ demo(data){......} } ...... mounted() { this.$bus.$on('xxxx',this.demo) }
-
提供数据:
this.$bus.$emit('xxxx',数据)
-
-
最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
事件订阅与发布
-
一种组件间通信的方式,适用于任意组件间通信。
-
使用步骤:
-
安装pubsub:
npm i pubsub-js
-
引入:
import pubsub from 'pubsub-js'
-
接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
methods(){ demo(data){......} } ...... mounted() { this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息 }
-
提供数据:
pubsub.publish('xxx',数据)
-
最好在beforeDestroy钩子中,用
PubSub.unsubscribe(pid)
去取消订阅。
-
vue 优化方式
1.v-if 和v-show
对于条件不大变化的场景使用v-if,对于频繁切换的使用v-show
v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
v-for 遍历必须为 item 添加 key,同时添加的key不可以是index值,而是item的id值
v-for 和 v-if 不能同时使用
- v-for的优先级比v-if大,从而都是先渲染再进行判断,带来性能上的浪费
- 如条件在外部在外层嵌套
temple
(不生产dom节点) - 在内部提前使用computer进行过滤
- 如条件在外部在外层嵌套
2 computed 和 watch 区分使用场景
3.Object.freeze()
含义:Object.freeze()接受一个对象作为参数,并返回一个相同的不可变的对象。这就意味着我们不能添加,删除或更改对象的任何属性。
在vue的优化中,面对展示类的数据时(数据不需要进行修改),使用object.freeze() 冻结对象,阻止vue的数据劫持从而大大提高效率,如果你的数据属性需要改变,可以重新替换成一个新的。
参考:https://juejin.cn/post/6844903922469961741
4.removeEventListener
组件销毁的时候会断开所有与实例联系,但是除了addEventListener(添加的事件监听),所以当一个组件销毁的时候需要手动去removeEventListener
5.图片懒加载
参考 :https://www.jianshu.com/p/4f3f79a0d7ce
6.路由懒加载
参考:https://router.vuejs.org/zh/guide/advanced/lazy-loading.html
7.第三方插件的按需引入
8.防抖节流
9.长列表固定个数
10.为减少重新渲染和创建dom节点的时间,采用虚拟dom
11.spa 使用 keep-alive
12.基础的 Web 技术的优化
1.开启 gzip 压缩
gzip(GNU- ZIP)是一种压缩技术。经过gzip压缩后页面大小可以变为原来的30%甚至更小,这样,用户浏览页面的时候速度会快得多。gzip的压缩页面需要浏览器和服务器双方都支持,实际上就是服务器端压缩,传到浏览器后浏览器解压并解析。浏览器那里不需要我们担心,因为目前的大多数浏览器都支持解析gzip压缩过的资源文件。在实际的应用中我们发现压缩的比率往往在3到10倍,也就是本来50k大小的页面,采用压缩后实际传输的内容大小只有5至15k大小,这可以大大节省服务器的网络带宽,同时如果应用程序的响应足够快时,网站的速度瓶颈就转到了网络的传输速度上,因此内容压缩后就可以大大的提升页面的浏览速度。
实现gzip压缩的方式有多种,比如:nginx、tomcat、java等,选用其中一种即可。
参考:https://blog.csdn.net/yjclsx/article/details/80250218
2.浏览器缓存
参考:https://blog.csdn.net/weixin_36852235/article/details/83033091
3.CDN 的使用
4.使用 Chrome Performance 查找性能瓶颈
vue-route
路由的两种工作模式
- hash
- url上,#号后面跟着的就是hash值
- hash值不会包含在http请求中,从而hash值不会带给服务器
- 优点
- 服务器上不需要对路径进行重定向
- 兼容性好
- 缺点:
- 若app将地址通过第三方手机app分享,app校验严格,则地址会被标记不合法
- 地址永远带#号,不美观
- history
- 优点:
- 地址栏干净美观
- 缺点
- 兼容性比hash模式差
- 应用部署上线后需要后端人员支持,需要对对应的路径进行重定向,解决刷新页面服务端404的问题
- 优点:
路由跳转的两种模式 push和replace
router-link 默认设置为push
push :
路由分别从 /home -》 /home/user -》 home/user/message
点击返回之后 则跳到 home/user
replace :
会代替当前浏览器的地址
当 home/user/message 设置了router-link
路由分别从 /home -》 /home/user -》 home/user/message
点击返回之后 则跳到 /home 而不是home/user
Vue-router有哪几种钩子函数
-
beforeEach 全局前置守卫
- 参数有
- to(Route路由对象)
- from(Route路由对象)
- next(function函数) 一定要调用才能进行下一步,确保 next 在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。
- 参数有
-
beforeResolve 全局解析守卫
- 它在 每次导航时都会触发,但是确保在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用。
- router.beforeResolve 是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置。
-
afterEach 全局后置钩子
- 这些钩子不会接受 next 函数也不会改变导航本身
- 分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用。
-
beforeEnter 路由独享守卫
- beforeEnter 守卫 只在进入路由时触发,不会在 params、query 或 hash 改变时触发。
-
组件内的守卫
- beforeRouteEnter
- 在渲染该组件的对应路由被验证前调用
- 不能获取组件实例
this
,因为当守卫执行时,组件实例还没被创建!但可以使用next()回调来获得
- beforeRouteUpdate
- 在当前路由改变,但是该组件被复用时调用
- 举例来说,对于一个带有动态参数的路径
/users/:id
,在/users/1
和/users/2
之间跳转的时候, - 由于会渲染同样的
UserDetails
组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 - 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例
this
- beforeRouteLeave
- 在导航离开渲染该组件的对应路由时调用
- 与
beforeRouteUpdate
一样,它可以访问组件实例this
- 这个 离开守卫 通常用来预防用户在还未保存修改前突然离开。该导航可以通过返回 false 来取消
vue-router 路由导航过程
- beforeRouteEnter
1.导航被触发。
2.在失活的组件里调用 beforeRouteLeave 守卫。
3.调用全局的 beforeEach 守卫。
4.在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
5.在路由配置里调用 beforeEnter。
6.解析异步路由组件。
7.在被激活的组件里调用 beforeRouteEnter。
8.调用全局的 beforeResolve 守卫(2.5+)。
9.导航被确认。
10.调用全局的 afterEach 钩子。
11.触发 DOM 更新。
12.调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
vue 插槽
发送请求方法
1.XMLHttpRequest
2.jqury
3.axois
4.fetch
5.vue-resouce
main.js
组件
为什么脚手架中引用的是vue.runtime.esm.js而不是整个vue.js文件?
1.vue.js与vue.runtime.esm.js的区别
(1)vue.js是完整版的Vue,包含:核心功能+模板解析器
(2)vue.runtime.esm.js是运行版本的Vue,只包含:核心功能:没有模板解析器
2.因为vue.runtime.esm.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容
3.打包之后使用vue.runtime.esm.js体积更小
vue ref属性
props
-
功能:让组件接收外部传过来的数据
-
传递数据:
<Demo name="xxx"/>
-
接收数据:
-
第一种方式(只接收):
props:['name']
-
第二种方式(限制类型):
props:{name:String}
-
第三种方式(限制类型、限制必要性、指定默认值):
props:{ name:{ type:String, //类型 required:true, //必要性 default:'老王' //默认值 } }
备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
-
mixin(混入)
-
功能:可以把多个组件共用的配置提取成一个混入对象
-
使用方式:
第一步定义混合:
{ data(){....}, methods:{....} .... }
第二步使用混入:
全局混入:
Vue.mixin(xxx)
局部混入:mixins:['xxx']
-
注意:
当混入中定义的属性与组件中定义的属性重名时,使用组件中的属性,当定义生命周期时,都执行,
当在全局中声明时所有vm和vc都添加混入
插件
plugs.js
export default {
install(Vue,x,y,z){
console.log(x,y,z)
//全局过滤器
Vue.filter('mySlice',function(value){
return value.slice(0,4)
})
//定义全局指令
Vue.directive('fbind',{
//指令与元素成功绑定时(一上来)
bind(element,binding){
element.value = binding.value
},
//指令所在元素被插入页面时
inserted(element,binding){
element.focus()
},
//指令所在的模板被重新解析时
update(element,binding){
element.value = binding.value
}
})
//定义混入
Vue.mixin({
data() {
return {
x:100,
y:200
}
},
})
//给Vue原型上添加一个方法(vm和vc就都能用了)
Vue.prototype.hello = ()=>{alert('你好啊')}
}
}
main.js
improt plugs from '../plugs.js'
//应用(使用)插件
Vue.use(plugs,1,2,3)
scoped
- 作用:让样式在局部生效,防止冲突。
- 写法:
<style scoped>
$nextTick
再下一次渲染之后。再执行其中的回调
vite和和vue-cli
vite:先构建服务器,按需加载需要的组件
vue-cli:先加载所有组件再进行打包在构建服务器
vue3
setup函数
- 理解:Vue3.0中一个新的配置项,值为一个函数。
- setup是所有Composition API(组合API)“ 表演的舞台 ”。
- 组件中所用到的:数据、方法等等,均要配置在setup中。
- setup函数的两种返回值:
- 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
- 若返回一个渲染函数:则可以自定义渲染内容。(了解)
- 注意点:
- 尽量不要与Vue2.x配置混用
- Vue2.x配置(data、methos、computed…)中可以访问到setup中的属性、方法。
- 但在setup中不能访问到Vue2.x配置(data、methos、computed…)。
- 如果有重名, setup优先。
- setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)
- setup执行的时机
- 在beforeCreate之前执行一次,this是undefined。
- setup的参数
- props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
- 获取时需现在前面添加props对象属性,
props:['name','sex']
- 获取时需现在前面添加props对象属性,
- context:上下文对象
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
this.$attrs
。 - slots: 收到的插槽内容, 相当于
this.$slots
。 - emit: 分发自定义事件的函数, 相当于
this.$emit
。
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
- props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
- 尽量不要与Vue2.x配置混用
ref函数
- 作用: 定义一个响应式的数据
- 语法:
const xxx = ref(initValue)
- 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。
- JS中操作数据:
xxx.value
- 模板中读取数据: 不需要.value,直接:
<div>{{xxx}}</div>
- 备注:
- 接收的数据可以是:基本类型、也可以是对象类型。
- 基本类型的数据:响应式依然是靠
Object.defineProperty()
的get
与set
完成的。 - 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数——
reactive
函数。
reactive
- 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用
ref
函数) - 语法:
const 代理对象= reactive(源对象)
接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象) - reactive定义的响应式数据是“深层次的”。
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
reacitve和ref的区别
- 从定义数据角度对比:
- ref用来定义:基本类型数据。
- reactive用来定义:对象(或数组)类型数据。
- 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过
reactive
转为代理对象。
- 从原理角度对比:
- ref通过
Object.defineProperty()
的get
与set
来实现响应式(数据劫持)。 - reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
- ref通过
- 从使用角度对比:
- ref定义的数据:操作数据需要
.value
,读取数据时模板中直接读取不需要.value
。 - reactive定义的数据:操作数据与读取数据:均不需要
.value
。
- ref定义的数据:操作数据需要
computed函数
-
与Vue2.x中computed配置功能一致
-
写法
import {computed} from 'vue' setup(){ ... //计算属性——简写 let fullName = computed(()=>{ return person.firstName + '-' + person.lastName }) //计算属性——完整 let fullName = computed({ get(){ return person.firstName + '-' + person.lastName }, set(value){ const nameArr = value.split('-') person.firstName = nameArr[0] person.lastName = nameArr[1] } }) }
2.watch函数
-
与Vue2.x中watch配置功能一致
-
两个小“坑”:
- 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
- 监视reactive定义的响应式数据中某个属性时:deep配置有效。
//情况一:监视ref定义的响应式数据 watch(sum,(newValue,oldValue)=>{ console.log('sum变化了',newValue,oldValue) },{immediate:true}) //情况二:监视多个ref定义的响应式数据 watch([sum,msg],(newValue,oldValue)=>{ console.log('sum或msg变化了',newValue,oldValue) }) /* 情况三:监视reactive定义的响应式数据 若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!! 若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 */ watch(person,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) },{immediate:true,deep:false}) //此处的deep配置不再奏效 //情况四:监视reactive定义的响应式数据中的某个属性 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //情况五:监视reactive定义的响应式数据中的某些属性 watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //特殊情况 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
3.watchEffect函数
-
watch的套路是:既要指明监视的属性,也要指明监视的回调。
-
watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
-
watchEffect有点像computed:
- 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
- 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。 watchEffect(()=>{ const x1 = sum.value const x2 = person.age console.log('watchEffect配置的回调执行了') })
shallowReactive 与 shallowRef
-
shallowReactive:只处理对象最外层属性的响应式(浅响应式,就第一次对象属性)。
-
shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理(即就是不去寻求reactive的帮助,value中直接为该对象),其中为一个对象时,修改对象的属性不会进行响应式,而替换整个对象,则会产生响应
-
什么时候使用?
- 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。