一、数据绑定的原理分析
- 数据绑定:一旦更新了
data
中的某个属性数据, 所有界面上直接使用或间接使用了此属性的节点都会更新。
- 数据劫持:
vue
实现数据绑定的一种技术,其核心思想是通过 defineProperty()
来监视 data
中所有属性(任意层次)数据的变化,一旦变 化就去更新界面。
- dep和watcher的理解:
- dep
dep
在初始化给data
的属性进行数据劫持的时候会被创建。
dep
的个数与data
中的属性一一对应。
dep
的结构中,id
是作为标识,subs: []
是n
个相关的watcher
容器。
- watcher
dep
与watcher
之间的关系
dep
和watcher
之间存在多对多的关系,data属性--> dep --> n个watcher
(模版中有多个表达式使用了此属性), 表达式 --> watcher --> n个dep
(多层表达式)
data
中属性的get()
方法建立的
- 初始化解析模块中的表达式创建
watcher
对象时建立关系的
- 实现数据绑定
Observer
,创建dep
对象,核心过程如下:
defineReactive: function(data, key, val) {
var dep = new Dep();
var childObj = observe(val);
Object.defineProperty(data, key, {
enumerable: true,
configurable: false,
get: function() {
if (Dep.target) {
dep.depend();
}
return val;
},
set: function(newVal) {
if (newVal === val) {
return;
}
val = newVal;
childObj = observe(newVal);
dep.notify();
}
});
}
- 给
dep
对象上需要进行原型挂载,核心过程如下:
addSub: function(sub) {
this.subs.push(sub);
},
depend: function() {
Dep.target.addDep(this);
},
removeSub: function(sub) {
var index = this.subs.indexOf(sub);
if (index != -1) {
this.subs.splice(index, 1);
}
},
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
}
- 对于
watcher
对象,核心过程如下:
function Watcher(vm, exp, cb) {
this.cb = cb;
this.vm = vm;
this.exp = exp;
this.depIds = {};
this.value = this.get();
}
dep
与watcher
之间需要建立关系,核心过程如下:
addDep: function (dep) {
if (!this.depIds.hasOwnProperty(dep.id)) {
dep.addSub(this);
this.depIds[dep.id] = dep;
}
},
get: function () {
Dep.target = this;
var value = this.getVMVal();
Dep.target = null;
return value;
},
二、数据绑定的实现
- Observer.js
function Observer(data) {
this.data = data;
this.walk(data);
}
Observer.prototype = {
walk: function(data) {
var me = this;
Object.keys(data).forEach(function(key) {
me.convert(key, data[key]);
});
},
convert: function(key, val) {
this.defineReactive(this.data, key, val);
},
defineReactive: function(data, key, val) {
var dep = new Dep();
var childObj = observe(val);
Object.defineProperty(data, key, {
enumerable: true,
configurable: false,
get: function() {
if (Dep.target) {
dep.depend();
}
return val;
},
set: function(newVal) {
if (newVal === val) {
return;
}
val = newVal;
childObj = observe(newVal);
dep.notify();
}
});
}
};
function observe(value, vm) {
if (!value || typeof value !== 'object') {
return;
}
return new Observer(value);
};
var uid = 0;
function Dep() {
this.id = uid++;
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
depend: function() {
Dep.target.addDep(this);
},
removeSub: function(sub) {
var index = this.subs.indexOf(sub);
if (index != -1) {
this.subs.splice(index, 1);
}
},
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
}
};
Dep.target = null;
- Watcher.js
function Watcher(vm, exp, cb) {
this.cb = cb;
this.vm = vm;
this.exp = exp;
this.depIds = {};
this.value = this.get();
}
Watcher.prototype = {
update: function () {
this.run();
},
run: function () {
var value = this.get();
var oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
},
addDep: function (dep) {
if (!this.depIds.hasOwnProperty(dep.id)) {
dep.addSub(this);
this.depIds[dep.id] = dep;
}
},
get: function () {
Dep.target = this;
var value = this.getVMVal();
Dep.target = null;
return value;
},
getVMVal: function () {
var exp = this.exp.split('.');
var val = this.vm._data;
exp.forEach(function (k) {
val = val[k];
});
return val;
}
};
- 数据绑定的实现
<!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>
<div id="test">
<p>{{ name }}</p>
<p v-text="name"></p>
<p v-text="friend.name"></p>
<button v-on:click="update">更新</button>
</div>
</body>
<script type="text/javascript" src="./js/mvvm/compile.js"></script>
<script type="text/javascript" src="js/mvvm/mvvm.js"></script>
<script type="text/javascript" src="./js/mvvm/observer.js"></script>
<script type="text/javascript" src="./js/mvvm/watcher.js"></script>
<script type="text/javascript">
new MVVM({
el: "#test",
data: {
name: "张三",
friend: {
name: "李四",
age: 28
}
},
method: {
update () {
this.name = "王五";
}
}
})
</script>
</html>
- 关于
vue
的源码分析,我在github
上建立了一个项目,项目地址如下https://github.com/jiuchengTk279/vueSourceCode.git
,欢迎大家访问下载,也希望可以多给予一些建议交流。