手写一个简单的Vue数据响应式吧
# 一、什么是数据的双向绑定(MVVM)
数据的双向绑定主要有以下两个方面:
- 改变代码层面的数据,view视图层也同步变化,即
Data
->view
的变化 - 改变视图层输入框中的内容,Data数据也同步变化,即
view
->Data
的变化
其一,Data数据层
->view视图层
的变化我们要通过Object.defineProperty
或Proxy
来实现; 其二,view视图层
->Data数据层
的变化可以通过事件监听来实现,也就是v-model的语法糖。只有两个步骤全都实现才是数据的双向绑定,实现其中一个只是数据的单向绑定!
# 二、响应式数据效果图
# 三、实现数据的响应式
依照vue的模板格式来,首先初始化内容,创建一个MVVM
类,html
内容都放在#app
下。
<div id="app">
<h2>通过改变视图=>改变数据</h2>
<input type="text" placeholder="姓名" v-model="info" />
<div>
<p>人物:<span>{{info}}</span></p>
</div>
</div>
<button onclick="ModifyData()">通过改变数据=>改变视图</button>
<button onclick="getData()">获取当前数据</button>
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
function ModifyData() {
p.setData("info", "武大郎");
}
function getData() {
console.log(p._data);
}
class MVVM {
constructor(el, data) {
this.init(el, data);
}
//初始化
init(el, v) {
this._data = v.data();
this.el = document.querySelector(el);
}
}
let p = new MVVM("#app", {
data() {
return {
info: "潘金莲",
};
},
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
以下各个函数的作用:
init
负责初始化数据内容observer
对目标对象的属性进行遍历,确保每个属性都被监听到defineReactive
对目标对象属性进行监听matchMustache
匹配html中Mustache的内容,用data中的数据进行替换,需要递归获取文本节点bindInput
获取绑定v-model
的输入框节点,改变输入框内容时,数据跟着改变updateView
数据发生变化时,触发页面更新setData
对外暴露的一个修改数据的接口
执行逻辑为:
new
一个MVVM
==> 调用 observer(data)
将对象变成响应式 ==> matchMustache
函数把中的内容替换为
data
的数据 ==> bindInput
函数获取v-model
的值用data
的值进行替换 ==> 输入框改变数据 ==> 触发set
更新视图 ==> 点击ModifyData
事件,调用setData
方法 ==> 触发set
更新视图。
# Object.defineProperty监听
class MVVM {
constructor(el, data) {
this.reg = /\{\{(.*?)\}\}/;
this.modelValue = {};
this.init(el, data);
}
//初始化
init(el, v) {
this._data = v.data();
this.el = document.querySelector(el);
//监听对象
this.observer(this._data)
//初始化data内容到视图层
this.matchMustache(this.el);
this.bindInput();
}
//对目标对象属性进行监听
defineReactive(object,key, value) {
let _this = this;
如果值是个对象则深度监听;
_this.observer(value);
Object.defineProperty(object, key, {
get() {
return value;
},
set(newValue) {
value = newValue;
//新增是个对象时也深度监听
_this.observer(newValue);
//修改触发更新
_this.updateView(key, value);
},
});
}
//监听data对象
observer(target) {
//如果不是对象则不进行监听
if (typeof target !== "object" || typeof target === null) {
return;
}
// this.defineReactive(target);
for (let key in target) {
this.defineReactive(target, key, target[key]);
}
}
//匹配Mustache语法的内容
matchMustache(el) {
const childNodes = el.childNodes;
childNodes.forEach((item) => {
if (item.nodeType === 3) {
const _value = item.nodeValue;
if (_value.trim().length) {
let _valied = this.reg.test(_value);
if (_valied) {
const _match = item.nodeValue.match(this.reg)[1];
this.modelValue[_match] = item.parentNode;
item.parentNode.innerText = item.nodeValue.replace(
this.reg,
this._data[_match] || undefined
);
}
}
}
item.childNodes && this.matchMustache(item);
});
}
//绑定输入框的内容
bindInput(key, value) {
const _inputs = document.querySelectorAll("input");
_inputs.forEach((item) => {
const _model = item.getAttribute("v-model").trim();
if (key && _model === key) {
item.value = value;
} else {
item.value = this._data[_model];
if (_model) {
item.addEventListener("input", (e) => {
this._data[_model] = e.target.value;
});
}
}
});
}
//更新视图
updateView(key, value) {
this.modelValue[key].innerText = value;
}
//改变数据 更新view
setData(key, value) {
this._data[key] = value;
this.bindInput(key, value);
}
}
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
89
90
91
92
93
94
95
96
97
98
99
100
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
89
90
91
92
93
94
95
96
97
98
99
100
# Proxy监听
把上面defineReactive
函数中Object.defineProperty
的监听改为下面Proxy
监听
defineReactive(object) {
let _this = this;
this._data = new Proxy(object, {
get(target, prop) {
return Reflect.get(target, prop);
},
set(target, prop, value, receiver) {
//修改触发更新
_this.updateView(prop, value);
return Reflect.set(...arguments);
},
});
}
observer(target) {
//如果不是对象则不进行监听
if (typeof target !== "object" || typeof target === null) {
return;
}
this.defineReactive(target);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 四、Object.defineProperty和Proxy的区别
- 前者监听的是对象的属性,后者监听的是整个对象
- 前者不能监听到新增属性和删除属性,后者可以监听
- 前者不能监听到数组的变化,后者可以监听
- 前者的兼容性比后者要好,因为
Proxy
是ES6
提供的一个新的API
- 前者的性能比后者要好
虽然proxy
性能和兼容性差,但是proxy
作为新标准将受到浏览器厂商重点持续的性能优化, 性能这块会逐步得到改善
# 五、参考文献
「Object.defineProperty」 深入浅出 (opens new window)
Reflect.get (opens new window)
上次更新: 2022/12/02, 09:00:55