最近看了一下 Vue.js 的实现相关的文章,了解到了其数据劫持(双向绑定)的原理,使用到了Object.defineProperty这个方法,花了点时间,自己尝试着做了一个小 demo。
MDN 解释:Object.defineProperty方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 使用Object.defineProperty()
// 接收的第一个参数为对象,第二个参数为属性名,第三个参数为配置对象
let obj = {}
Object.defineProperty(obj, 'name', {
enumerable: true, // 是否可枚举,默认值 false,如果为false,在for in遍历里不会展示属性
writable: true, // 是否可写,默认值 false,如果为false的话,给name赋值,不会生效
configurable: true, // 是否可配置(是否可删除),默认值 false
value: 'huqing' // name对应的值
})

// 上面的写法其实和下面的写法是一样的
let obj = {}
obj.name = 'huqing'

既然写法一样,为何要大费周折去写那么多呢,其实重点在其getset方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 注意!当使用get set时,不能写value和writable
let obj = {}
let str
Object.defineProperty(obj, 'name', {
enumerable: true,
configurable: true,
get() {
// 读,当我们读取属性时,会执行get方法
return str // 当我们obj.name进行读取时,会返回'huqing'
},
set(newValue) {
// 写,当我们写入属性时,会执行set方法
str = newValue
}
})

obj.name = 'huqing' // 写入
console.log(obj.name) // 'huqing' // 读取

从这可以看出,我们可以在 get、set 函数中,写出对应的业务逻辑,做一系列我们想做的事情

话不多说,开始做 demo

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
<!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 id="input" value="">
<script>
let el = document.getElementById('input')
let obj = { // 1. 创建一个对象
name: ""
}

function oberseve(obj) { // 2. 对对象进行观察
if (typeof obj !== 'object') return // 2.1 判断参数是否为对象
for (let key in obj) { // 2.2 对对象进行遍历,目的是为了把每个属性都设置get/set
defineReactive(obj, key, obj[key])
if (typeof obj[key] === 'object') {
oberseve(obj[key]) // 2.3 obj[key]有可能还是一个对象,需要递归,给它的子属性也进行设置
}
}
}

function defineReactive(target, property, value) { // 3. 使用Object.defineProperty
Object.defineProperty(target, property, {
get () {
el.value = value // 3.1 当读取时,把值赋值给input框
return value
},
set (newVal) {
el.value = newVal // 3.2 当设置时,把赋值给input框
value = newVal
}
})
}

oberseve(obj) // 4.执行该函数,对obj对象里的属性进行设置get/set
el.addEventListener('input', function () { // 5.给输入框绑定input事件
obj.name = this.value // 6.当输入框输入内容时,会把输入框的内容赋值给obj.name,触发obj.name的set方法
})
</script>
</body>
</html>

当我们在输入框输入之后,在控制台输入obj.name,会发现值已经与输入框里的值一样

observe1
observe1

当我们在控制台,给obj.name赋值时,会发现输入框的内容也会作出相应更改

observe2
observe2

这样就完成了一个简陋的数据双向绑定了!