四种方法实现数据双向绑定


前言

在一些前端框架中,例如 angularvue都有数据双向数据绑定的功能,这个功能极大的方便我们操作数据。那么接下来我会讲解一下双向数据绑定的4种实现方式。

方式

1.手动触发绑定

手动触发绑定的主要思路是通过在数据对象定义 get 和 set 方法(可以使用其他的命名方法),调用时手动去触发 get 和 set 方法去获取数据,修改数据,改变数据后会主动去触发 get 和 set 函数中视图层的重新渲染。

简单的手动触发绑定代码如下:

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>手动双向绑定</title>
</head>
<body>
<input type="text" id="input" s-value="value">
<span id="el" s-text="value"></span>
<script>
let elems = [document.getElementById('el'), document.getElementById('input')];
let data = {
value: ''
}
let directive = {
text: function (text) {
this.innerHTML = text
},
value: function (value) {
this.setAttribute('value', value)
}
}
// 监听 input 的 keyup 事件
elems[1].addEventListener('keyup', function (e) {
set('value', e.target.value)
})
function scan() {
for (let elem of elems) {
for (let attr of elem.attributes) {
if (attr.nodeName.indexOf('s-') !== -1) {
// 调用属性指令
directive[attr.nodeName.slice(2)].call(elem, data[attr.nodeValue])
}
}
}
}
function set(key, value) {
data[key] = value;
scan();
}
</script>
</body>
</html>

2.数据劫持

数据劫持的基本思路是使用 Object.defineProperty 对 ViewModel 数据对象进行 get 和 set 的监听,当有数据变动的时候扫描元素节点,然后去运行对应节点上的指令(directive)。

代码如下:

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>数据劫持</title>
</head>
<body>
<input type="text" id="input" s-value="value">
<span id="el" s-text="value"></span>
<script>
let elems = [document.getElementById('el'), document.getElementById('input')];
let data = {
value: ''
}
let directive = {
text: function (text) {
this.innerHTML = text
},
value: function (value) {
this.setAttribute('value', value)
}
}
let value;
defineGetAndSet(data, 'value')
// 监听 input 的 keyup 事件
elems[1].addEventListener('keyup', function (e) {
data.value = e.target.value;
})
function scan() {
for (let elem of elems) {
for (let attr of elem.attributes) {
if (attr.nodeName.indexOf('s-') !== -1) {
// 调用属性指令
directive[attr.nodeName.slice(2)].call(elem, data[attr.nodeValue])
}
}
}
}
function defineGetAndSet(obj, attrName) {
Object.defineProperty(obj, attrName, {
get: function () {
return value
},
set: function (newValue) {
value = newValue;
scan()
},
configurable: true,
enumerable: true
})
}
</script>
</body>
</html>

3.使用 es6的 Proxy

利用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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>使用 proxy 进行数据双向绑定</title>
</head>
<body>
<input type="text" id="input" s-value="value">
<span id="el" s-text="value"></span>
<script>
let elems = [document.getElementById('el'), document.getElementById('input')];
let directive = {
text: function (text) {
this.innerHTML = text
},
value: function (value) {
this.setAttribute('value', value)
}
}
// ------------- 看下面 -------------
let data = new Proxy({}, {
get: function (target, key, receiver) {
return target.value
},
set(target, key, value, receiver) {
target.value = value;
scan();
}
})
// ------------- 看上面 -------------
// 监听 input 的 keyup 事件
elems[1].addEventListener('keyup', function (e) {
data.value = e.target.value;
})
function scan() {
for (let elem of elems) {
for (let attr of elem.attributes) {
if (attr.nodeName.indexOf('s-') !== -1) {
// 调用属性指令
directive[attr.nodeName.slice(2)].call(elem, data[attr.nodeValue])
}
}
}
}
</script>
</body>
</html>

4. 脏检查

脏检查的基本原理是在 ViewModel 对象的某个属性值发生变化的时候找到与这个属性值相关的所有元素,然后去比较数据变化,如果变化就用 directive 指令调用,对这个元素进行重新渲染。

简单的脏检查代码如下:

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>脏检查</title>
</head>
<body>
<input type="text" id="input" s-bind="value" s-event="value">
<span id="el" s-event="text" s-bind="value"></span>
<script>
let elems = [document.getElementById('el'), document.getElementById('input')];
let directives = {
text: function (text) {
this.innerHTML = text
},
value: function (value) {
this.setAttribute('value', value)
}
}
let data = {
value: ''
}
// 扫描元素,使每个元素的 directive 数组为空
scan(elems)
// 监听 input 的 keyup 事件
elems[1].addEventListener('keyup', function (e) {
data.value = e.target.value;
startDirtyCheck(e.target.getAttribute('s-bind'))
})
function scan() {
for (let elem of elems) {
elem.directive = []
}
}
// 开启脏检查
function startDirtyCheck(value) {
let list = document.querySelectorAll('[s-bind=' + value + ']')
dirtyCheck(list)
}
function dirtyCheck(elems) {
// 扫描带指令的节点属性
for (let i = 0, len = elems.length; i < len; i++) {
let elem = elems[i];
for (let j = 0, len1 = elem.attributes.length; j < len1; j++) {
let attr = elem.attributes[j];
if (attr.nodeName.indexOf('s-event') !== -1) {
let dataKey = elem.getAttribute('s-bind')
// 进行脏数据检查,如果数据改变,重新执行指令
if (elem.directive[attr.nodeValue] !== data[dataKey]) {
directives[attr.nodeValue].call(elem, data[dataKey])
elem.directive[attr.nodeValue] = data[dataKey]
}
}
}
}
}
</script>
</body>
</html>
-------------本文结束感谢您的阅读-------------

本文标题:四种方法实现数据双向绑定

文章作者:shenzekun

发布时间:2018年05月10日 - 11:05

最后更新:2018年05月10日 - 14:05

原始链接:http://www.shenzekun.cn/四种方法实现数据双向绑定.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

您的支持将鼓励我继续创作!