实现Vue的v-if/v-show

  • vuev-if 通过注释元素进行占位

文件结构

1
2
3
├── index.html
├── index.js
└── vue.js

index.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
<style>
.box-wrapper {
width: 800px;
height: 200px;
}

.box {
float: left;
width: 200px;
height: 200px;
line-height: 200px;
text-align: center;
color: #fff;
font-size: 50px;
}

.box1 {
background-color: pink;
}

.box2 {
background-color: skyblue;
}

.box3 {
background-color: cornflowerblue;
}

.box4 {
background-color: mediumvioletred;
}
</style>
<body>
<div id="app">
<div class="box-wrapper">
<div class="box box1" v-if='boxShow1'>1</div>
<div class="box box2" v-show='boxShow2'>2</div>
<div class="box box3" v-if='boxShow3'>3</div>
<div class="box box4" v-show='boxShow4'>4</div>
</div>

<button @click='showBox1'>1</button>
<button @click='showBox2'>2</button>
<button @click='showBox3'>3</button>
<button @click='showBox4'>4</button>
</div>
<script type="module" src="./index.js"></script>
</body>

index.js

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
import Vue from './vue.js'

new Vue({
el: "#app",
data: {
boxShow1: true,
boxShow2: false,
boxShow3: false,
boxShow4: true,
},
methods: {
showBox1() {
console.log('click 1');
this.boxShow1 = !this.showBox1
},
showBox2() {
console.log('click 2');
this.boxShow2 = !this.showBox2
},
showBox3() {
console.log('click 3');
this.boxShow3 = !this.showBox3
},
showBox4() {
console.log('click 4');
this.boxShow4 = !this.showBox4
},
}
})

vue.js

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
112
113
114
115
116
117
118
119
120
export default class Vue {

constructor(options) {
const { el, data, methods } = options;
this.el = document.querySelector(el);
this.data = data;
this.methods = methods;
this.showPool = new Map();
this.eventPool = new Map();

this.init()
}

init() {
this.initData()
this.initDom(this.el)
this.initView(this.showPool)
this.initEvent(this.eventPool)
}

initData() {
for (let key in this.data) {
Object.defineProperty(this, key, {
get() {
console.log('#Getter:', this.data[key]);
return this.data[key]
},
set(newValue) {
console.log('#Setter:', key, newValue);
this.data[key] = newValue
this.domChange(key, this.showPool)
}
})
}
}

initDom(el) {
const _childNodes = el.childNodes
if (!_childNodes.length) return false;

_childNodes.forEach(dom => {
if (dom.nodeType === 1) {
const vIf = dom.getAttribute('v-if')
const vShow = dom.getAttribute('v-show')
const vEvent = dom.getAttribute('@click')

if (vIf) {
this.showPool.set(dom, {
type: 'if',
show: this.data[vIf],
data: vIf
})
} else if (vShow) {
this.showPool.set(dom, {
type: 'show',
show: this.data[vShow],
data: vShow
})
}

if (vEvent) {
this.eventPool.set(dom, this.methods[vEvent])
}
}

this.initDom(dom)
})
}

initView(showPool) {
this.domChange(null, showPool)
}

domChange(data, showPool) {
if (!data) {
for (let [k, v] of showPool) {
switch (v.type) {
case 'if':
v.comment = document.createComment('v-if')
!v.show && k.parentNode.replaceChild(v.comment, k)
break;
case 'show':
!v.show && (k.style.display = 'none')
break;
default:
break;
}
}
return
}

for (let [k, v] of showPool) {
if (v.data === data) {
switch (v.type) {
case 'if':
v.show
? k.parentNode.replaceChild(v.comment, k)
: v.comment.parentNode.replaceChild(k, v.comment)
v.show = !v.show
break;
case 'show':
v.show
? (k.style.display = 'none')
: (k.style.display = 'block')
v.show = !v.show
break;
default:
break;
}
}
}
}

initEvent(eventPool) {
for (let [k, v] of eventPool) {
k.addEventListener('click', v.bind(this), false)
}
}

}

仓库地址