參考網(wǎng)址:
1.http://www.reibang.com/p/f194619f6f26?from=singlemessage&isappinstalled=0
2.http://www.cnblogs.com/kidney/p/6052935.html
四大步驟
==1.監(jiān)聽器Observer==:用來劫持監(jiān)聽所有的屬性趣惠,主要運用Object.Proprety
屬性來監(jiān)聽數(shù)據(jù)错维。如果有改變時意推,就通知訂閱器列肢,由訂閱器來通知所有的訂閱者迹恐。
==2.訂閱器Dep==: 用來管理所有的訂閱者逗载,主要是一個數(shù)組,保存所有的訂閱者
==3.訂閱者Watcher==: 每一個watcher都有一個更新函數(shù)Update农曲,當接收到訂閱器的通知改變時社搅,就會執(zhí)行update函數(shù),從而更新視圖
==4.解析器Compile==: 掃描和解析每個節(jié)點的相關指令(v-model, v-on等v-指令)乳规,如果存在v-model形葬,v-on等指令,解析器compile就會初始化這些模板數(shù)據(jù)暮的,使之可以頁面首次打開時把vue實例的數(shù)據(jù)顯示在視圖上笙以,然后初始化相應的訂閱者Wather
完整流程
下面代碼的實現(xiàn)流程:
可以從這個Vue函數(shù)開始入手
function Vue(options){
console.log(this); //this是指向實例對象
this.data = options.data;
var data = this.data;
observe(data,this); //第一步
var id = options.el;
var dom = nodeToFragment(document.getElementById(id),this); //第二部
document.getElementById(id).appendChild(dom);
}
var vm = new Vue({
el: 'app',
data:{
text:'hello world'
}
})
第一步:創(chuàng)建一個observe去監(jiān)聽vue的實例vm中data的所有屬性
第二步:調用nodeToFragment()
①==劫持==id=‘app’的元素下的所有子節(jié)點。(后面對于DOM的操作都在這里進行操作冻辩,最后更新完成再更新到我們真正的DOM樹上源织,避免對DOM的反復操作)
②在這里調用解析器compile
第三步:compile()
①遍歷dom中節(jié)點是否有v指令屬性,有的話就對視圖進行==初始化==(如:把上面的text:‘hello world’
初始化到<input type="text" v-model="text">
的value
中)
②為所有有v指令的節(jié)點==創(chuàng)建一個watcher==微猖,并把對應的動態(tài)綁定屬性值傳過去
compile中與Observer的聯(lián)系:
③在初始化的過程中谈息,就必定會調用到我們observe的get方法,因此==在Observe這里把watcher添加到訂閱器Dep中==
④在compile中我們還會設置對事件的監(jiān)聽(如input事件),執(zhí)行了對視圖中的數(shù)據(jù)修改->反映到model數(shù)據(jù)中(調用observer的set方法)
第四步:Observer通知訂閱器Dep凛剥,數(shù)據(jù)值已修改侠仇。Dep.notify()通知所有的watcher去更新視圖
動態(tài)綁定測試代碼
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div id="app">
<input type="text" v-model="text">
{{ text }}
</div>
<script type="text/javascript">
//監(jiān)視者
/**
* [observe description]
* @param {[type]} obj [vue的data]
* @param {[type]} vm [vue實例]
* @return {[type]} [description]
*/
function observe(obj,vm){
Object.keys(obj).forEach(function (key) {
defineReactive(vm, key, obj[key]);
})
};
/**
* [defineReactive description]
* @param {[type]} obj [vue實例]
* @param {[type]} key [屬性名]
* @param {[type]} val [屬性值]
* @return {[type]} [description]
*/
function defineReactive(obj, key, val){
console.log('observe:defineReactive');
var dep = new Dep();
console.log(dep);
//obj:vue實例vm(被操作的目標對象),key要定義or要修改的屬性名
Object.defineProperty(obj,key,{
get : function (){
console.log('gettttttttt!');
console.log(Dep.target);
if(Dep.target)
dep.addSub(Dep.target)
return val;
},
set : function (newVal){
console.log('set!!!!!!!!!!!!!!!!');
if(newVal === val) return;
val = newVal;
console.log(val);
dep.notify();
}
});
};
//解析器
//node:劫持的dom犁珠, vm:vue實例
function compile (node, vm) {
console.log('compile-----start');
var reg = /\{\{(.*)\}\}/;
if(node.nodeType === 1) //元素逻炊,查找v-modal綁定的屬性值
{
var attr = node.attributes;
for(var i=0;i<attr.length;i++){
if(attr[i].nodeName == 'v-model'){
var name = attr[i].nodeValue; //v-model的屬性名
node.addEventListener('input',function(e){
console.log('檢測到值改變:'+e.target.value);
vm[name] = e.target.value; //給相應的 data 屬性賦值,進而觸發(fā)該屬性的 set 方法
});
node.value = vm[name]; //把vue對應屬性名的值賦給節(jié)點,觸發(fā)get方法,vm.data[name]不會觸發(fā)get方法犁享,但是vm[name]會觸發(fā)get方法S嗨亍!
node.removeAttribute('v-model'); //為什么要刪除v-model炊昆?
}
}
new Watcher(vm, node, name, 'input'); //通知watcher去修改桨吊?
}
if(node.nodeType === 3){
if (reg.test(node.nodeValue)) {
var name = RegExp.$1; // 獲取匹配到的字符串
name = name.trim();
new Watcher(vm, node, name, 'text');
}
}
console.log('compile-----end');
}
//訂閱器
function Dep (){
this.subs = [];
console.log('dep');
}
Dep.prototype = {
addSub: function(sub){
console.log('addToDep')
this.subs.push(sub);
},
notify: function(){
this.subs.forEach(function(sub){
sub.update(); //通知watch去update
});
}
}
//var vdom = nodeToFragment(document.getElementById('app'));
//console.log(vdom);
function nodeToFragment(node,vm){
console.log('nodeToFragment---start');
var flag = document.createDocumentFragment();
var child;
//console.log(node.childNodes); // length : 5
while (child = node.firstChild){
compile(child,vm); //把劫持的child給compile解析
flag.appendChild(child);
}
//console.log(node.childNodes); // length : 0
console.log('nodeToFragment------end')
return flag;
}
/**
* [Watcher 訂閱者]
* @param {[type]} vm [vue實例對象]
* @param {[type]} node [含有v指令的節(jié)點]
* @param {[type]} name [description]
* @param {[type]} nodeType [description]
*/
function Watcher (vm, node, name, nodeType) {
console.log('watcher');
console.log(this);
//Dep target是Dep和watcher關聯(lián)的唯一橋梁
Dep.target = this;
/**
* 關于Dep.target: 是一個全局變量威根,保存了當前的watcher
* wathcer構造函數(shù)中,為Dep.target賦值后會通過watcher.update()方法去調用observe.get()
* 在observer.get()方法中就是根據(jù)Dep.target來把當前watcher加入到訂閱器Dep中
* 所以在把wathcer加入完Dep后,Dep.target就沒有用了视乐,所以在this.update()后面要把Dep.watcher設為null
*/
this.name = name;
this.node = node;
this.vm = vm;
this.nodeType = nodeType;
this.update();
Dep.target = null;
}
Watcher.prototype = {
//更新視圖上的值洛搀,{{ value }}
update: function () {
this.get();
if (this.nodeType == 'text') {
this.node.nodeValue = this.value;
}
if (this.nodeType == 'input') {
this.node.value = this.value;
}
},
// 獲取 data 中的屬性值
get: function () {
this.value = this.vm[this.name]; // 觸發(fā)相應屬性的 get
}
}
function Vue(options){
console.log(this); //this是指向實例對象
this.data = options.data;
var data = this.data;
observe(data,this);
var id = options.el;
var dom = nodeToFragment(document.getElementById(id),this);
document.getElementById(id).appendChild(dom);
}
var vm = new Vue({
el: 'app',
data:{
text:'hello world'
}
})
</script>
</body>
</html>