1.beforeCreate: 初始化实例,this指向创建的实例,不能访问到data、computed、watch、methods上的方法和数据
2.created:实例创建完成,可以访问到data、methods、computed、watch上的数据和方法,未挂载到DOM,不能访问$el属性
3.beforeMount:挂载前被调用,将template编译成render函数
4.mounted:实例挂载到DOM上,可以获取到DOM节点,$ref属性可以访问
5.beforeUpdate:更新之前访问现有的DOM
6.updated:组件数据更新之后
7.beforeDestory:组件实例销毁之前,this仍然可以获取到实例
8.destoryed: 组件实例销毁之后,(关闭页面,播放视频用于记录播放时间 会使用)
// 使用keep-alive额外多出2个生命周期
9.activated: keep-alive缓存的组件激活,(判断id是否相同,不相同就发起请求)
10.deactivated: keep-alive缓存的组件停止调用
如果渲染页面包含父子组件,并且要去子组件数据优先加载,那么父组件中的请求方法要放在mounted中。如果没有父子组件那么在created和mounted都可以。
请求封装在methods中时,beforeCreate 阶段拿不到methods中的方法
beforeCreate、created、beforeMount、mounted
父组件:beforeCreate-->created-->beforeMount-->
子组件:beforeCreate-->created-->beforeMount-->mounted-->
父组件:mounted
因为created中并未挂载DOM,直接输出是拿不到的,需要在异步请求中获取.
<div ref="child"></div> this.$refs.child 获取到dom
1.只要写异步代码就可以获取dom。例如:setTimeout、请求、Promise.xxx()等等
2.使用vue系统内置的this.$nextTick(()=>{})
beforeCreate、created、beforeMount、mounted、activated
加入keep-alive,再次进入页面会执行:
activated
没有加入keep-alive:
beforeCreate、created、beforeMount、mounted
父组件传递给后代
1. 父组件传(v-bind)给子组件接收(props):这种父子传递方便,但是父孙比较麻烦,需要父(v-bind)=>子(props,v-bind)=>孙(props);
子组件不能修改父组件数据,光看不能动
2. 子组件直接使用父组件:this.$parent.xxx;
子组件可以直接修改父组件数据
3. 父组件直接传递给孙组件:依赖(provide)注入(inject)
不方便查找数据来源
后代传递给父组件
1. 子组件传值给父组件:this.$emit('change',value)
2. 父组件直接使用子组件:this.$children[0].xxx; 会把子组件使用个数按数组顺序从0开始排布
3. 父组件直接使用子组件:this.$refs.child.xxx;<Child ref="child"></Child>
兄弟之间传值
1. $emit,$on,全局事件总线,谁的数据需要传递,谁就要触发事件
Vue.prototype.$bus = new Vue()
兄弟A触发事件传递:this.$bus.$emit('change',this.value)
兄弟B直接接收:this.$bus.$on('change',value=>{console.log(value)})
缓存当前组件,重新进入页面无需再次请求数据。
- 匿名插槽:直接用,无法区别
- 具名插槽:区别
- 作用域插槽:传值
//组件涉及知识点:slot、组件通信...
{{ $t('common.confirm') }}
{{ $t('common.cancel') }}
{{formTitle}}
state:全局共享属性
getters:针对state数据进行二次计算
mutations:存放同步方法
actions:存放异步方法,并且是通过提交mutations
modules:把vuex进行模块划分
1. this.$store.state.xxx 可以直接修改vuex的值,既可以使用也可以修改 模板使用{{$store.state.xxx}}
2. 辅助函数:mapState 中间会拷贝一次vuex数据,不能直接修改,只读
...mapGetters(['name','nickname'])
1. this.$store.getters.xxx 两者均不可以赋值修改
2. 辅助函数:mapGetters 两者均不可以赋值修改
通过输入框v-model去绑定getters值,输入值发生变化是会报错的
1. this.$store.state.user.token
2. 辅助函数 mapState:...mapState({token: state => state.user.token})
3. ...mapActions({findAllByCallId: 'base/findAllByCallId',getSiteCode: 'user/getSiteCode',}),
- vuex本身不具备本地存储,修改vuex中数据后,刷新页面数据会恢复到定义值
- 发起请求获取token,将token本地化存储
localStorage.setItem('token',token)
,然后在vuex中的state去优先读取localStorage.getItem('token')
本地存储的token,再退出登录时,移除本地存储localStorage.removeItem('toekn')
销毁本地存储
路由模式:history、hash
区别:
1. 当前页面找不到,history会给后端发送一次请求,而hash不会发送(遇到不存在的乱输网址,history会发送一次请求给后端压力,hash则不会)
2. 表象不同:hash有#,而history有/
3. 项目打包后,前端自测问题:hash是可以看到内容的,而history默认看不到内容需要自己配置项
import VueRouter from 'vue-router'
const routerPush = VueRouter.prototype.push
VueRouter.prototype.push = function(location){
return routerPush.call(this, location).catch(err => err)
}
$router不仅包含当前路由,还包含整个路由的属性和方法
$route包含当前路由对象
1. 全局守卫:beforeEach 路由进入之前;afterEach路由进入之后
2. 路由独享守卫:beforeEnter 路由进入之前,对进入该路由做一些限制 (推荐)
{
path: '/about',name:'about',beforeEnter:function(to, from,next){
if(true){next()}else{next('/login')}
}
}
3. 组件内守卫:beforeRouteEnter 路由进入之前;beforeRouteUpdate 路由更新之前;beforeRouteLeave 路由离开之前;(不推荐)
this.$set(target,key,修改后的值);target:对象或者数组 key:对象的key或数组的下标 目标值
获取更新后的DOM,可以异步的去执行函数,解决一些问题;比如:在created生命周期中获取dom元素,可以使用nextTick异步获取
源码|原理
$nextTick(callback){
return Promise.resolve().then(() => {
callback()
})
}
用来获取dom元素,方便操作节点
用来获取当前组件data数据
获取当前组件的所有子组件
找到当前组件的父组件,找不到返回自身
找到根组件
获取当前组件的根节点
数据定义在return内和外的区别:
1. return外:单独修改这个数据是不可以的,没有被object.definePrototype数据劫持
2. return内:正常数据,可以被修改
computed计算属性的结果值,可以被修改吗? 可以,需要通过get/set写法
computed:{
changeNum(){
return this.num+=10
},
changeStr(){
get(){return this.str.slice(-1)},
set(value){this.str = value}
}
}
watch:{
str(newVal,oldVal){
console.log(newVal,oldVal)
},
obj:{
handle(newVal,oldVal){
console.log('obj',newVal,oldVal)
},
immediate: true, // 开启首次监听
deep:true // 开启深度监听
}
}
computed:有缓存机制,重复调用只执行一次
methods:没有缓存机制,重复调用几次执行几次
v-for是大于v-if vue3中 v-if大于v-for
获取更新后的dom。nextTick返回一个promise,让参数callback在 .then异步行为中去执行
通过Object.defineProperty 劫持数据发生的改变,如果数据发生改变(在set中进行赋值),触发update方法进行更新节点内容{{str}},从而实现数据双向绑定的原理
提升性能; 把dom结构数据化,不去操作dom结构,而是操作数据,再把数据展示到dom
虚拟dom是表示真实dom的js对象
1. 如果新老节点不是同一个节点名称(比如:同div),那么久暴力删除旧的节点,创建插入新的节点
2. 只能同级比较,不能跨层比较,跨层比较会暴力删除新建
注意:如果要性能提升,一定要加入key,key是唯一的标识,在更改前后,确认是不是同一个节点。
diff算法:
1. 采用分层求异的方式,只对比同层级节点,不会跨层级比较,来查找最小变化,目的是尽可能复用老的节点。
2. 在更新时会生成新的虚拟dom,这时候新旧虚拟dom会进行比较,差别化更新。
比较策略为以下:
2.1.如果新旧节点不一样,直接创建一个新标签替换老标签。
2.2.新旧节点一样,文本不一样,新文本替换旧文本。
2.3.新旧节点一样,新的有子节点,旧的没有子节点,直接添加新的子节点。
2.4.新旧节点一样,新的没有子节点,旧的有子节点,直接删除旧的子节点。
2.5.新旧节点一样,而且都有子节点,那么就要比较子节点:
内部会采用首尾指针法进行比较,先比对头,头如果不一样,会进行尾尾比较,如果尾不一样,会进行老头跟新尾比较,如果还不一样,会进行老尾跟新头比较。如果都不一样那么会进行乱序比较
注意:比较这些时候会先判断标签是否一样,再判断key是否一样,判断key的好处在于最大化复用老节点。
1. 去登录完成之后,一般把密码加密一下,服务端返回给我们一个token,前端做一个密码加密,不加密的话服务端拿到的是一个明文,存在风险,加密的方式一般通过MD5直接进行加密,MD5理论上是不可逆的,
2. 拿到token之后做持久化的登录,本地缓存token,使用localStorage缓存一下,然后在vuex里面去获取,通常情况这个token会延迟到凌晨3点再去失效,半夜使用的人比较少,第二天的话如果还在某个页面,那么就会需要重新登入。
3. 之前有没有使用cookie处理token?cookie的话很少用了,他们本质都是用来存储的,但是cookie的话服务端是可以操控它的,这种情况下,与其使用cookie让服务端介入,倒不如直接把token保存在localStorage里面,和服务端尽量从业务上分隔开,这也是前后端分离的核心逻辑。
1. 我们内部把这些权限分为了三类,第一类是功能权限(页面权限),第二类是按钮权限,第三类是接口权限。
2. 用户进行登入之后,服务端返回一个权限的数据,然后通过vue-router里面动态路由的概念addRoutes,把动态路由给插进去,左边的菜单栏就有数据可以渲染,那么所谓的一级菜单,二级菜单只要有树形结构很容易渲染出来。
3. 按钮权限,登入拿到的权限数据就包含了按钮的数据,通过v-if去隐藏
4. 接口权限,一般是和按钮权限配合着用的,大多数服务端去进行处理就可以了。
token过期的话,请求接口服务端会给我们返回401,并且文字提示中包含过期,我们根据这个在请求的响应拦截器中去清除token,跳转到登录页
1. 打包优化:
2. 首屏优化:
3. 懒加载优化:(图片+数据),
scope 的本质是基于 HTML和 CSS属性选择器,分别添加 data-v-xxx属性;
具体来说是通过 vue-loader 实现的,可大致分为三步:
1. 先通过vue-loader解析 vue组件,提取 template、script、style 对应代码块;
2. 然后构造组件实例,在组件实例的选项上绑定scopedId;
3. 最后对style的css代码进行编译转化,应用scopedId生成选择器的属性;
数据的更新是同步的,视图的更新是异步的,因为视图同步更新的话,会造成多次渲染,影响性能。
因为视图是异步更新的,外界可能在更新数据之后,拿到最新的dom元素进行操作,这个时候就可以使用nextTick。vue2中nexttick向下做了兼容,支持的话可以使用 promise.then()、MutationObserver、setImmediate,不支持可以用setTimeout()。vue3则放弃了兼容,直接使用promise.then()。
将所有的钩子函数以字符串的方式放入到数组中,然后合并的时候会将外部存在的钩子重写成函数,采用先进先出的方式来管理钩子函数,当外部调用钩子函数时候,会触发callHooks函数,内容会按顺序依次调用,并且将钩子函数内部的this修改为vue的实例。
当key值发生变化的时候,会调用对应的handle函数。深一点的我不清楚
它具有缓存的功能,当某个数据发生改变时,Object.defineProperty中的get和set就会重新执行,当某个数据没有变化时,会返回上一次计算的值(缓存)
数组和对象的处理是不一样的,对象会进行遍历,将里面每一个值都定义成响应式,而数组是通过重写数组的7个方法来进行响应式处理:
push(最后添加)、pop(最后删除)、unshift(最前面添加)、shift(最前面删除)、reverse(反转)、sort(排序)、splice(删除)
由于Object.defineProperty只对读取跟修改有响应式变化,新增的变化是非响应式的,可以通过 $set 、Vue.set 、数组的话可以使用7个方法
(Vue.set是将set函数绑定在vue的构造函数上,this.$set是将set函数绑定在Vue原型上)
如果目标是一个数组的话,并且你传入key是一个索引,那么内部会做以下事情
修正数组的length,可能进行删除/新增操作
调用数组的splice方法进行更新,splice已经重写
返回value
如果目标是一个对象,是一个新增操作,内部会做以下事情
调用defineReactive函数,内部用object.defineProperty将数据定义成响应式
通过watcher更新页面
返回value
组件中的data写成一个函数,这样每次复用组件的时候,都会返回一份新的data,相当于每个组件实例都有自己私有的数据空间,它们只负责维护各自的数据,不会造成混乱。而写成对象形式,所有组件实例就共用一个data,数据会混乱。
让组件自己记录自己:
1.便于开发工具调试;
2.项目中使用到keep-alive时候,会把缓存组件实例,如果想要刷新进入 可以使用 exclude=‘name’,这样改组件就不会进入缓存;
3.可以被当成递归组件来使用;
一般放在mounted中;有些需要根据业务场景去处理,比如父子组件中需要先加载子组件数据,再去渲染父组件数据,那么这个时候就要把父组件放在mounted中去执行。生命周期父前3子前4父mounted
computed计算属性,依赖其他属性值,并且存在缓存,它依赖的属性值发生改变,才会重新计算computed的值,否则用缓存值。
watch监听,每当监听的数据发生变化时,就会执行回调进行后续操作。
当我们需要在数据变化时,需要执行异步操作,就使用watch,它允许我们执行异步操作。
用来进行双向绑定的,即可以作用表单元素,也可以作用自定义组件,实质是一个语法糖,会生成一个属性和事件。
作用于输入文本框,生成 value属性和input事件
作用域单项/多选框,生成 checked属性和change事件
作用域自定义组件,生成value属性和input事情。
- 首先实现一个监听器(Observer),对数据对象进行遍历,包括子属性对象,利用 Object.defineProperty() 对属性都加上setter和getter 。读取对象某个属性就触发getter,给对象的某个属性赋值的话,就会触发setter,那么就能监听数据变化。
模板会先转换为AST抽象语法树,然后把AST转换为可执行的render函数,最后才生成真实DOM。
发布订阅模式,单例模式,观察者模式,代理模式
m–>model数据, v–>view视图, vm–>ViewModel(vue实例对象,将视图中状态和用户行为分离出,只关心数据和业务的处理)
c–>controller(控制器)
合理设置key值,如果只是渲染,可以用index,如果涉及新增/修改,那么要用唯一标识来作为key。
使用懒加载组件。
合理使用keep-alive组件。
数据层次不能太深,合理设置响应式数据。
v-if和v-show的选择。