利用原生JS实现的v-model

如何获取页面滑动高度?使用window.pageYOffset或者document.documentElement.scrollTop

1
2
3
let obj = { a: 1, b: 2 };
let { a: a1 } = obj;
a1 === 1; // true

重点:实现事件发布订阅模式+实现数据代理

1.先介绍怎么利用Proxy实现数据代理功能

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
<b id="count"></b>
<button id="inc">加一</button>
<button id="dec">减一</button>

</script type="text/javascript">
window.addEventListener('DOMContentLoaded', () => {
let count = document.getElementById('count');
let inc = document.getElementById('inc');
let dec = document.getElementById('dec');

let source = { count: 1 };

let render = value => {
count.innerHTML = value;
}

let options = {
set: function (target, property, value) {
target[property] = value;
render(value);
}
};

let proxy = new Proxy(source, options);

inc.addEventListener('click', () => {
proxy.count++;
});

dec.addEventListener('click', () => {
proxy.count--;
});
});
</script>

如上所示,我们使用了Proxy来代理source对象,所有作用于Proxy实例上的变化都会同步转发到被代理的source的对象上面。

2.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
class Event {
handlers = {};

/** 订阅事件 */
addEventListener(type, handler) {
if (!(type in this.handlers)) {
this.handlers[type] = [];
}
this.handlers[type].push(handler);
}

/** 发布事件 */
dispatchEvent(type, ...params) {
if (!(type in this.handlers)) {
return new Error('还没有注册该事件');
}
this.handlers[type].forEach(handler => {
handler(...params);
});
}

/** 移除事件监听器 */
removeEvent(type, handler) {
if (!(type in this.handlers)) {
return new Error('还没有注册该事件');
}
if (!handler) {
delete this.handlers[type];
return;
}
let idx = this.handlers[type].findIndex(ele == handler);
this.handlers[type].splice(idx, 1);
if (this.handlers[type].length <= 0) {
delete this.handlers[type];
}
}
}

let event = new Event();
let load = params => { console.log('load', params); }
let load2 = params => { console.log('load2', params); }
event.addEventListener('load', load);
event.addEventListener('load', load2);
event.dispatchEvent('load', 'params is this'); // load params is this 换行 load2 params is this

3.代码实现

正题介绍,如何实现一个简易的vue,实现类似v-model的功能。

分析:获取需要进行监听的属性,对这几个属性进行代理。一旦发生变化,那么通知变化处理方法。

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
<body>
<div id="app">
<input id="input" v-model="input" />
</div>
<script>

class Event {
handlers = [];

addEventListener = (type, handler) => {
if(!(type in this.handlers)) {
this.handlers[type] = [];
}
this.handlers[type].push(handler);
}

dispatchEvent = type => {
if (!(type in this.handlers)) {
return new Error('未注册该类型的事件');
}
this.handlers[type].map(item => {
item.updateUI();
})
}
}

class UIHandler {
constructor(dom, property, attr, proxyObj) {
this.dom = dom;
this.property = property;
this.attr = attr;
this.proxyObj = proxyObj;
}

updateUI = function() {
this.dom[this.attr] = this.proxyObj[this.property]+'??';
}
}

class MyVue {
constructor(elementId, data) {
this.event = new Event();
this.$data = new Proxy(data, this.proxyOption);
this.init(elementId);
}

proxyOption = {
get: (target, property) => {
return target[property];
},
set: (target, property, value) => {
target[property] = value;
this.event.dispatchEvent(property);
return true;
}
};

init = function(elementId) {
let root = document.getElementById(elementId);
let childs = Array.from(root.children);
childs.map(dom => {
if(dom.hasAttribute('v-model')) {
this.vModelHandler(dom);
}
})
}

/** 对于v-model来说,要实现的是输入实时改变UI,同时同步更新代理数据 */
vModelHandler = function(dom) {
let property = dom.getAttribute('v-model');
let watcher = new UIHandler(dom, property, 'value', this.$data);
this.event.addEventListener(property, watcher);
dom.addEventListener('input', () => {
this.$data[property] = dom.value;
}); // 同步数据处理
}
}

window.addEventListener('DOMContentLoaded', () => {
new MyVue(
'app',
{
input: 'aha'
}
);
});
</script>
</body>