vue双向绑定原理简单实现

在3.0以前,vue主要的双向绑定原理就是通过object.defineProperty 自己只是知道大概原理,现在手动实现下
首先是data-binding.html的书写
在内部定义一个new Vue 然后简易的进行数据绑定 input绑定p h1 h2

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
<!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">
<meta name="buppt" content="https://github.com/buppt">
<title>data binding</title>
</head>

<body>
<h1 id="name"></h1>
<p id="num"></p>
<h2 id="name2"></h2>
<input id="input" type="text">
<script src="./instance/index.js"></script>
<script src="./observer/index.js"></script>
<script src="./observer/dep.js"></script>
<script src="./observer/watcher.js"></script>
<script>
var name1 = document.getElementById("name");
var num = document.getElementById("num");
var name2 = document.getElementById("name2");
var input = document.getElementById("input");
var selfVue = new Vue({
name: "hello",
a: 1
});
selfVue.bindData(name1, "name"); //name1元素绑定name属性
selfVue.bindData(num, "a");
selfVue.bindData(name2, "name");
selfVue.bindData(input, "name");



</script>
</body>

随后是vue类的定义

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
class Vue {
constructor(data) {
this.data = data;
observe(data); //调用observe类 来将该对象变为可监听的
}
bindData (elm, name) {
var self = this;
if (elm.tagName == 'INPUT') {
elm.addEventListener('input', function (e) {

var newValue = e.target.value;
var val = self.data[name]
if (val === newValue) {
return;
}
self.data[name] = newValue;
});
} else {
elm.innerHTML = this.data[name]
}
new Watcher(this, name, function (val) { //在这里会触发watcher的构造函数,随后在构造函数内部调 用get
elm.innerHTML = val;
});

};

debounce (func, wait) { //因为是频繁触发,所以最好是用防抖方式来做到缓冲
var timeout = null;

return function () {
var args = arguments;
var context = this;
if (timeout)
clearTimeout(timeout);
timeout = setTimeout(function () {
func.apply(context, args);
}, wait);
}
}


}

Observer类的定义

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
class Observer {
constructor(data) {
this.data = data;
this.walk(data); //调用walk遍历属性
}
walk (data) {
//遍历对象中的属性
Object.keys(data).forEach(function (key) {
defineReactive(data, key, data[key]); //对每个属性调用defineReactive来变成可监听的
})
}
}



function observe (value) {
if (!value || typeof value !== "object") {
return;
}
return new Observer(value);
}

function defineReactive (data, key, val) {
//为每一个属性确立一个Dep类 然后dep类里面有deps数组
const dep = new Dep();
let childOb = observe(val); //子属性也要做
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
if (Dep.target) { //Dep.target的设定尤其重要
dep.depend();
if (childOb) {
childOb.dep.depend();
}
}
return val;
},
set: function (newVal) {
if (val == newVal) {
return;
} else {
val = newVal;
dep.notify(); //set的时候对应的watcher触发跟新操作
}
}
})
}

Dep类的实现

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
class Dep {
constructor() {
this.subs = [];
}
addSub (sub) {
this.subs.push(sub); //用来保存watcher 实际在vue源码中会判断id 防止重复push
}
notify () {
this.subs.forEach((sub) => {
sub.update(); //每个进行更新
})
}
depend () {
if (Dep.target) {
Dep.target.addDep(this); //执行watcher的addDep
}
}
}
Dep.target = null;
const targetStack = []
function pushTarget (_target) {

if (Dep.target)
targetStack.push(Dep.target);
Dep.target = _target;
// console.log(targetStack);
}
function popTarget () {
Dep.target = targetStack.pop();
}

这里主要用到堆栈的概念 因为相对于vue1的绑定到每个dom元素这么的细腻,
vue2的组件跟新通知到组件级别 组件级别内部再使用diff算法,所以比如A在渲染的时候遇到B,所以A要先保存,再渲染B,B渲染好以后弹出,A再渲染
watcher类的实现

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
class Watcher {
constructor(vm, expression, cb) {
this.cb = cb;
this.vm = vm;
this.expression = expression;
this.value = this.get();
}
update () {
this.run();
}
run () {
const value = this.get(); //这里也会触发get操作 所以会在addSub那里判断是否是重复性加入组件
var oldVal = this.value;
this.value = value;
if (value != oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
}
get () {
Dep.target = this;
pushTarget(this);
//属性值触发 触发属性的get操作进行Dep.target的添加
var value = this.vm.data[this.expression];
popTarget();
return value;
}
addDep (dep) {
dep.addSub(this);
}
}

详见
https://segmentfault.com/q/1010000010095427
https://www.cnblogs.com/natsu07/p/10371448.html
github连接:https://github.com/KevinSwiftiOS/VueDataBinding