原理
你们都知道,vue是个MVVM框架,可以实现view和model的双向绑定,不像backbone那样,model改变须要手动去通知view更新,而vue实现的原理就是经过Object.defineProperty实现数据挟持,定义setter,而后数据改变的时候通知视图更新。
下面是网上vue的实现原理图: javascript

一、MVVM
入口文件,在这里对vue当中的data进行初始化,调用observer遍历
el下的全部节点,解析之类和取值操做({{}})。遍历
data的代理。html
二、Observer
遍历data,经过Object.defineProperty设置getter和setter,在setter知道数据发生了改变,而后通知Wacher去更新view。vue
三、Compile
遍历$el下的全部节点,解析指令和取值操做等,为每一个节点绑定更新函数(在这里),绑定事件和method的关系,同时也添加订阅者,当接受到视图更新的订阅消息后,调用更新函数,实现视图更新。同时在添加订阅者的时候,初始化渲染视图。java
四、Watcher
Watcher做为订阅者,充当Observer和Compile的中间桥梁,包含update方法,update方法调用Compile中绑定的事件更新函数,实现对视图的初始化和更新操做。node
MVVM的实现
MVVM完成初始化操做,而且调用observer和compile。对data.attribute。由于一个属性可能对应多个指令,因此须要一个_binding属性来存放属性对应的全部订阅者,这样属性一改变,就能够取出全部的订阅者去更新视图。git
github
function MVVM(options) {
// 初始化
this.$data = options.data;
this.$methods = options.methods;
this.$el = options.el;
// 保存data的每一个属性对应的全部watcher
this._binding = {};
// 调用observer和compile
this._observer(options.data);
this._compile();
// this.xxx 代理this.$data.xxx
this.proxyAttribute();
}
复制代码复制代码复制代码
Observer的实现
Observer遍历$data,经过Object.defineProperty的setter的挟持数据改变,监听到数据改变后取出全部该属性对应的订阅者,而后通知更新函数更新视图。
注意:这里有循环,且闭包(getter和setter)里面须要依赖循环项(value和key),因此用当即执行函数解决循环项获取不对的问题。数组
bash
MVVM.prototype._observer = function(data) {
var self = this;
for(var key in this.$data) {
if (this.$data.hasOwnProperty(key)) {
// 初始化属性对应的订阅者容器(数组)
this._binding[key] = {
_directives: [],
_texts: []
};
<span class="hljs-keyword">if</span>(typeof this.<span class="hljs-variable">$data</span>[key] === <span class="hljs-string">"object"</span>) {
<span class="hljs-built_in">return</span> this._observer(this.<span class="hljs-variable">$data</span>[key]);
}
var val = data[key];
// 当即执行函数获取正确的循环项
(<span class="hljs-keyword">function</span>(value, key) {
Object.defineProperty(self.<span class="hljs-variable">$data</span>, key, {
enumerable: <span class="hljs-literal">true</span>,
configurable: <span class="hljs-literal">true</span>,
get: <span class="hljs-function"><span class="hljs-title">function</span></span>() {
<span class="hljs-built_in">return</span> value;
},
<span class="hljs-built_in">set</span>(newval) {
<span class="hljs-keyword">if</span>(newval === value) {
<span class="hljs-built_in">return</span>;
}
value = newval;
// 监听到数据改变后取出全部该属性对应的订阅者,通知view更新-属性
<span class="hljs-keyword">if</span>(self._binding[key]._directives) {
self._binding[key]._directives.forEach(<span class="hljs-keyword">function</span>(watcher) {
watcher.update();
}, self);
}
// 监听到数据改变后取出全部该属性对应的订阅者,通知view更新-文本
<span class="hljs-keyword">if</span>(self._binding[key]._texts) {
self._binding[key]._texts.forEach(<span class="hljs-keyword">function</span>(watcher) {
watcher.update();
}, self);
}
}
});
})(val, key);
}
复制代码
复制代码<span class="hljs-keyword">if</span>(typeof this.<span class="hljs-variable">$data</span>[key] === <span class="hljs-string">"object"</span>) {
<span class="hljs-built_in">return</span> this._observer(this.<span class="hljs-variable">$data</span>[key]);
}
var val = data[key];
// 当即执行函数获取正确的循环项
(<span class="hljs-keyword">function</span>(value, key) {
Object.defineProperty(self.<span class="hljs-variable">$data</span>, key, {
enumerable: <span class="hljs-literal">true</span>,
configurable: <span class="hljs-literal">true</span>,
get: <span class="hljs-function"><span class="hljs-title">function</span></span>() {
<span class="hljs-built_in">return</span> value;
},
<span class="hljs-built_in">set</span>(newval) {
<span class="hljs-keyword">if</span>(newval === value) {
<span class="hljs-built_in">return</span>;
}
value = newval;
// 监听到数据改变后取出全部该属性对应的订阅者,通知view更新-属性
<span class="hljs-keyword">if</span>(self._binding[key]._directives) {
self._binding[key]._directives.forEach(<span class="hljs-keyword">function</span>(watcher) {
watcher.update();
}, self);
}
// 监听到数据改变后取出全部该属性对应的订阅者,通知view更新-文本
<span class="hljs-keyword">if</span>(self._binding[key]._texts) {
self._binding[key]._texts.forEach(<span class="hljs-keyword">function</span>(watcher) {
watcher.update();
}, self);
}
}
});
})(val, key);
}
复制代码} } 复制代码复制代码
Compile的实现
Compile遍历全部的节点,解析指令,为每一个节点绑定更新函数,且添加订阅者,当订阅者通知view更新的时候,调用更新函数,实现对视图的更新。
这里一样须要使用当即执行函数来解决闭包依赖的循环项问题。
还有一点须要解决的是,若是节点的innerText依赖多个属性的话,如何作到只替换改变属性对应的文本问题。
好比{{message}}:{{name}}已经被编译解析成“欢迎: 鸣人”,若是message改变为“你好”,怎么让使得“欢迎:鸣人”改成“你好:鸣人”。闭包
MVVM.prototype._compile = function() {
var dom = document.querySelector(this.$el);
var children = dom.children;
var self = this;
var i = 0, j = 0;
for(; i < children.length; i++) {
var node = children[i];
(function(node) {
// 解析{{}}里面的内容
// 保存指令原始内容,否则数据更新时没法完成替换
var text = node.innerText;
var matches = text.match(/{{([^{}]+)}}/g);
if(matches && matches.length > 0) {
// 保存和node绑定的全部属性
node.bindingAttributes = [];
for(j = 0; j < matches.length; j++) {
// data某个属性
var attr = matches[j].match(/{{([^{}]+)}}/)[1];
// 将和该node绑定的data属性保存起来
node.bindingAttributes.push(attr);
(function(attr) {
self._binding[attr]._texts.push(new Watcher(self, attr, function() {
// 改变的属性值对应的文本进行替换
var innerText = text.replace(new RegExp("{{" + attr + "}}", "g"), self.$data[attr]);
// 若是该node绑定多个属性 eg:<div>{{title}}{{description}}</div>
for(var k = 0; k < node.bindingAttributes.length; k++) {
if(node.bindingAttributes[k] !== attr) {
// 恢复原来没改变的属性对应的文本
innerText = innerText.replace("{{" + node.bindingAttributes[k] + "}}", self.$data[node.bindingAttributes[k]]);
}
}
node.innerText = innerText;
}));
})(attr);
}
}
// 解析vue指令
var attributes = node.getAttributeNames();
<span class="hljs-keyword">for</span>(j = 0; j < attributes.length; j++) {
// vue指令
var attribute = attributes[j];
// DOM attribute
var domAttr = null;
// 绑定的data属性
var vmDataAttr = node.getAttribute(attribute);
// 更新函数,但observer中model的数据改变的时候,经过Watcher的update调用更新函数,从而更新dom
var updater = null;
<span class="hljs-keyword">if</span>(/v-bind:([^=]+)/.test(attribute)) {
// 解析v-bind
domAttr = RegExp.<span class="hljs-variable">$1</span>;
// 更新函数
updater = <span class="hljs-keyword">function</span>(val) {
node[domAttr] = val;
}
// data属性绑定多个watcher
self._binding[vmDataAttr]._directives.push(
new Watcher(self, vmDataAttr, updater)
)
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(attribute === <span class="hljs-string">"v-model"</span> && (node.tagName = <span class="hljs-string">'INPUT'</span> || node.tagName == <span class="hljs-string">'TEXTAREA'</span>)) {
// 解析v-model
// 更新函数
updater = <span class="hljs-keyword">function</span>(val) {
node.value = val;
}
// data属性绑定多个watcher
self._binding[vmDataAttr]._directives.push(
new Watcher(self, vmDataAttr, updater)
)
// 监听input/textarea的数据变化,同步到model去,实现双向绑定
node.addEventListener(<span class="hljs-string">"input"</span>, <span class="hljs-keyword">function</span>(evt) {
var <span class="hljs-variable">$el</span> = evt.currentTarget;
self.<span class="hljs-variable">$data</span>[vmDataAttr] = <span class="hljs-variable">$el</span>.value;
});
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(/v-on:([^=]+)/.test(attribute)) {
// 解析v-on
var event = RegExp.<span class="hljs-variable">$1</span>;
var method = vmDataAttr;
node.addEventListener(event, <span class="hljs-keyword">function</span>(evt) {
self.<span class="hljs-variable">$methods</span>[method] && self.<span class="hljs-variable">$methods</span>[method].call(self, evt);
});
}
}
})(node);
复制代码
}
复制代码// 解析vue指令
var attributes = node.getAttributeNames();
<span class="hljs-keyword">for</span>(j = 0; j < attributes.length; j++) {
// vue指令
var attribute = attributes[j];
// DOM attribute
var domAttr = null;
// 绑定的data属性
var vmDataAttr = node.getAttribute(attribute);
// 更新函数,但observer中model的数据改变的时候,经过Watcher的update调用更新函数,从而更新dom
var updater = null;
<span class="hljs-keyword">if</span>(/v-bind:([^=]+)/.test(attribute)) {
// 解析v-bind
domAttr = RegExp.<span class="hljs-variable">$1</span>;
// 更新函数
updater = <span class="hljs-keyword">function</span>(val) {
node[domAttr] = val;
}
// data属性绑定多个watcher
self._binding[vmDataAttr]._directives.push(
new Watcher(self, vmDataAttr, updater)
)
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(attribute === <span class="hljs-string">"v-model"</span> && (node.tagName = <span class="hljs-string">'INPUT'</span> || node.tagName == <span class="hljs-string">'TEXTAREA'</span>)) {
// 解析v-model
// 更新函数
updater = <span class="hljs-keyword">function</span>(val) {
node.value = val;
}
// data属性绑定多个watcher
self._binding[vmDataAttr]._directives.push(
new Watcher(self, vmDataAttr, updater)
)
// 监听input/textarea的数据变化,同步到model去,实现双向绑定
node.addEventListener(<span class="hljs-string">"input"</span>, <span class="hljs-keyword">function</span>(evt) {
var <span class="hljs-variable">$el</span> = evt.currentTarget;
self.<span class="hljs-variable">$data</span>[vmDataAttr] = <span class="hljs-variable">$el</span>.value;
});
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(/v-on:([^=]+)/.test(attribute)) {
// 解析v-on
var event = RegExp.<span class="hljs-variable">$1</span>;
var method = vmDataAttr;
node.addEventListener(event, <span class="hljs-keyword">function</span>(evt) {
self.<span class="hljs-variable">$methods</span>[method] && self.<span class="hljs-variable">$methods</span>[method].call(self, evt);
});
}
}
})(node);
复制代码} 复制代码复制代码
Wathcer的实现
Watcher充当订阅者的角色,架起了Observer和Compile的桥梁,Observer监听到数据变化后,通知Wathcer更新视图(调用Wathcer的update方法),Watcher再告诉Compile去调用更新函数,实现dom的更新。同时页面的初始化渲染也交给了Watcher(固然也能够放到Compile进行)。
function Watcher(vm, attr, cb) {
this.vm = vm; // viewmodel
this.attr = attr; // data的属性,一个watcher订阅一个data属性
this.cb = cb; // 更新函数,在compile那边定义
// 初始化渲染视图
this.update();
}
复制代码Watcher.prototype.update = function() { // 通知comile中的更新函数更新dom this.cb(this.vm.$data[this.attr]); } 复制代码复制代码
所有代码
git地址:github.com/VikiLee/MVV…
使用例子
<!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>Document</title>
</head>
<body>
<div id="view">
<div v-bind:id="id">
{{message}}:{{name}}
</div>
<input type="text" v-model="name"/>
<button v-on:click="handleClick">获取输入值</button>
</div>
</body>
<script src="js/MVVM.js" type="text/javascript"></script>
<script>
var vue = new MVVM({
el: "#view",
data: {
message: "欢迎光临",
name: "鸣人",
id: "id"
},
methods: {
handleClick: function() {
alert(this.message + ":" + this.name + ", 点击肯定路飞会出来");
this.name = '路飞';
}
}
})
复制代码setTimeout(function() { vue.message = "你好"; }, 1000); </script> </html> 复制代码复制代码