1. Object.defineProperty,能够看这篇文章javascript
2. 观察者模式,能够看个人笔记html
双向绑定一个是视图改变数据,这个简单,好比input中输入的文本绑定到数据中,那么能够经过监听input事件实现vue
另外一个是数据改变视图,这个具体怎么实现呢?先看如下总结,带着这些总结看代码更容易理解java
咱们要实现Watcher Dep Observer Compile,如下是它们的介绍。node
1. Watcher:首先要知道,每一个双向绑定的属性(如绑定在v-model中的属性)都会生成watcher实例,watcher包含一个更新视图的方法(命名为update)。git
2. Dep:观察者系统,用于存放(订阅)watcher,在属性改变时(setter)触发,会执行watcher的update方法从而更新视图。github
3. Observer:循环data中的属性,对于每一个属性都生成一个观察者系统实例dep,而后设置getter,在getter中包含一个让dep订阅该属性的watcher的操做,这个操做是怎么执行的呢?实际上是经过在生成该属性的watcher时,读取一下该值,那么就会进入到getter中,从而执行订阅操做。而后再设置setter,在setter中触发dep,从而执行watcher中更新dom的方法update,达到属性值改变时更新视图的目的。segmentfault
4. Compile:这个用于解析dom,初始化视图和为全部双向绑定的属性(如v-mode {{}} )生成watcher实例,由3可知,为属性建立watcher时,会读取一下该属性,让这个属性的观察者dep订阅该watcher。bash
1. 首先实现下Vue构造函数,由此可知Vue在实例化时会作什么。dom
function Vue (options) {
this.data = options.data(); // vue的data是一个工厂函数
let dom = document.querySelector(options.el);
observe(this.data); // 为data 的属性进行 Object.defineProperty 操做
new Compile(dom, this); // 解析dom,初始化视图,为双向绑定的属性生成watcher实例
}复制代码
看看这个Vue构造函数好像有点不妥,好比我要读取data中的一个name属性时,我要这样写this.data.name,可是想一想咱们平时用vue时是否是直接this.name就能读取到呢?因此这里要给属性作一下代理。
function Vue(options) {
this.data = options.data(); // vue的data是一个工厂函数
let dom = document.querySelector(options.el);
// 代理下data的属性
for (let key of Object.keys(this.data)) {
this.proxy(key);
}
// 为data 的属性进行 Object.defineProperty 操做
observe(this.data);
// 解析dom,初始化视图,为双向绑定的属性生成watcher实例
new Compile(dom, this);
}
Vue.prototype.proxy = function (key){
Object.defineProperty(this, key, {
configurable: false,
enumerable: true,
get () {
return this.data[key];
},
set (newVal) {
this.data[key] = newVal;
}
});
}复制代码
2. 由1可知,Vue实例化时会执行observe方法,上面已经介绍过observe主要是设置getter和setter,而且会用到观察者模式,因此咱们先实现一个观察者系统,再实现observe方法。
// 观察者系统 用于订阅watcher
function Dep() {
this.subs = [];
}
Dep.prototype = {
addSub: function (sub) {
this.subs.push(sub);
},
notify: function () {
this.subs.forEach(function (sub) {
sub.update(); // 执行watcher的更新视图的方法。
});
}
}
function observe(data) {
if (typeof data !== 'object') return;
for (let key of Object.keys(data)) {
defineReactive(data, key, data[key]);
}
}
function defineReactive(data, key, val) {
observe(val);
let dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
/*
在getter中包含订阅watcher的操做,在实例化该属性的watcher时,
会把watcher绑定到Dep的静态属性target上,而后读取一下该属性,
从而进入getter这里执行这个订阅操做。
*/
if (Dep.target) {
dep.addSub(Dep.target);
}
return val;
},
set(newval) {
val = newval;
// 触发观察者,从而执行watcher的update方法,更新视图
dep.notify();
}
})
}复制代码
3. 由2可知,在getter中有个订阅watcher的操做,那么咱们实现下watcher,watcher会包含一个更新视图的方法update。
function Watcher(vm, exp, cb) {
this.cb = cb; // 一个更新视图的方法
this.vm = vm;
this.exp = exp;
// 绑定本身到Dep.target
Dep.target = this;
// 就是此处,读取一下本身,从而进入getter,订阅本身(Dep.target)
this.value = this.vm[this.exp];
// 释放Dep.target
Dep.target = null;
}
Watcher.prototype = {
update () {
let newValue = this.vm[this.exp];
let oldValue = this.value;
if (newValue !== oldValue) {
this.cb.call(this.vm, newValue, oldValue)
}
}
}复制代码
4. 好了 watcher有了,那么实现下Compile,初始化视图并为双向绑定的属性生成watcher。
function Compile (el, vm) {
this.el = el;
this.vm = vm;
this.compileElement(el);
}
Compile.prototype = {
compileElement (el) {
let childs = el.childNodes;
Array.from(childs).forEach(node => {
let reg = /\{\{(.*)\}\}/;
let text = node.textContent;
if (this.isElementNode(node)) // 元素节点
this.compile(node)
else if (this.isTextNode(node) && reg.test(text)) { // 文本节点
this.compileText(node, reg.exec(text)[1]);
}
if (node.childNodes && node.childNodes.length) {
this.compileElement(node);
}
})
},
compile (node) {
let nodeAttr = node.attributes;
Array.from(nodeAttr).forEach(attr => {
if (this.isDirective(attr.nodeName)) { // v-model属性
node.value = this.vm[attr.nodeValue]; // 初始化
// 绑定input事件,达到视图更新数据目的
node.addEventListener('input', () => {
this.vm[attr.nodeValue] = node.value;
})
new Watcher(this.vm, attr.nodeValue, val => {
node.value = val;
})
}
})
},
compileText (node, exp) {
node.textContent = this.vm[exp]; // 初始化
new Watcher(this.vm, exp, val => {
node.textContent = val;
});
},
isElementNode (node) {
return node.nodeType === 1;
},
isTextNode (node) {
return node.nodeType === 3;
},
isDirective (attr) {
return attr === 'v-model';
}
}复制代码
大功告成,使用一下看看效果吧。
html:
<div id="demo">
<div>{{text}}</div>
<input v-model="text">
</div>
script:
new Vue({
el: '#demo',
data() {
return {
text: 'hello world'
}
}
})
复制代码
代码已提交到 github上