vue修饰符&适用场景
vue修饰符大致分为5类:表单修饰符,事件修饰符,鼠标修饰符,键盘修饰符,v-bind修饰符
路由守卫
全局路由守卫:beforeEach(全局前置守卫),afterEach(全局后置守卫)
作用范围是:所有路由
beforeEach(全局前置守卫)调用:
1、组件初始化时调用;
2、每次路由切换之前调用,A切换至B,B还没有呈现时调用
//全局前置守卫
router.beforeEach((to,from,next) =>{
//第一个参数to,包含的内容是切换后的路由对象,也就是跳转后的路由对象 =》路由B
//第二个参数from,包含的内容的是切换前的路由对象,也就是跳转前的路由对象 =〉路由A
//第三个参数next(),是否往下执行,执行的话,如果不写的话路由就不会跳转,操作将会终止
})
afterEach(全局后置守卫)调用:
1、组件初始化时调用;
2、每次路由切换之后调用,A切换至B,B呈现时调用
应用(设置系统内tab页签)
router.afterEach((to,from) =>{
//第一个参数to,包含的内容是切换后的路由对象,也就是跳转后的路由对象 =》路由B
//第二个参数from,包含的内容的是切换前的路由对象,也就是跳转前的路由对象 =》路由A
}
独享守卫:beforeEnter
进入组件时,被调用
应用:单个组件权限控制
// 该文件专门用于穿件整个应用的路由器
import VueRouter from 'vue-router'
// 创建一个路由器
const router = new VueRouter({ routes:[
{
name:'home',
path:'/home',
meta:{
// 是否授权
isAuth:true,
title:'首页',
permissionKey:'home',
},
component: () => import("@/pages/home"), // 组件路径
beforeEnter:(to,from,next)=>{
const {meta} = to
// 判断是否需要鉴定一下权限
if(meta.isAuth){
if(localStorage.getItem('permissionList' ).includes(meta.permissionKey)){
next()
}else{
alert(`无权限查看${meta.title}`)
}
}else{
next()
}
}
}
]
})
组件内守卫:beforeRouteEnter,beforeRouteLeave
beforeRouteEnter:进入组件内被调用,
beforeRouteLeave:离开组件时,应用:取消组件内一些操作
<template>
<div>
测试组件内守卫
</div>
</template>
<script>
export default {
data() {
return {
name:'这是一个组件'
}
},
beforeRouteEnter(to, from, next) {
next((vm) => {
console.log("thisInBeforeRouteEnterNext", this); // undefined
console.log("vm", vm);
console.log("dataDataInBeforeRouteEnter ", vm.name);
console.log(vm.getName());
});
},
beforeRouteLeave(to, from, next) {
// 离开时
},
mounted(){
},
methods:{
getName(){
return `组件名字${this.name}`
}
},
}
</script>
<style scoped>
</style>
应用:菜单(路由)权限
设置系统内tab页签
// 该文件专门用于穿件整个应用的路由器
import VueRouter from 'vue-router'
import store from '@/store';
// 创建一个路由器
const router = new VueRouter({ routes:[
{
name:'home',
path:'/home',
meta:{
// 是否授权
isAuth:true,
permissionKey:'home',
title:'首页'
},
component: () => import("@/pages/home"), // 组件路径
}
]
})
router.beforeEach((to,from,next)=>{
const {meta} = to
// 判断是否需要鉴定一下权限
if(meta.isAuth){
if(localStorage.getItem('permissionList' ).includes(meta.permissionKey)){
next()
}else{
alert(`无权限查看${meta.title}`)
}
}else{
next()
}
})
router.afterEach((to) => {
store.commit('system/setPageTabs',to); // 把打开页面的放到页面标签数据中
})
export default router
vue2.0的数据双向绑定v-model
MVVM设计思想
- M 数据模型 model 对应data
- V 视图 view 对应模版
- VM 视图模型 vue的实例对象
如何实现响应式:发布订阅模式+双向数据绑定=简单的响应式
原理:采用 数据劫持 结合 发布者-订阅者模式的方式 通过使用 Object.defineProperty()方法,劫持各个属性的 setter,getter, 在数据变动时 发布消息给订阅者,触发相应的监听回调来渲染视图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模拟vue2.0 v-model</title>
</head>
<body>
<h2>双向数据绑定</h2>
<input type="text" id="username">
</br>
显示值:<span id="nameInfo"></span>
<script>
let obj = {}
Object.defineProperty(obj,'username',{
// 劫持 username 属性
get:function(){
console.log('取值');
},
set:function(val){
console.log('设置值');
document.getElementById('nameInfo').innerText = val
}
})
document.getElementById('username').addEventListener('keyup',function(){
console.log(event);
obj.username = event.target.value
})
</script>
</body>
</html>
//订阅.js
// 1.订阅器模型
let Dep = {
clientlist:{},
// 添加订阅
listen:function(key,fn){
// if(!this.clientlist[key]){
// this.clientlist=[]
// }
// this.clientlist[key].push(fn)
// 以上代码的简写
(this.clientlist[key]||(this.clientlist[key]=[])).push(fn)
},
// 推送
trigger:function(){
let key = Array.prototype.shift.call(arguments), // 处理方法参数
fns = this.clientlist[key]
if(!fns || fns.length===0){
// 一个都没有订阅
return false;
}
console.log('arguments',arguments);
for(let i =0,fn; fn = fns[i++];){
fn.apply(this,arguments)
}
}
}
let dataHi = function({data,tag,datakey,selector}){
console.log(data,tag,datakey,selector);
let value = '',
el = document.querySelector(selector)
Object.defineProperty(data,datakey,{
get:function(){
return value
},
set:function(val){
value = val
// 数据变化了!!!发布消息
Dep.trigger(tag,value)
}
})
// 订阅
Dep.listen(tag,function(text){
// 订阅回调
el.innerText = text
})
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue2.0数据双向绑定demo</title>
</head>
<body>
<h3>订阅1</h3>
<span class="one-box"></span>
<h3>订阅2</h3>
<span class="two-box"></span>
<script src='./订阅.js'> </script>
<script>
let dataObj = {}
dataHi({
data:dataObj,
tag:'vue-1',
datakey:'one',
selector:'.one-box'
})
dataHi({
data:dataObj,
tag:'vue-2',
datakey:'two',
selector:'.two-box'
})
dataObj.one='订阅1的内容'
dataObj.two='订阅2的内容'
</script>
</body>
</html>
Vue3.0响应式--Proxy
基本数据类型,依然是get和set实现
复杂数据类型,使用了proxy。proxy也有get和set,但是更聪明。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue3.0响应式--proxy</title>
</head>
<body>
<script>
let obj = {
name:'gyh',
age:18,
}
let p = new Proxy(obj,{
// target 数据源
get(target,propName){
console.log(`读取p的${propName}属性`);
return target[propName]
},
// 修改/新增
set(target,propName,value){
console.log(`修改p的${propName}属性为:${value}`);
target[propName] = value
},
deleteProperty(target,propName){
console.log(`删除p的${propName}属性`);
return delete target[propName]
},
})
</script>
</body>
</html>
Vue2.0和Vue3.0Diff算法区别
Diff算法,把新旧虚拟DOM进行对比,找出变化的放转为DOM的操作
1、事件缓存,比如有点击事件的按钮,onclick会读缓存
2、Vue3添加了静态标记:Vue2 是全量 Diff,Vue3 是静态标记 + 非全量 Diff
静态标记就是判断当前节点是否需要动态更新,比如:div1就是静态的,会加静态标记,在patch过程中,会跳过静态节点对比
<div id="app">
<div id='div1'>沐华</div>
<p id='p1'>{{ age }}</p>
</div>
3、静态提升:静态节点会保存起来,每次更新age,只创建动态内容,复用静态内容
4、使用最长递增子序列优化了对比流程
在 Vue2 里
updateChildren
会进行
- 头和头比
- 尾和尾比
- 头和尾比
- 尾和头比
- 都没有命中的对比
在 Vue3 里
patchKeyedChildren
为
- 头和头比
- 尾和尾比
- 基于最长递增子序列进行移动/添加/删除
比如:老的 children:[ a, b, c, d, e, f, g ]
新的 children:[ a, b, f, c, d, e, h, g ]
1、先进行头和头比,发现不同就结束循环,得到 [ a, b ]
2、再进行尾和尾比,发现不同就结束循环,得到 [ g ]
3、再保存没有比较过的节点 [ f, c, d, e, h ],并通过 newIndexToOldIndexMap 拿到在数组里对应的下标,生成数组 [ 5, 2, 3, 4, -1 ],-1 是老数组里没有的就说明是新增
4、然后再拿取出数组里的最长递增子序列,也就是 [ 2, 3, 4 ] 对应的节点 [ c, d, e ]
5、然后只需要把其他剩余的节点,基于 [ c, d, e ] 的位置进行移动/新增/删除就可以了
使用最长递增子序列可以最大程度的减少 DOM 的移动,达到最少的 DOM 操作,
vue按钮级别的权限,如何控制?
- 常用的v-if
- 封装组件
- 自定义指令
app.directive("auth", {
mounted: (el, binding) => {
const value = binding.value
if (!value) return
if (!hasPermission(value)) {
// 挂载的时候没有权限把元素删除
removeEl(el)
}
},
updated(el, binding) {
// 按钮权限码没有变化,不做处理
if (binding.value === binding.oldValue) return
// 判断用户本次和上次权限状态是否一样,一样也不用做处理
let oldHasPermission = hasPermission(binding.oldValue)
let newHasPermission = hasPermission(binding.value)
if (oldHasPermission === newHasPermission) return
// 如果变成有权限,那么把元素添加回来
if (newHasPermission) {
addEl(el)
} else {
// 如果变成没有权限,则把元素删除
removeEl(el)
}
},
})
const hasPermission = (value) => {
return [1, 2, 3].includes(value)
}
const removeEl = (el) => {
// 在绑定元素上存储父级元素
el._parentNode = el.parentNode
// 在绑定元素上存储一个注释节点
el._placeholderNode = document.createComment("auth")
// 使用注释节点来占位
el.parentNode?.replaceChild(el._placeholderNode, el)
}
const addEl = (el) => {
// 替换掉给自己占位的注释节点
el._parentNode?.replaceChild(el, el._placeholderNode)
}
Vue生命周期
- 首先创建一个vue实例
- Init Events Lifecycle:在beforeCreate之前,对vue的生命周期钩子函数和vue的一些内置事件(如事件修饰符所代表的事件,.once, .enter等),此时数据代理还没有开始
- beforeCreate:此时data和methods都没有初始化,所以无法通过vue实例来访问data的数据和methods中的方法
- Init injections reactivity:在created之前,初始化组件实例的依赖注入(init injections),就是对你在main.js中use的路由和vuex等一些第三方库,以便能够确保在组件创建和使用过程中正确地注入这些依赖项。
- created:该阶段data和methods已经初始化了,可以通过vue实例来访问data的数据和methods中的方法,但整个页面没有挂载到页面上,还无法获取到el的值
- el选项的判断:如果有el选项(options),则继续执行;如果没有,则等待vm.$mounted(el)这个函数被调用,直到调用了这个函数才继续执行。
- template选项的判断:如果有template这个选项,则直接用这个template;如果没有,则去找el这个节点上的整个外部HTNL结构,包含
这个节点。
比如el:'#app'
<div id="app">
<h1>Hello</h1>
<h2>World</h2>
</div>
在判断阶段,此时开始解析模板,在内存中生成虚拟DOM,浏览器页面还不能显示解析好内容
- beforeMounted:此时
- 页面呈现的是未经Vue编译的DOM结构。
- 所有对DOM的操作,最终都不奏效。
- create vm.$el and replace "el" with it:创建vm.$el,然后将内存中的虚拟DOM转为真实DOM插入页面
- mounted:此时,
- 页面中呈现的是经过Vue编译的DOM。
- 对DOM的操作均有效(尽可能避免)。至此初始化过程结束,一般在此进行:开启定时器、发送网络请求、订阅消息、绑定自定义事件、等初始化操作。
- beforeUpdate:此时:数据是新的,但页面是旧的,即:页面尚未和数据保持同步。
- Virtual DOM re-render and patch:根据新数据复集成新的虚拟 DOM,随后与引旧的虚拟DOM进行比较,最终完成页面更新,即:完成了Model View的更新,也就是进行了diff算法。
- updated: 此时:数据是新的,页面也是新的,即:页面和数据保持同步。
- beforeDestroy:此时:vm中所有的:data、methods、指令等等,都做处于可用状态,马上要执行销毁过程,一般在此阶段:关闭定时器、取消订阅消息、解绑自定义事件等收尾操作
- destroyed:当执行到destroyed函数时,组件已经被完全销吸,此时所有的数据、方法、指令、过滤器都不可用
computed
和
watch
有什么区别?
相同点:
- 本质上都是一个watcher实例,它们都通过响应式系统与数据,页面建立通信,只是行为不同
- 计算属性和监听属性对于新值和旧值一样的赋值操作,都不会做任何变化,不过这一点的实现是在响应式系统完成的。
- 它们都是以Vue的依赖追踪机制为基础的
不同点:
- 计算属性具有“懒计算”功能,只有依赖的值变化了,才允许重新计算,成为"缓存",感觉不够准确。
- 在数据更新时,计算属性的dirty状态会立即改变,而监听属性与组件重新渲染,至少会在下一个"Tick"执行。