Contents
  1. 1. 实现原理
  2. 2. 代码
    1. 2.1. HTML部分
    2. 2.2. js部分
  3. 3. 一点改进

双向绑定是现在很多MVVM框架都要实现的功能,比如Vue.js

vue的核心就是一个响应的数据绑定系统:当数据改变时,DOM自动改变,DOM内容改变,底层数据也会相应的更新。

本文就借助Object.defineProperty()来定义了两个指令x-valuex-text,以简单模拟Vue中使用指令实现双向绑定的场景。

最终效果如下:

实现原理

model --> view的绑定,借助了Object.defineProperty()方法,它可以定义访问器属性,并且通过getter和setter函数对数据的读写进行监听

view --> model的绑定,实质是通过监听DOM的keyup、change等事件,通过事件处理函数来更新底层数据

代码

下面来看代码实现:

HTML部分

1
2
3
4
5
6
7
8
9
<!--其中使用了两个指令,x-value 和 x-text-->
<form autocomplete="off">
<span>姓名:</span><input type="text" x-value="name"/>
<p x-text="name"></p>
<span>年龄:</span><input type="number" x-value="age"/>
<p x-text="age"></p>
<span>学校:</span><input type="text" x-value="school"/>
<p x-text="school"></p>
</form>

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
'use strict';
// 基本数据对象
var data = {
_name: '',
_age: 0,
_school: ''
};
/*
* model --> view 的绑定
* 基本思想:使用defineProperty()的getter和setter
* 当model发生改变时,在setter函数中触发更新DOM的函数
*/
// 将对象的某个属性封装为访问器属性
var defineGetAndSet = (obj, prop) => {
Object.defineProperty(obj, prop, {
get: () => {
return obj['_' + prop];
},
set: (newValue) => {
obj['_' + prop] = newValue;
// 触发DOM更新
render(prop, newValue);
}
});
};
// 针对不同指令,进行不同DOM的更新操作
var directives = {
'x-value': function (newValue) {
this.setAttribute('value', newValue);
},
'x-text': function (newValue) {
this.innerHTML = newValue;
}
};
// 依据新数据,来触发DOM更新
var els = [document.querySelectorAll('[x-value]'), document.querySelectorAll('[x-text]')];
var render = (prop, newValue) => {
els.forEach((item) => {
for (let i = 0, len1 = item.length; i < len1; i ++) {
let attrs = item[i].attributes;
for (let j = 0, len2 = attrs.length; j < len2; j ++) {
if (attrs[j].nodeName.indexOf('x-') !== -1 && attrs[j].nodeValue === prop) {
directives[attrs[j].nodeName].call(item[i], newValue);
}
}
}
});
};
// 定义三个访问器属性
defineGetAndSet(data, 'name');
defineGetAndSet(data, 'age');
defineGetAndSet(data, 'school');
// 初始化属性值
data.name = 'xwj';
data.age = 20;
data.school = 'ouc';
/*
* view --> model的绑定
* 基本思想:监听表单的keyup、change等事件
* 在事件处理函数中将值传给model
*/
// 定义事件处理函数
var updateData = (event) => {
if (event.target.hasAttribute('x-value')) {
var prop = event.target.getAttribute('x-value');
data[prop] = event.target.value;
}
}
// 添加事件监听
document.addEventListener('keyup', updateData);
document.addEventListener('change', updateData);

一点改进

以上代码,虽然实现了基本的双向绑定,但是,还有一些改进之处。

我们可以把Object.defineProperty()观察者模式结合起来,当访问器属性中的stter监听到数据变化时,就通过发布者将新的数据发布出去,而DOM作为订阅者,就可以直接作出相应的改变

Contents
  1. 1. 实现原理
  2. 2. 代码
    1. 2.1. HTML部分
    2. 2.2. js部分
  3. 3. 一点改进