vue2知识回顾

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
haovue.html
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'
}

真实节点

1
<h1>你好h1</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不是同一节点

1

35:手写diff算法,相同节点有无children 上

1

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源码将视图和数据结合的实现