Vue2小知识回顾 以后应该很多项目都会使用上v3了,但是v2也不能忘掉,毕竟学习过,那么现在就来复习一下
1生命周期 在vue2的生命周期中,created可以访问到data的数据,访问不到dom根节点el,在mounted中可以同时访问到el和data
使用keepalive组件缓存路由后组件后,生命周期会触发activated
2:keepalive vue自带组件,防止重复构建和请求,提升性能,新的数据可以从activated生命周期中获取
3:v-show和v-if v-if完全的删除以及创建dom’盒子
v-show是display:none,会造成回流以及重绘问题,不占位
初次加载v-if好,重复显示与隐藏v-show更好
4:v-if和v-for的优先级 v2中v-for的优先级比v-if高,v3相反
在源码中体现,genElement函数中
5:ref 帮助获 取dom,vue的语法糖,方便快捷获取dom信息
配合nextTick使用
6:nextTick 获取更新后的data和dom内容,异步
7:scope原理 使样式只在当前页面生效,不会全局影响样式
原理:给节点新增自定义属性data-v-xxxxx,利用css属性选择器添加样式
div[data-v-xxxx] {
}
8:样式穿透 对于外来第三方的组件想要修改样式
1:scss:父元素 /deep/ 想要修改样式的组件类名 {
color:red
}
2:stylus:lang=”stylus”
父元素 >>> 想要修改样式的组件类名 {
color:red
}
3:::v-deep
9:父子组件通信 自定义props 10:子传父 自定义事件 this.$emit(event,data)
11:兄弟 eventBus,bus.js
import Vue from vue
export default new Vue
import bus from ‘bus.js’
bus.$emit()
bus.$on()
14:computed,watch,methods computed对比methods:
computed具有缓存,基于响应式的依赖,依赖不变就不会调用,走第一次缓存
methods:无缓存,重复调用
computed对比watch:
watch:监听数据或者路由发生改变才可以响应执行,是当前监听数据发生改变才会执行
computed:无需发生改变,如果影响计算属性值的某个属性改变,就会触发,多对一
15:vue设置代理 看看vue-ccli官网中的devServer配置,解决开发时的跨域问题
publicPath:‘./’,
devServer :{
proxy:需要代理的地址
}
代理在打包完成后是不生效的
可以去xiaoluxian.cn网站去看看打包完后会发生的一些问题
16:打包路径和路由模式 history
hash
直观点的区分就是#
打包完后出现的空白页是怎么回事?
默认打包后引入的js资源等文件是以/开头,要修改成./
publicPath:‘./’
后续会出现点击跳转错误问题,是因为路由模式的原因,将history模式改成hash就没事
如果既想要模式是history,又想看到内容
就需要和后端说一下,注意地址的问题,让他做重定向
17:代理和环境变量 打包完后代理不生效了
需要设置模式和环境变量
生产模式和开发模式
.env.development
.env.production
axios封装时判断请求根路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 axios(options){ let baseUrl = '' if(process.env.VUE_APP_ENV === 'dev'){ baseUrl = options.url }else{ baseUrl = process.env.VUE_APP_BASE_API + options.url } return axios({ url = baseUrl }) }
18:props和data的优先级 在源码的initState
1:props最先
2:之后是methods
3:再然后是data
4:computed
5:watch
19:VueX 专门为vue设计的管理状态的工具
有什么属性?
state类似data
1 2 3 4 5 6 7 8 import {mapState} from vuex computed:{ ...mapState(['name','age']) }
getters类似computed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 getters:{ changeStr(){ return state.name + state.age } } import { mapGetters } from vuex computed:{ ...mapGetters(['changeStr']) }
mutations类似methods
1 2 3 4 import { mapMutations } from vuex methods:{ ...mapMutations(['changeSome']) }
actions类似异步的methods,提交的是mutaions。而不是直接修改state
1 2 3 4 5 6 7 8 9 actions: { submit({commit},state){ } } impot { mapActions } from vuex methods:{ ...mapActions(['submit']) }
modules吧以上四个属性再细分,模块化仓库更好管理
vuex是单项数据流,单相思
vuex持久化存储
本身不是持久化存储
1 2 3 4 5 6 7 8 9 方式1 state:{ num:localStorage.getItem('num') || 1 }, add(){ state.num++ localStorage.setItem('num',state.num) } 方式2 使用插件 自行百度vuex持久化插件
20:vue-router 路由 路由模式
hash
history
区别:
history在跳转无效路由时,会发送一个请求 /about/id
打包后前端自测需要使用hash,如果使用history会出现空白页
21:SPA SPA单页面应用
缺点是SEO优化不好,SEO多页面更好
性能不是特别好,所有页面都牵扯到一个页面,可能会涉及到重绘和重排
22:路由路径传值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 显式 this.router.push({ path:'/about', query:{ a:1 } }) /about?a=1 this.$route.query 隐式 this.router.push({ name:'About', params:{ a:1 } }) /about this.$route.params
23:路由导航守卫 具体自行百度 全局
路由独享
组件内
使用场景较多的是,在进入页面时,判断有无登陆,无登陆就跳转登录页,有登陆就进行后续操作
24:动态路由 使用场景:详情页
/detail/1
/detail/2
path:’/detail/:id’
25:源码 模板编译 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 haovue.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <div>{{str}}</div> {{ str }} </div> <script src="./haovue.js"></script> <script> new HaoVue({ el:'#app', data:{ str:'hello hao' } }) </script> </body> </html>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 haovue.js class HaoVue { constructor(options){ this.$el = document.querySelector(options.el) this.$data = options.data this.compile(this.$el) } compile(node){ node.childNodes.forEach(item => { // 判断节点类型 元素节点是1 文本节点是3 如果有{ { } }就替换 if(item.nodeType == 1){ this.compile(item) } if(item.nodeType == 3){ let reg = /\{\{(.*?)\}\}/g; let text = item.textContent; item.textContent = text.replace(reg,(match,vmKey)=>{ vmKey = vmKey.trim() return this.$data[vmKey] }) } }) } }
26:源码 生命周期 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 haovue.js class HaoVue { constructor(options){ console.log(options) // 需要改变this指向才能访问el和data 因为此时声明周期在组件的实例中 if(typeof options.beforeCreate === 'function'){ options.beforeCreate.bind(this)() console.log(this.$data,this.$el) } this.$data = options.data if(typeof options.created === 'function'){ options.created.bind(this)() console.log(this.$data,this.$el) } if(typeof options.beforeMount === 'function'){ options.beforeMount.bind(this)() console.log(this.$data,this.$el) } this.$el = document.querySelector(options.el) if(typeof options.mounted === 'function'){ options.mounted.bind(this)() console.log(this.$data,this.$el) } this.compile(this.$el) } compile(node){ node.childNodes.forEach(item => { // 判断节点类型 元素节点是1 文本节点是3 如果有{ { } }就替换 if(item.nodeType == 1){ this.compile(item) } if(item.nodeType == 3){ let reg = /\{\{(.*?)\}\}/g; let text = item.textContent; item.textContent = text.replace(reg,(match,vmKey)=>{ vmKey = vmKey.trim() return this.$data[vmKey] }) } }) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 haovue.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <div>{{str}}</div> {{ str }} </div> <script src="./haovue.js"></script> <script> new HaoVue({ el:'#app', data:{ str:'hello hao' }, beforeCreate(){ console.log('beforeCreate') }, created(){ console.log('created') }, beforeMount(){ console.log('beforeMount') }, mounted(){ console.log('mounted') } }) </script> </body> </html>
27:源码 添加事件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <div>{{str}}</div> {{ str }} <button @click="btnClick">点击事件</button> </div> <script src="./haovue.js"></script> <script> new HaoVue({ el:'#app', data:{ str:'hello hao' }, beforeCreate(){ console.log('beforeCreate') }, created(){ console.log('created') }, beforeMount(){ console.log('beforeMount') }, mounted(){ console.log('mounted') }, methods:{ btnClick(){ alert(this.str + '按钮点击') // 此时无法直接this.str使用 得放到数据劫持中 } } }) </script> </body> </html>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class HaoVue { constructor(options){ this.$options = options // 需要改变this指向才能访问el和data 因为此时声明周期在组件的实例中 if(typeof options.beforeCreate === 'function'){ options.beforeCreate.bind(this)() } this.$data = options.data if(typeof options.created === 'function'){ options.created.bind(this)() } if(typeof options.beforeMount === 'function'){ options.beforeMount.bind(this)( } this.$el = document.querySelector(options.el) if(typeof options.mounted === 'function'){ options.mounted.bind(this)() } this.compile(this.$el) } compile(node){ node.childNodes.forEach(item => { // 判断节点类型 元素节点是1 文本节点是3 如果有{ { } }就替换 if(item.nodeType == 1){ // 判断是否包含属性 if(item.hasAttribute('@click')){ let vmKey = item.getAttribute('@click').trim() item.addEventListener('click',(event)=>{ this.eventFn = this.$options.methods[vmKey].bind(this) this.eventFn(event) }) } if(item.childNodes.length > 0){ this.compile(item) } } if(item.nodeType == 3){ let reg = /\{\{(.*?)\}\}/g; let text = item.textContent; item.textContent = text.replace(reg,(match,vmKey)=>{ vmKey = vmKey.trim() return this.$data[vmKey] }) } }) } }
28:源码 劫持
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 haovue.js this.$data = options.data this.proxyData() proxyData(){ for(let key in this.$data){ Object.defineProperty(this,key,{ get(){ return this.$data[key] }, set(value){ this.$data[key] = value } }) // 此时虽然数据被修改,但是视图没修改 } }
29:更新视图 1 2 3 4 5 haovue.html btnClick(e){ this.str = '一点都不好' console.log(this) // 此时无法直接this.str使用 得放到数据劫持中 }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 haovue.js class HaoVue { constructor(options){ this.$options = options this.$watchEvent = {} // 需要改变this指向才能访问el和data 因为此时声明周期在组件的实例中 if(typeof options.beforeCreate === 'function'){ options.beforeCreate.bind(this)() } this.$data = options.data this.proxyData() this.observe() if(typeof options.created === 'function'){ options.created.bind(this)() } if(typeof options.beforeMount === 'function'){ options.beforeMount.bind(this)() } this.$el = document.querySelector(options.el) if(typeof options.mounted === 'function'){ options.mounted.bind(this)() } this.compile(this.$el) } // 将data中的数据挂载在原型上 使得页面通过this.xxx的方式能够访问到 // 并且进行数据劫持 当data中的数据更新 原型上的数据也要更新 双向绑定 劫持 proxyData(){ for(let key in this.$data){ Object.defineProperty(this,key,{ get(){ return this.$data[key] }, set(value){ this.$data[key] = value } }) } } // 此时虽然数据被修改,但是视图没修改 observe(){ for(let key in this.$data){ let value = this.$data[key] let that = this Object.defineProperty(this.$data,key,{ get(){ return value }, set(val){ value = val if(that.$watchEvent[key]){ that.$watchEvent[key].forEach(item=>{ item.update() }) } } }) } } // 编译 compile(node){ node.childNodes.forEach(item => { // 判断节点类型 元素节点是1 文本节点是3 如果有{ { } }就替换 if(item.nodeType == 1){ // 判断是否包含属性 if(item.hasAttribute('@click')){ let vmKey = item.getAttribute('@click').trim() item.addEventListener('click',(event)=>{ this.eventFn = this.$options.methods[vmKey].bind(this) this.eventFn(event) }) } if(item.childNodes.length > 0){ this.compile(item) } } if(item.nodeType == 3){ let reg = /\{\{(.*?)\}\}/g; let text = item.textContent; item.textContent = text.replace(reg,(match,vmKey)=>{ vmKey = vmKey.trim() if(this.hasOwnProperty(vmKey)){ let watcher = new Watch(this,vmKey,item,'textContent') if(this.$watchEvent[vmKey]){ this.$watchEvent[vmKey].push(watcher) }else{ this.$watchEvent[vmKey] = [] this.$watchEvent[vmKey].push(watcher) } } return this.$data[vmKey] }) } }) } } class Watch { constructor(vm,key,node,attr){ // 对象 this.vm = vm // 属性名称 this.key = key // 节点 this.node = node // 改变文本节点的内容字符串 this.attr = attr } // 执行改变update操作 update(){ this.node[this.attr] = this.vm[this.key] } }
30:diff算法 算法,提升性能,vue和react都有使用和改良,与dom有关系,虚拟dom,数据,将dom数据化
不断的修改dom是十分耗费性能的,因此将dom转换为数据
vue和react中的diff算法主要借鉴了snabbdom和virtual-dom
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 搭建snabbdom环境 npm init -y npm install webpack@5 webpack-cli@3 webpack-dev-server@3 -S npm install snabbdom -S 创建一个webpack.config.js 配置 module.exports = { entry:{ index:"./src/index.js" }, output:{ path:__dirname + "/public", filename:"./js/[name].js" }, devServer:{ contentBase:"./public", inline:true } } 修改package.json配置 "scripts": { "dev": "webpack-dev-server" }, npm run dev
一个简单的snabbdom环境就搭建好了
31:v-model 首选通过defineProperty劫持数据发生的改变,如果数据改变了就触发set的update,进行更新节点内容,从而实现双向绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 compile(node){ node.childNodes.forEach(item => { // 判断节点类型 元素节点是1 文本节点是3 如果有{ { } }就替换 if(item.nodeType == 1){ // 判断是否包含属性 if(item.hasAttribute('@click')){ let vmKey = item.getAttribute('@click').trim() item.addEventListener('click',(event)=>{ this.eventFn = this.$options.methods[vmKey].bind(this) this.eventFn(event) }) } // 判断元素节点是否添加了v-model if(item.hasAttribute('v-model')){ let vmKey = item.getAttribute('v-model').trim() if(this.hasOwnProperty(vmKey)){ item.value = this[vmKey] } item.addEventListener('input',(event)=>{ this[vmKey] = item.value }) } if(item.childNodes.length > 0){ this.compile(item) } } if(item.nodeType == 3){ let reg = /\{\{(.*?)\}\}/g; let text = item.textContent; item.textContent = text.replace(reg,(match,vmKey)=>{ vmKey = vmKey.trim() if(this.hasOwnProperty(vmKey)){ let watcher = new Watch(this,vmKey,item,'textContent') if(this.$watchEvent[vmKey]){ this.$watchEvent[vmKey].push(watcher) }else{ this.$watchEvent[vmKey] = [] this.$watchEvent[vmKey].push(watcher) } } return this.$data[vmKey] }) } }) }
32:snabbdom 虚拟节点
1 2 3 4 5 6 7 8 { children:undefined, data:{}, elm:h1, key:undefined, sel:'h1', text:'你好h1' }
真实节点
新老节点替换规则
如果新老节点不是同一个节点名称,那么久脑力删除旧的,创建插入新的节点
判断是否是同一节点是通过key判断的
添加key能够提升性能 key是唯一标识
只能同级比较,跨层直接暴力删除
如果是相同节点 情况更复杂,两种
新节点没有children,证明是文本节点,直接覆盖
新节点有children,分为两种
新的有children,旧的也有children,diff核心,永远都是从旧节点第一个child开始匹配
旧前,新前
旧节点和新节点的第一个相等,那么旧节点和新节点指针相加
旧后,新后
旧节点和新节点的最后一个相等,那么旧节点和新节点指针都–
旧前,新后
旧节点第一个和新节点的最后一个相等,那么旧节点指针加加和新节点指针–
旧后,新前
旧节点最后一个一个和新节点的最后一个相等,那么旧节点指针加加和新节点指针–
以上都不满足
创建和删除
新的有children,旧的没有,创建元素添加,吧旧的内容添加创建新的插入
33:手写diff算法,生成虚拟dom 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 h.js import vnode from './vnode' export default function(sel,data,params){ if(typeof params === 'string'){ // h的第三个参数是字符串类型 意味着他没有子元素 return vnode(sel,data,undefined,params,undefined) }else if(Array.isArray(params)){ let child = [] for(let item of params){ child.push(item) } return vnode(sel,data,child,undefined,undefined) } } vnode.js export default function(sel,data,children,text,ele){ return { sel, data, children, text, ele } } index.js import h from './dom/h' // let vnode = h('div',{},'hello') let vnode = h('div',{},[ h('h1',{},'ni'), h('h1',{},'ni2'), h('h1',{},'ni3'), ]) console.log(vnode)
34:手写diff算法,patch不是同一节点
35:手写diff算法,相同节点有无children 上
36:手写diff算法,相同节点有无children 中 37:手写diff算法,相同节点有无children 下 38:MVVM 出现的原因:
web1.0时代,前后端代码都在一起
前后端代码都是一个人开发,技术没有侧重点,责任不够细分
项目不好维护
mvc都是后端先出的,htmlcssjs页面没有,后端无法工作
web2.0时代
ajax出现了,前端后端数据分离
解决问题,后端不用等前端页面弄完,后端做后端,前端做前端,
单页面,html和css,js都在一个页面,单个页面也会出现不好维护的情况,但是比1.0好
出现前端mvvm框架,前端出现过mvc框架,但是被mvvm干掉了
解决问题,吧大页面进行拆分,单个组件进行维护
什么是mvvm
model,view,view model的缩写
view,视图,页面展现的内容
model,数据模型,数据层,data
view model 视图模型层 就是vue源码将视图和数据结合的实现