1. vue2响应式原理 Object.defineProperty
(1) defineProperty: 定义属性
Object.defineProperty 声明一个属性
function defineProperty() {
var _obj = {};
Object.defineProperty(_obj, "a", {
value: 1,
});
return _obj;
}
var obj = defineProperty();
console.log(obj); // { a: 1 }
Object.defineProperties 声明多个属性
function defineProperty() {
var _obj = {};
Object.defineProperties(_obj,{
a: {
value: 11
},
b: {
value: 22
}
})
return _obj;
}
var obj = defineProperty();
console.log(obj); // { a: 11, b: 22 }
(2) Object.defineProperties 默认的增加了属性后,所输出的这个对象是不可修改,不可枚举,不可删除
var _obj = {};
Object.defineProperties(_obj, {
a: {
value: 11,
},
b: {
value: 22,
},
});
console.log(_obj) // {a: 11, b: 22}
_obj.a = 888
console.log(_obj) // {a: 11, b: 22} 属性值不可修改
for(let i in _obj){
console.log(i + ':' + _obj[i]) // 属性不可枚举
// 枚举: 把对象的每一项列举出来
// 不可枚举: 不能拿到到key值
}
delete _obj.a
console.log(_obj) // {a: 11, b: 22} 属性不可删除
writable => 写; enumerable => 枚举; configurable => 可配置 , 可操作
var _obj = {};
Object.defineProperties(_obj, {
a: {
value: 11,
writable: true,
enumerable:true,
configurable:true
},
b: {
value: 22,
},
});
console.log(_obj) // {a: 11, b: 22}
_obj.a = 888
console.log(_obj) // {a: 888, b: 22}
for(let i in _obj){
console.log(i + ':' + _obj[i]) // a:888
}
delete _obj.a
console.log(_obj) // {b: 22}
(3) 每个属性定义的时候,都会产生 getter 和 setter 的机制。
数据劫持 : 对待一个对象,它的取值或设置值,有一系列的配置和阻止的方法,按getter和setter的机制去进行数据操作的逻辑,数据在javaScript当中往往就是对象。
输出顺序: get => 1111
const obj = {}
let a = 1111
Object.defineProperty(obj,'a',{
get: function() {
console.log('get')
return a
}
})
console.log(obj.a)
输出顺序: get => 1111 => set => get => bbb
const obj = {}
let a = 1111
Object.defineProperty(obj,'a',{
get: function() {
console.log('get')
return a
},
set: function(newValue) {
console.log('set')
a = newValue
//视图重新渲染
}
})
console.log(obj.a)
obj.a = 'bbb'
console.log(obj.a)
value 、writable 和 getter 、setter 不能同时一起设置,这两者是互斥的。
(4) 处理不同类型的情况
处理值为字符串的情况
输出: 更新视图层
const data = {
name: 'aa',
age: 18
}
tyoeFn(data)
function tyoeFn(target) {
for(let key in target) {
defineFn(target,key,target[key])
}
}
function defineFn(target,key,value) {
Object.defineProperty(target,key,{
get: function() {
return value
},
set: function(newValue) {
if(value != newValue) {
console.log('更新视图层')
value = newValue
}
}
})
}
data.name = 'bb'
处理值为复杂对象的情况
输出: 更新视图层
const data = {
name: 'aa',
age: 18,
obj: {
nameObj: 'nameObj'
}
}
tyoeFn(data)
function tyoeFn(target) {
if(typeof target !== 'object' || target === null) {
return target
}
for(let key in target) {
defineFn(target,key,target[key])
}
}
function defineFn(target,key,value) {
tyoeFn(value)
Object.defineProperty(target,key,{
get: function() {
return value
},
set: function(newValue) {
tyoeFn(newValue)
if(value != newValue) {
console.log('更新视图层')
value = newValue
}
}
})
}
data.obj.nameObj = 'bb'
Object.defineProperty:需要进行深度监听,才能够在数据改变的时候视图进行更新。
第一个缺点(深度监听):如果说这个数据是个对象,并且层级很深的话,一直不断的深度监听下去,直到是一个普通的值为止。如果数据很复杂的话,页面初次渲染会卡死。
第二个缺点:不能处理删除,新增,所以在vue中 删除用Vue.delete,新增用 Vue.set
(3) 处理值为数组的情况
用原型链给 Object.defineProperty里的 数组 增加push等方法
const data = {
arr:[1,2,3,5]
}
const oldArr = Array.prototype;
const newArr = Object.create(oldArr);
['push','pop','shift','unshift'].forEach(methodName => {
newArr[methodName] = function() {
console.log('更新视图层')
oldArr[methodName].call(this,...arguments)
}
})
tyoeFn(data)
function tyoeFn(target) {
if(Array.isArray(target)) {
target.__proto__ = newArr //
}
if(typeof target !== 'object' || target === null) {
return target
}
for(let key in target) {
defineFn(target,key,target[key])
}
}
function defineFn(target,key,value) {
tyoeFn(value)
Object.defineProperty(target,key,{
get: function() {
return value
},
set: function(newValue) {
if(value != newValue) {
console.log('更新视图层')
value = newValue
}
}
})
}
data.arr.push(666)
2. vue3响应式原理Proxy
Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。
proxy是es6新特性,为了对目标的作用主要是通过handler对象中的拦截方法拦截目标对象target的某些行为(如属性查找、赋值、枚举、函数调用等)。
target 目标对象 进行处理的对象; handler 容器 无数操作对象属性的方法
Proxy(target,handler)
target 操作的对象;prop 对象的属性;value: 对象属性值
get: (target, prop)
set: (target, prop, value)
let tar = {
a:1,
b:2
}
let proxy = new Proxy(tar, {
get: function(tar, prop) {
return '属性值' + tar[prop]
},
set: function(tar, prop,value) {
tar[prop] = value
console.log(tar[prop])
}
})
console.log(proxy.a) // 属性值1 访问proxy,就会走 get, 就会执行get里的方法
console.log(tar.a) // 1
proxy.b = 3 // 3
总结:
(1) 通过proxy对象来代理target,先去操作proxy,然后通过handel重写proxy里面这些对对象操作的方式,来间接的操作target。
(2)defineProperty,原则上是给对象增加属性用的,它在修改数组的长度,用所引去设置元素的值,数组的push,pop这些方法是无法触发defineProperty的setter方法。
缺点:vue里使用的对数组的操作全都是vue自己重修写的,不是js的原生,所以导致代码非常重。
proxy没有以上问题,对数组的操作完全可以触发set方法。
(3)vue2没有使用proxy,因为proxy是es6的方法,因为兼容性的问题。vue3.0时,es6广泛推崇,很多浏览器es6兼容,所以使用proxy更加合理。proxy在vue在编译的时候,会进行转换,转换成es5相关执行,就是defineProperty。
(4) vue2.0的data是一个函数,用return返回的原因: 一是,防止引用值重写; 二是,产生的对象,是defineProperty直接操作这个对象。
vue3.0的data:{} 原因是: proxy做一层代理去访问,而这个对象target是必须通过代理proxy才能去完成一系列的data的操作
3. vue2.0 深入响应式原理——为啥数据更改,但页面没有响应更改?
是因为数据没有被依赖收集
数组 => 不会依赖收集,不管有没有值;
对象 => 空值不会依赖收集,有值才会依赖收集。
Vue2.0 不能检测数组和对象的变化。
(1)1. 对于对象
vue初始化实例时执行getter 和 setter 的转化,所以 prototy 必须在 data 上才能 让 vue 把它转换为响应式。
解决方法:Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property。
1.1 => 对于已经创建的实例 obj ,Vue 不允许动态添加根级别的响应式proterty; 即不存在的值(vue无法能检测proterty的添加或删除)。
export default {
data() {
return {
obj: { a: 1 },
};
},
methods: {
btn() {
console.log(this.obj.a); // 1
console.log(this.obj.b); // undefined
//解决方法:Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property。
this.$set(this.obj, "b", 2);
console.log(this.obj.b); // 2
},
},
};
1.2 => 空值不会依赖收集( obj 没有 a ,a 是事后添加的,此时虽然 obj有a ,但 Vue并没有响应式 a ,所以页面上 obj 显示 仍未 {} );
<p>{{obj}}</p> // 页面显示 {}
<button @click="btn">按钮</button>
export default {
name: 'Home',
data() {
return {
obj: {}
}
},
methods:{
btn() {
this.obj.a = 111
console.log(this.obj.a) // 111
}
}
}
1.3 => 有值才会依赖收集 ( obj 有 b ,b 是 在 data 里事前的,已被 Vue 依赖收集 转换响应式 )。
<p>{{obj}}</p> // 页面显示 { 'b': 33 }
<button @click="btn">按钮</button>
export default {
name: 'Home',
data() {
return {
obj: {b:22}
}
},
methods:{
btn() {
this.obj.b = 33
console.dir(this.obj.b) // 33
}
}
}
1.4 => 重新更新 b 的依赖,vue 会遍历 obj ,在遍历的时候 把 a 依赖收集
<p>{{obj}}</p> // 页面显示 {"b": 33,"a": 555}
<button @click="btn">按钮</button>
export default {
name: 'Home',
data() {
return {
obj: {b:22}
}
},
methods:{
btn() {
this.obj.b = 33
this.obj.a = 555
console.dir(this.obj) // {"b": 33,"a": 555}
}
}
}
1.5 => 先点击 按钮22 ,再点击 按钮11,页面显示 {"b": 33,"a": 555} ;
这是因为 先点击 按钮22 ,此时 obj 里已经有了 a;
再点击 按钮11,重新更新 b 的依赖,vue 会遍历 obj ,在遍历的时候 把 a 依赖收集。
反过来,先点击 按钮 11,再点击 按钮22,页面显示 {"b": 33} 。
<p>{{obj}}</p>
<button @click="btn11">按钮11</button>
<button @click="btn22">按钮22</button>
export default {
name: 'Home',
data() {
return {
obj: {b:22}
}
},
methods:{
btn11() {
this.obj.b = 33
},
btn22() {
this.obj.a = 555
}
}
}
(2) 对于数组
Vue 不能检测以下数组的变动:
=> 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
=> 当你修改数组的长度时,例如:vm.items.length = newLength
解决办法 :
Vue.set => vm.$set(vm.items, indexOfItem, newValue)
splice => vm.items.splice(newLength)
<p>{{arr}}</p> // 页面显示 [1,2,3,4,5]
<button @click="btn">按钮</button>
export default {
name: 'Home',
data() {
return {
arr:[1,2,3,4,5]
}
},
methods:{
btn() {
this.arr[2] = 'aaa'
console.dir(this.arr) // arr:[1,2,"aaa",4,5]
}
}
}
<p>{{arr}}</p> // 页面显示 [1,2,3,4,5]
<button @click="btn">按钮</button>
export default {
name: 'Home',
data() {
return {
arr:[1,2,3,4,5]
}
},
methods:{
btn() {
this.arr.length = 2
console.dir(this.arr) // [1,2]
}
}
}
不会依赖收集,不管有没有值。
<p>{{arr}}</p> // 页面显示 []
<button @click="btn">按钮</button>
export default {
name: 'Home',
data() {
return {
arr:[]
}
},
methods:{
btn() {
this.arr[0] = '111'
console.dir(this.arr) // arr['111']
}
}
}
<p>{{arr}}</p> // 页面显示 [1,2,3]
<button @click="btn">按钮</button>
export default {
name: 'Home',
data() {
return {
arr:[1,2,3]
}
},
methods:{
btn() {
this.arr[0] = 'aaa'
console.dir(this.arr) // arr['aaa',2,3]
}
}
}
4.vue 中传值的形式
父子,子父 之间传值:
父 传 子 => propx
子 传 父 => on/emit
父 传 子 => children 仅适用只有一个子组件
父 传 子 => ref 用 this.refs来标记子组件,从而获取子组件实例
父 传 子 ,爷 传 孙子 => provoid / inject
父 传 子 => .sync修饰符
(1) 父 传 子 => propx
(2) 子 传 父 => on/emit
父组件 自定义组件使用on监听派发出的事件
子组件 this.$emit('gx') 使用emit派发自定义事件: gx
(3) children
this.$parent 得到父组件实例;
this.$children 得到子组件实例,可能有多个,且不保证顺序 ;
这种仅适用 一个 子组件。
(4) ref
(5) 子孙后代组件传值
=> provoid / inject 兄弟俩需要一起使用
- provoid 对象 | 返回一个对象的函数。
- inject 字符串数组 | 对象。
(6) 使用.sync修饰符
直接在子组件修改 父组件传过来的值,虽然可以, 但是 控制台 会报错;vue 不建议这么做,因为会造成 属性修改来源不明。
.sync修饰符可以实现 子组件 与 父组件 的双向绑定,并且可以实现子组件同步修改父组件的值。
同级/兄弟组件传值:
同级/兄弟组件,它们没有直接的隶属关系,对于这类的组件传值,通常会使用vuex、EventBus、observable。
(1) Event Bus
EventBus,也叫事件总线或者中央事件总线。说到底,无非就是利用Vue本身自带的 emit进行。 emit 发送数据的那个组件
需要注意的是:销毁组件时记得移除$on绑定的事件,避免造成重复监听。
(2)Vuex 实现组件全局状态(数据)管理机制
频繁、大范围的数据共享 => vuex
使用vuex的优点:1.能够在vuex集中管理共享的数据,易于开发和后期维护;2.能够高效实现组件之间的数据共享;3.存储在vuex中的数据都是响应式的,能够实时保持数据与页面的同步。
vuex的主要核心概念:State、Mutation、Action、Getter
(2.1) State 提供唯一的 公共数据源,所有的共享数据放在State存储
2.1.1 组件访问State中数据的第一种方式:
this.$store.state.全局数据名称
2.1.2 组件访问State中数据的第二种方式:
(2.2) Mutation 用于变更Store中的数据
2.2.1 只能通过mutation变更Store数据,可集中监控所有数据变化;不可以在组件中直接操作Store中的数据。
2.2.2 this.$store.commit() 触发 mutation的第一种方式
2.2.3 mapMutations函数 触发 mutation的第二种方式
(2.3) Action 用于处理异步任务
this.$store.dispatch() 触发actions 的第一种方式
(2.4)Getter 不会修改Store里的数据,只包装数据
(2.5) 解决浏览器刷新后vuex数据消失
(3) observable
Vue.observable,让一个对象变成响应式数据。Vue 内部会用它来处理 data 函数返回的对象
返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新。也可以作为最小化的跨组件状态存储器
使用场景
在非父子组件通信时,可以使用通常的bus或者使用vuex,但是实现的功能不是太复杂,而使用上面两个又有点繁琐。这时,observable就是一个很好的选择。
创建一个js文件
// 引入vue
import Vue from 'vue
// 创建state对象,使用observable让state对象可响应
export let state = Vue.observable({
name: '张三',
'age': 38
})
// 创建对应的方法
export let mutations = {
changeName(name) {
state.name = name
},
setAge(age) {
state.age = age
}
}
在.vue文件中直接使用即可
<template>
<div>
姓名:{{ name }}
年龄:{{ age }}
<button @click="changeName('李四')">改变姓名</button>
<button @click="setAge(18)">改变年龄</button>
</div>
</template>
import { state, mutations } from '@/store
export default {
// 在计算属性中拿到值
computed: {
name() {
return state.name
},
age() {
return state.age
}
},
// 调用mutations里面的方法,更新数据
methods: {
changeName: mutations.changeName,
setAge: mutations.setAge
}
}
5 方法调用、computed、watch的区别
方法:页面数据每次重新渲染都会重新执行。性能消耗大,除非不希望有缓存的时候用。
computed:计算属性,依赖其他属性计算值,并且 computed 的值有缓存,只有当计算值变化才会返回内容。
watch:监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。
除非不希望缓存,一般用方法; 一般来说需要依赖别的属性来动态获得值的时候可以使用computed; 对于监听到的值变化需要做到异步操作或开销较大的操作用 watch。
6 混入 mixin 抽离组件公共逻辑
组件和 mixin 的 data 和 事件重复时,就用 组件的 替换 mixin 的,没有重复就用 mixin的;
当 生命周期钩子重复时,两者是并存的, 先执行 mixin ,再执行 组件的 。
7 keep-alive
Vue会把组件渲染缓存起来,当再次回到已经渲染的组件,这个组件不会再重新执行渲染的过程,会从缓存里的结果最终显示这个组件。
(1) 属性
keep-alive是vue中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。
keep-alive 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
keep-alive可以设置以下props属性:
include- 字符串或正则表达式。只有名称匹配的组件会被缓存exclude- 字符串或正则表达式。任何名称匹配的组件都不会被缓存max- 数字。最多可以缓存多少组件实例
(2) 生命周期钩子 activated与deactivated
设置了 keep-alive 缓存的组件,会多出两个生命周期钩子(activated与deactivated):
-
首次进入组件时:
beforeRouteEnter>beforeCreate>created>mounted>activated> ... ... >beforeRouteLeave>deactivated -
再次进入组件时:
beforeRouteEnter>activated> ... ... >beforeRouteLeave>deactivated -
activated在组件第一次渲染时会被调用,之后在每次缓存组件被激活时调用。
因为组件被缓存了,再次进入缓存路由/组件时,不会触发这些钩子: beforeCreate created beforeMount mounted 都不会触发。
- deactivated:组件被停用(离开路由)时调用
使用了keep-alive就不会调用beforeDestroy(组件销毁前钩子)和destroyed(组件销毁),因为组件没被销毁,被缓存起来了。
这个钩子可以看作beforeDestroy的替代,如果你缓存了组件,要在组件销毁的的时候做一些事情,你可以放在这个钩子里。
(3) 那假设切换组件时需要数据请求呢?缓存的组件应该不会重新请求了吧
不会,缓冲的还是之前请求到的数据,可以监听路由的变化再对应的请求接口;
不会重新请求了,因为mounted钩子不会再执行~。
=>所以在使用 keep-alive 的时候,Vue 会给你额外提供两个钩子,activated 和 deactivated;所以要请求数据的话可以放在 activated 中~。
(4) 缓存后如何获取数据? 解决方案可以有以下两种:
- beforeRouteEnter
- actived
每次组件渲染的时候,都会执行beforeRouteEnter
beforeRouteEnter(to, from, next){\
next(vm=>{\
console.log(vm)\
// 每次进入路由执行\
vm.getData() // 获取数据\
})\
}
在keep-alive缓存的组件被激活的时候,都会执行actived钩子
activated(){\
this.getData() // 获取数据\
}
(5)v-show 与 keep-alive
状态频繁切换,也可以使用 v-show
v-show 是通过css样式 display:none 来显示与否;
整体结构简单,使用v-show;
组件比较复杂,代码比较多,使用专门的组件缓存keep-alive。
8 异步组件
当组件test 的代码提交非常大时,首次渲染时加载不必要的代码时,这样就会很大的影响性能。
=> 解决办法:异步组件
在组件注册时使用异步组件加载,当组件test代码很大的时候,首次渲染时不去下载这么大的代码,而是到需要的时候再去下载,这样就可以极大的提升性能。
9. 动态组件
通过 component 的 is 属性,来切换不同的组件。
-
<component></component>按名称访问全局和本地组件,可以动态绑定我们的组件,根据数据不同更换不同的组件,在这种情况下,它更像是一个容器。 -
:is 属性:is-bind的缩写,component标签中的is属性决定了当前采用的是那个组件。
10 插槽
(1) 插槽 <slot> ,可以实现内容的动态发布;
有时需要在组件模板中定义大量重复的内容区域,可以使用插槽来避免重复。
PS:其中<span>****</span> 我们不希望是静态不变的,而是动态内容。
(2) 这时,需要将动态内容的模板部分使用<slot>插槽来实现
(3)<slot> 插槽会获取到组件元素包含的内容进行渲染;没有插槽,组件元素内部无法被识别。
(4)如果动态内容有较多一样,只有少部分不同,可以设置插槽默认值。
11 具名插槽 v-slot:名字
为了实现更多的复杂布局,系统提供了具名插槽来实现调用机制。
具名插槽,除了使用指令 v-slot 来调用,还支持 #号 的简写方式。
12 作用域插槽
父子组件之间存在作用域,它们的模板内容只能在自己的作用域编辑;
作用域的问题导致使用插槽时,无法通过父组件区域之间访问子组件内容。
解决思路:1.子组件插槽 v-bind 把数据传出;2.父组件 v-slot 指令获取子组件插槽的数据。
简写方案:
<current-user>
<template v-slot="slotProps">
{{slotProps.user.age}}
</template>
</current-user>
ES6写法,解构
<current-user>
<template v-slot="{{user}}">
{{user.age}}
</template>
</current-user>
13 nextTick
下次DOM 更新循环结束之后执行延迟回调。
在修改数据之后立即使用这个方法,获取更新后的DOM 。
使用场景: 如果想要在修改数据后立刻得到更新后的 DOM 结构,可以使用 VUE.nextTick()
14 父子组件生命周期执行顺序
15 Vue2 与 Vue3 生命周期的区别
16 v-if 和 v-show 的区别
最大的区别在于:对于不显示在页面上的处理,
v-if是直接不渲染该元素;
v-show是做了css样式,display:none来控制元素不显示。
=> 什么时候使用v-if,啥时使用v-show呢?
=> 元素在页面上渲染时,它的显示与隐藏是一次性决定的,后面不再改变的,使用v-if会更好,因为使用v-show它会把不需要显示的也渲染在DOM里面。
=> 如果元素是在刚开始渲染后,还会再频繁的切换,一会显示一会隐藏,使用v-show会更好,因为它只需要操作css样式即可;但使用v-if的话,它要去创建dom元素,销毁dom元素,那这样的话性能开销会更加大。
17 v-model
父组件传入一个参数道子组件;
子组件监听这个值的变化;
利用emit。通知父组件更新这个值
18 对 vue 的理解
Vue.js 是一款流行的JavaScript前端框架;Vue作者是尤雨溪。
Vue所关注的核心是MVC模式中的视图层,同时,它也能方便地获取数据更新,并通过组件内部特定的方法实现视图与模型的交互。
(1) Vue核心特性: 数据驱动(MVVM)
- Model:模型层,应用的数据及业务逻辑
- View:视图层:应用的展示效果,各类UI组件,可以简单的理解为HTML页面
- ViewModel:视图模型层,框架封装的核心,用来连接Model和View,是Model和View之间的通信桥梁
(2) 组件化 : 在Vue中每一个.vue文件都可以视为一个组件
-
降低整个系统的耦合度,在保持接口不变的情况下,我们可以替换不同的组件快速完成需求,例如输入框,可以替换为日历、时间、范围等组件作具体的实现
-
调试方便,由于整个系统是通过组件组合起来的,在出现问题的时候,可以用排除法直接移除组件,或者根据报错的组件快速定位问题,之所以能够快速定位,是因为每个组件之间低耦合,职责单一,所以逻辑会比分析整个系统要简单
-
提高可维护性,由于每个组件的职责单一,并且组件在系统中是被复用的,所以对代码进行优化可获得系统的整体升级
(3) 指令系统 解释:指令 (Directives) 是带有 v- 前缀的特殊属性
作用:当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM
- 常用的指令
-
- 条件渲染指令
v-if - 列表渲染指令
v-for - 属性绑定指令
v-bind - 事件绑定指令
v-on - 双向数据绑定指令
v-model
- 条件渲染指令
没有指令之前我们是怎么做的?是不是先要获取到DOM然后在....干点啥
19 双向绑定的理解
(1) 什么是双向绑定
单向绑定 : 把Model绑定到View,当我们用JavaScript代码更新Model时,View就会自动更新。
双向绑定 : 在单向绑定的基础上,用户更新了View,Model的数据也自动被更新了,这种情况就是双向绑定。
(2) 双向绑定的原理是什么
Vue 是数据双向绑定的框架,双向绑定由三个重要部分构成
- 数据层(Model):应用的数据及业务逻辑
- 视图层(View):应用的展示效果,各类UI组件
- 业务逻辑层(ViewModel):框架封装的核心,它负责将数据与视图关联起来
而上面的这个分层的架构方案,可以用一个专业术语进行称呼:MVVM
这里的控制层的核心功能便是 “数据双向绑定” 。
理解ViewModel 它的主要职责就是:
- 数据变化后更新视图
- 视图变化后更新数据
20 Vue生命周期的理解
(1) 生命周期有哪些
Vue生命周期总共可以分为8个阶段:创建前后, 载入前后, 更新前后, 销毁前销毁后,以及一些特殊场景的生命周期
(2)Vue生命周期流程图
具体分析
beforeCreate -> created
- 初始化
vue实例,进行数据观测
created
- 完成数据观测,属性与方法的运算,
watch、event事件回调的配置 - 可调用
methods中的方法,访问和修改data数据触发响应式渲染dom,可通过computed和watch完成数据计算 - 此时
vm.$el并没有被创建
created -> beforeMount
- 判断是否存在
el选项,若不存在则停止编译,直到调用vm.$mount(el)才会继续编译 - 优先级:
render>template>outerHTML vm.el获取到的是挂载DOM的
beforeMount
- 在此阶段可获取到
vm.el - 此阶段
vm.el虽已完成DOM初始化,但并未挂载在el选项上
beforeMount -> mounted
- 此阶段
vm.el完成挂载,vm.$el生成的DOM替换了el选项所对应的DOM
mounted
vm.el已完成DOM的挂载与渲染,此刻打印vm.$el,发现之前的挂载点及内容已被替换成新的DOM
beforeUpdate
- 更新的数据必须是被渲染在模板上的(
el、template、render之一) - 此时
view层还未更新 - 若在
beforeUpdate中再次修改数据,不会再次触发更新方法
updated
- 完成
view层的更新 - 若在
updated中再次修改数据,会再次触发更新方法(beforeUpdate、updated)
beforeDestroy
- 实例被销毁前调用,此时实例属性与方法仍可访问
destroyed
- 完全销毁一个实例。可清理它与其它实例的连接,解绑它的全部指令及事件监听器
- 并不能清除DOM,仅仅销毁实例
(3) 使用场景分析
(4) 数据请求在created和mouted的区别
created是在组件实例一旦创建完成的时候立刻调用,这时候页面dom节点并未生成
mounted是在页面dom节点渲染完毕之后就立刻执行的
触发时机上created是比mounted要更早的
两者相同点:都能拿到实例对象的属性和方法
讨论这个问题本质就是触发的时机,放在mounted请求有可能导致页面闪动(页面dom结构已经生成),但如果在页面加载前完成则不会出现此情况
建议:放在create生命周期当中
21 为什么要在列表组件中写 key,其作用是什么?
key的作用 : key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确, 更快的找到对应的vnode节点
如果使用了key,Vue会根据keys的顺序记录element,曾经拥有了key的element如果不再出现的话,会被直接remove或者destoryed
22 Vue常用的修饰符有哪些?有什么应用场景?
(1) 修饰符
vue中修饰符分为以下五种:
- 表单修饰符
- 事件修饰符
- 鼠标按键修饰符
- 键值修饰符
- v-bind修饰符
(2) 修饰符的作用
(2.1) 表单修饰符
在我们填写表单的时候用得最多的是input标签,指令用得最多的是v-model
关于表单的修饰符有如下:
-
lazy 在我们填完信息,光标离开标签的时候,才会将值赋予给
value,也就是在change事件之后再进行信息同步 v-model.lazy="value" -
trim 自动过滤用户输入的首空格字符,而中间的空格不会过滤 v-model.trim="value"
-
number 自动将用户的输入值转为数值类型,但如果这个值无法被
parseFloat解析,则会返回原来的值 v-model.number="age"
(2.2) 事件修饰符
事件修饰符是对事件捕获以及目标进行了处理,有如下修饰符:
- stop 阻止了事件冒泡,相当于调用了
event.stopPropagation方法 - prevent 阻止了事件的默认行为,相当于调用了
event.preventDefault方法 - self 只当在
event.target是当前元素自身时触发处理函数; 将事件绑定在自身身上,相当于阻止事件冒泡 - once 绑定了事件以后只能触发一次,第二次就不会触发
- capture 使事件触发从包含这个元素的顶层开始往下触发
(2.3) 鼠标按钮修饰符 鼠标按钮修饰符针对的就是左键、右键、中键点击,有如下:
- left 左键点击
- right 右键点击
- middle 中键点击
(2.4) 键盘修饰符
键盘修饰符是用来修饰键盘事件(onkeyup,onkeydown)的
- 普通键(enter、tab、delete、space、esc、up...)
- 系统修饰键(ctrl、alt、meta、shift...)
<input @keyup.enter="submit">
(2.5) v-bind修饰符
v-bind修饰符主要是为属性进行操作,用来分别有如下:
- async
- prop
- camel
async: 能对props进行一个双向绑定
//父组件
<comp :myMessage.sync="bar"></comp>
//子组件
this.$emit('update:myMessage',params);
使用async需要注意以下两点:
- 使用
sync的时候,子组件传递的事件名格式必须为update:value,其中value必须与子组件中props中声明的名称完全一致 - 注意带有
.sync修饰符的v-bind不能和表达式一起使用 - 将
v-bind.sync用在一个字面量的对象上,例如v-bind.sync=”{ title: doc.title }”,是无法正常工作的
props: 设置自定义标签属性,避免暴露数据,防止污染HTML结
<input id="uid" title="title1" value="1" :index.prop="index">
camel : 将命名变为驼峰命名法,如将view-Box属性名转换为 viewBox
<svg :viewBox="viewBox"></svg>
23 跨域是什么?Vue项目中你是如何解决跨域的呢?
(1) 跨域是什么
跨域本质是浏览器基于同源策略的一种安全手段
同源策略(Sameoriginpolicy),是一种约定,它是浏览器最核心也最基本的安全功能
所谓同源(即指在同一个域)具有以下三个相同点
- 协议相同(protocol)
- 主机相同(host)
- 端口相同(port)
反之非同源请求,也就是协议、端口、主机其中一项不相同的时候,这时候就会产生跨域
(2)如何解决 解决跨域的方法有很多,下面列举了三种:
- JSONP
- CORS
- Proxy
而在vue项目中,我们主要针对CORS或Proxy这两种方案进行展开
(3) CORS : 跨域资源共享
CORS 实现起来非常方便,只需要增加一些 HTTP 头,让服务器能声明允许的访问来源
只要后端实现了 CORS,就实现了跨域
(4) Proxy 也称网络代理
代理(Proxy)也称网络代理,是一种特殊的网络服务,允许一个(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。一些网关、路由器等网络设备具备网络代理功能。一般认为代理服务有利于保障网络终端的隐私或安全,防止攻击
如果是通过vue-cli脚手架工具搭建项目,我们可以通过webpack为我们起一个本地服务器作为请求的代理对象。
通过该服务器转发请求至目标服务器,得到结果再转发给前端,但是最终发布上线时如果web应用和接口服务器不在一起仍会跨域
在vue.config.js文件,新增以下代码
amodule.exports = {
devServer: {
host: '127.0.0.1',
port: 8084,
open: true, // vue项目启动时自动打开浏览器\
proxy: {
'/api': { // '/api'是代理标识,用于告诉node,url前面是/api的就是使用代理的
target: "http://xxx.xxx.xx.xx:8080", //目标地址,一般是指后台服务器地址
changeOrigin: true, //是否跨域
pathRewrite: { // pathRewrite 的作用是把实际Request Url中的'/api'用""代替
'^/api': ""
}
}
}
}
}
24 Vue2 与 Vue3 的区别
Vue3 简要就是:
- 利用新的语言特性(es6)
- 解决架构问题
Vue3的新特性:
- 速度更快
- 体积减少
- 更易维护
- 更接近原生
- 更易使用
25 过滤器
过滤器: 就是把一些不必要的东西过滤掉
Vue 允许你自定义过滤器,可被用于一些常见的文本格式化
ps: Vue3中已废弃filter
使用方法:vue中的过滤器可以用在两个地方:双花括号插值和 v-bind 表达式,过滤器应该被添加在 JavaScript表达式的尾部,由“管道”符号指示:
<!-- 在双花括号中 -->
{{ message | capitalize }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>