爱客仕-前端团队博客园

实现简单的数据绑定

近年来前端框架日新月异,react、angular、vue无疑是当中最火的三者
他们都不约而同的提供了双向绑定数据的解决办法

什么是双向绑定?简单说就是开发者不用再去关注数据变化引起的ui变化或者ui操作引起的数据变化,让开发者更专注于逻辑代码的开发
这对于开发者来说实在是太方便了
对于他内部是怎么实现的,一直停留在vue文档的深入响应式原理的这么一句话:

把一个普通对象传给 Vue 实例作为它的 data 选项,Vue.js 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。这是 ES5 特性,不能打补丁实现,这便是为什么 Vue.js 不支持 IE8 及更低版本。

这次决定弄个明白

双向绑定的方式大致上分两种:

  • 一种是以angular为代表的脏检查
  • 另一种是vue使用的Object.defineProperty

下面我们尝试在摆脱vue但参考vue的前提下实现一个简单的双向绑定:

首先需要最基础的两个dom元素,一个input标签跟一个p标签
同时为了让数据能够跟ui绑定,我们分别在input跟p上加上自定义属性v-model与v-text
看起来大致跟vue的官网demo一样:

1
2
<input v-model="value" type="text" id="input">
<p v-text="value" id="output"></p>

获取input跟p元素,定义初始data:

1
2
3
4
5
6
const input = document.getElementById('input')
const output = document.getElementById('output')
let data = {
value: 'hello world!'
}

触发数据同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function init () {
// 遍历input标签跟p标签的所有属性,当匹配到我们约定好的'v-'开头的自定义属性时,执行相对应操作
let regexp = /^v-/
for( let dom of [input, output] ) {
for( let attr of dom.attributes ) {
if( attr.nodeName.match(regexp) ) {
switch (attr.nodeName.split('-')[1]) {
case 'model':
dom.value = data.value
break
case 'text':
dom.innerHTML = data.value
break
}
}
}
}
}
// 同步初始数据
init()

接下来轮到Object.defineProperty登场
mdn是这么介绍他的:

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象。

在这里我们通过Object.defineProperty来修改data对象的value属性,劫持该属性的getter-setter函数来处理当访问或修改value时候调用我们绑定的函数

1
2
3
4
5
6
7
8
9
10
11
Object.defineProperty(data, 'value', {
// 访问value时执行
get: function() {
return data.val
},
// 修改value时执行
set: function(newValue) {
data.val = newValue
init()
}
})

最后我们在input上注册一个事件监听器,每次input的时候同步input.value到data.value

1
2
3
input.addEventListener('input', function(e) {
data.value = e.target.value
}, false)

所有代码如下: