Vue的v-if和v-show实现原理

页面表现

  • v-if
    • true: 显示DOM
    • false: 用注释节点占位
    • 注释节点与DOM之间的切换
  • v-show
    • true: 不作处理(用户自己设置的 display
    • false: 行内样式设置**display: none**
    • 行内样式:style or style=”display: none”

实现

思路

  1. 对 data 进行数据劫持
  2. 分析 ast 数,找到元素绑定的 v-if 以及 @methods 事件函数
  3. 分别放进 showPool 和 eventPool 中(数据格式为 Map
    1. showPool 数据格式
    2. key: DOM对象
    3. value: { type: 'if/show', prop: data.xxx }
    4. eventPool 数据格式
    5. key: DOM对象
    6. value: methods.xxx 方法
  4. 绑定事件处理函数
  5. 初次渲染
  6. 如果遇到 v-if ,先给 showPool 中对应 value 加个 comment 注释节点用于后续占位
  7. 用户触发 methods 事件函数
  8. data数据更新
  9. 触发了属性 setter
  10. 进行 update 更新页面

函数

  • initData 数据响应式
    • 参数:
      • vm 实例(获取 vm.$data)
      • showPool(setter 触发,在 showPool 中找对应dom)
  • initPool v-if、v-show、event 放进对应 pool
    • 参数:
      • template(ast树分析模板)
      • methods(找到事件函数)
      • showPool
      • eventPool
  • bindEvent 事件绑定
    • 参数:
      • vm 实例(需要把方法放进实例)
      • eventPool(遍历 eventPool 挂载方法到 实例 上)
  • render 页面渲染
    • 参数:
      • vm 实例(vm.$data 查模板绑定的属性;vm.$el 找到根容器最后把模板 append 进去)
      • showPool(遍历 pool 根据绑定属性的 true、false 初始化页面的显示状态)
      • container(append 进根容器)
  • update 页面更新
    • vm 实例(获取属性值)
    • key(那个属性更新了)
    • showPool(找到相应key的DOM更新)

showPool:

eventPool:

实现代码

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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/**
* showPool Map {dom: {}}
* [
* [
* dom,
* {
* type: if/show
* prop: data下边的对应属性
* }
* ]
* ]
*
* eventPool
*
* [
* [
* dom,
* handler
* ]
* ]
*/

var Vue = (function() {
function Vue(options) {
// el
this.$el = document.querySelector(options.el);
this.$data = options.data();

this._init(this, options.template, options.methods);
}

Vue.prototype._init = function(vm, template, methods) {

var container = document.createElement('div');
container.innerHTML = template;

var showPool = new Map();
var eventPool = new Map();
initData(vm, showPool); // 更新的时候要用到showPool
initPool(container, methods, showPool, eventPool);
bindEvent(vm, eventPool);
render(vm, showPool, container);
}

function initData(vm, showPool) {
var _data = vm.$data;

for (var key in _data) {
(function(key) {
Object.defineProperty(vm, key, {
get: function() {
return _data[key];
},
set: function(newVal) {
// this.isShowImg1 = true;
_data[key] = newVal;
update(vm, key, showPool)
}
});
})(key);
}
}

function initPool(container, methods, showPool, eventPool) {
var _allNodes = container.getElementsByTagName('*');
var dom = null;
for (var i = 0; i < _allNodes.length; i++) {
dom = _allNodes[i];

var vIfData = dom.getAttribute('v-if');
var vShowData = dom.getAttribute('v-show');
var vEvent = dom.getAttribute('@click');

if (vIfData) {
showPool.set(
dom,
{
type: 'if',
prop: vIfData
}
)
dom.removeAttribute('v-if');
} else if (vShowData) {
showPool.set(
dom,
{
type: 'show',
prop: vShowData
}
)
dom.removeAttribute('v-show');
}

if (vEvent) {
eventPool.set(
dom,
methods[vEvent]
)
dom.removeAttribute('@click');
}
}
console.log(eventPool);
}

function bindEvent(vm, eventPool) {
for (var [dom, handler] of eventPool) {
vm[handler.name] = handler;
dom.addEventListener('click', vm[handler.name].bind(vm), false);
}
}

function render(vm, showPool, container) {
var _data = vm.$data;
var _el = vm.$el;

for (var [dom, info] of showPool) {
switch (info.type) {
case 'if':
info.comment = document.createComment('v-if');
// 如果为假,就用 comment 节点替换 dom 节点
// replaceChild(newNode, oldNode);
!_data[info.prop] && dom.parentNode.replaceChild(info.comment, dom);
break;
case 'show':
// 如果为假,就设置display为none
!_data[info.prop] && (dom.style.display = 'none');
break;
default:
break;
}
}

_el.appendChild(container);
}

function update(vm, key, showPool) {
var _data = vm.$data;

for (var [dom, info] of showPool) {
if (info.prop === key) {
switch (info.type) {
case 'if':
!_data[key] ? dom.parentNode.replaceChild(info.comment, dom)
: info.comment.parentNode.replaceChild(dom, info.comment);
break;
case 'show':
!_data[key] ? (dom.style.display = 'none')
: (dom.removeAttribute('style'));
break;
default:
break;
}
}
}
}

return Vue;
})();

export default Vue;