前端VUE面试

参考

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()来说实现,用于基本类型
  • 生命钩子函数
    在这里插入图片描述
  • 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,改变的值)

vue differ算法

vue2

  1. 只比较同一层级,不跨级比较
  2. 比较 key
  3. 比较标签名

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了
beforeUpdatedata改变之后,对应的组件重新渲染之前,这时data值是新的,而页面上的值是旧的
updateddata改变之后,对应组件重新渲染之后,这时页面和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)}},当渲染之后如果没有发生变化也会执行,而计算属性由于拥有缓存,其依赖值不改变,不会执行

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 算法提升判断的速度,在页面重新渲染时更快消耗更少

  1. key 的作用主要是为了更高效的更新虚拟 DOM,因为它可以非常精确的找到相同节点,因此 patch 过程会非常高效
  2. Vue 在 patch 过程中会判断两个节点是不是相同节点时,key 是一个必要条件。比如渲染列表时,如果不写 key,Vue 在比较的时候,就可能会导致频繁更新元素,使整个 patch 过程比较低效,影响性能
  3. 应该避免使用数组下标作为 key,因为 key 值不是唯一的话可能会导致上面图中表示的 bug,使 Vue 无法区分它他,还有比如在使用相同标签元素过渡切换的时候,就会导致只替换其内部属性而不会触发过渡效果
  4. 从源码里可以知道,Vue 判断两个节点是否相同时主要判断两者的元素类型和 key 等,如果不设置 key,就可能永远认为这两个是相同节点,只能去做更新操作,就造成大量不必要的 DOM 更新操作,明显是不可取的

VUEX

用于实习集中式状态管理的一个VUEX

state

  • 用于存储数据
  • 获取数据时建议使用getters
  • 硬要直接使用的话在计算属性中使用,从而实现响应式 ,this.$state.state.count
  • mapState 辅助函数可以简化在计算属性中的写法,要与计算属性的局部变量共同使用时使用…mapState

getter

  • 获取state中的数据,同时进行处理
  • 参数中可以获取state和其他getter
    通过属性访问:this.$store.getters.doneTodosCount
    • 也可使用mapGetters 先引用,放在compute中,...mapGetters(['方法名','方法名'])

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去解绑当前组件所用到的事件

组件自定义事件

  1. 一种组件间通信的方式,适用于:子组件 ===> 父组件

  2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。

  3. 绑定自定义事件:

    1. 第一种方式,在父组件中:<Demo @atguigu="test"/><Demo v-on:atguigu="test"/>

    2. 第二种方式,在父组件中:

      <Demo ref="demo"/>
      ......
      mounted(){
         this.$refs.xxx.$on('atguigu',this.test)
      }
      
    3. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

  4. 触发自定义事件:this.$emit('atguigu',数据)

  5. 解绑自定义事件this.$off('atguigu')

  6. 组件上也可以绑定原生DOM事件,需要使用native修饰符。

  7. 注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

全局事件总线

  1. 一种组件间通信的方式,适用于任意组件间通信。

  2. 安装全局事件总线:

    new Vue({
    	......
    	beforeCreate() {
    		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
    	},
        ......
    }) 
    
  3. 使用事件总线:

    1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.$bus.$on('xxxx',this.demo)
      }
      
    2. 提供数据:this.$bus.$emit('xxxx',数据)

  4. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

事件订阅与发布

  1. 一种组件间通信的方式,适用于任意组件间通信。

  2. 使用步骤:

    1. 安装pubsub:npm i pubsub-js

    2. 引入: import pubsub from 'pubsub-js'

    3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
      }
      
    4. 提供数据:pubsub.publish('xxx',数据)

    5. 最好在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 路由导航过程

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

  1. 功能:让组件接收外部传过来的数据

  2. 传递数据:<Demo name="xxx"/>

  3. 接收数据:

    1. 第一种方式(只接收):props:['name']

    2. 第二种方式(限制类型):props:{name:String}

    3. 第三种方式(限制类型、限制必要性、指定默认值):

      props:{
      	name:{
      	type:String, //类型
      	required:true, //必要性
      	default:'老王' //默认值
      	}
      }
      

    备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

mixin(混入)

  1. 功能:可以把多个组件共用的配置提取成一个混入对象

  2. 使用方式:

    第一步定义混合:

    {
        data(){....},
        methods:{....}
        ....
    }
    

    第二步使用混入:

    ​ 全局混入:Vue.mixin(xxx)
    ​ 局部混入:mixins:['xxx']

  3. 注意:
    当混入中定义的属性与组件中定义的属性重名时,使用组件中的属性,当定义生命周期时,都执行,
    当在全局中声明时所有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

  1. 作用:让样式在局部生效,防止冲突。
  2. 写法:<style scoped>

$nextTick

再下一次渲染之后。再执行其中的回调

vite和和vue-cli

vite:先构建服务器,按需加载需要的组件
vue-cli:先加载所有组件再进行打包在构建服务器
在这里插入图片描述

vue3

setup函数

  1. 理解:Vue3.0中一个新的配置项,值为一个函数。
  2. setup是所有Composition API(组合API)“ 表演的舞台 ”
  3. 组件中所用到的:数据、方法等等,均要配置在setup中。
  4. setup函数的两种返回值:
    1. 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
    2. 若返回一个渲染函数:则可以自定义渲染内容。(了解)
  5. 注意点:
    1. 尽量不要与Vue2.x配置混用
      • Vue2.x配置(data、methos、computed…)中可以访问到setup中的属性、方法。
      • 但在setup中不能访问到Vue2.x配置(data、methos、computed…)。
      • 如果有重名, setup优先。
    2. setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)
    3. setup执行的时机
      • 在beforeCreate之前执行一次,this是undefined。
    4. setup的参数
      • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
        • 获取时需现在前面添加props对象属性,props:['name','sex']
      • context:上下文对象
        • attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs
        • slots: 收到的插槽内容, 相当于 this.$slots
        • emit: 分发自定义事件的函数, 相当于 this.$emit

ref函数

  • 作用: 定义一个响应式的数据
  • 语法: const xxx = ref(initValue)
    • 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)
    • JS中操作数据: xxx.value
    • 模板中读取数据: 不需要.value,直接:<div>{{xxx}}</div>
  • 备注:
    • 接收的数据可以是:基本类型、也可以是对象类型。
    • 基本类型的数据:响应式依然是靠Object.defineProperty()getset完成的。
    • 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数—— reactive函数。

reactive

  • 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
  • 语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
  • reactive定义的响应式数据是“深层次的”。
  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。

reacitve和ref的区别

  • 从定义数据角度对比:
    • ref用来定义:基本类型数据
    • reactive用来定义:对象(或数组)类型数据
    • 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象
  • 从原理角度对比:
    • ref通过Object.defineProperty()getset来实现响应式(数据劫持)。
    • reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
  • 从使用角度对比:
    • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
    • reactive定义的数据:操作数据与读取数据:均不需要.value

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。
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值