vue.js中有兩個(gè)核心功能:響應(yīng)式數(shù)據(jù)綁定宅倒,組件系統(tǒng)。主流的mvc框架都實(shí)現(xiàn)了單向數(shù)據(jù)綁定,而雙向綁定無(wú)非是在單向綁定基礎(chǔ)上給可輸入元素添加了change事件瓮具,從而動(dòng)態(tài)地修改model和view收奔。
介紹vue原理之前掌呜,我們先簡(jiǎn)單回顧一下什么是mvc。
[阮老師mvc詳解鏈接點(diǎn)這里]
MVC
視圖(View):用戶(hù)界面坪哄。
控制器(Controller):業(yè)務(wù)邏輯
模型(Model):數(shù)據(jù)保存
View 傳送指令到 Controller
Controller 完成業(yè)務(wù)邏輯后质蕉,要求 Model 改變狀態(tài)
Model 將新的數(shù)據(jù)發(fā)送到 View,用戶(hù)得到反饋
MVP
各部分之間的通信翩肌,都是雙向的模暗。
View 與 Model 不發(fā)生聯(lián)系,都通過(guò) Presenter 傳遞念祭。
View 非常薄兑宇,不部署任何業(yè)務(wù)邏輯,稱(chēng)為"被動(dòng)視圖"(Passive View)粱坤,即沒(méi)有任何主動(dòng)性隶糕,而 Presenter非常厚祝钢,所有邏輯都部署在那里。
MVVM
可以看到若厚,MVVM 模式將 Presenter 改名為 ViewModel拦英,基本上與 MVP 模式完全一致。唯一的區(qū)別是测秸,它采用雙向綁定(data-binding):View的變動(dòng)疤估,自動(dòng)反映在 ViewModel,反之亦然霎冯。Angular 和 Ember 都采用這種模式铃拇。
好了,接下來(lái)來(lái)介紹本章的重點(diǎn)沈撞,vue的原理慷荔。
1.vuejs雙向綁定
html代碼
<input type="text" id="a">
<span id="b"></span>
js代碼
var obj = {};
Object.defineProperty(obj, 'hello', {
set: function (newVal) {
document.getElementById('a').value = newVal;
document.getElementById('b').innerHTML = newVal;
}
})
document.addEventListener('keyup', function (e) {
obj.hello = e.target.value;
});
效果1:
這個(gè)效果就是在文本框中輸入的值會(huì)顯示在旁邊的<span>標(biāo)簽里。這個(gè)例子就是雙向綁定的實(shí)現(xiàn)缠俺,但是僅僅為了說(shuō)明原理显晶,這個(gè)和我們平時(shí)用的vue.js還有差距,下面是我們常見(jiàn)的vue.js寫(xiě)法
html代碼
{{ text }}
js代碼
el: 'app',
data: {
text: 'hello world'
}
})
為了實(shí)現(xiàn)這樣的容易理解的代碼vue.js背后做了很多工作壹士,我們一一分解磷雇。
- 輸入框以及文本節(jié)點(diǎn)與data中的數(shù)據(jù)綁定
- 輸入框變化的時(shí)候,data中的數(shù)據(jù)同步變化躏救。即MVVM中 view => viewmodel的變化
- data中的數(shù)據(jù)變化時(shí)唯笙,文本節(jié)點(diǎn)的內(nèi)容同步變化。即MVVM中viewmode => view的變化
2 數(shù)據(jù)初始化綁定
介紹數(shù)據(jù)初始化綁定之前先說(shuō)一下DocumentFragment盒使。DocumentFragment(文檔片段)可以看做是節(jié)點(diǎn)容器崩掘,它可以包含多個(gè)子節(jié)點(diǎn),可以把它插入到DOM中少办,只有它的子節(jié)點(diǎn)會(huì)插入目標(biāo)節(jié)點(diǎn)苞慢,所以可以把它看做是一組節(jié)點(diǎn)容器。使用DocumentFragment處理節(jié)點(diǎn)速度和性能優(yōu)于直接操作DOM凡泣。Vue進(jìn)行編譯的時(shí)候就是將掛載目標(biāo)的所有子節(jié)點(diǎn)劫持到DocumentFragment中枉疼,經(jīng)過(guò)處理后再將DocumentFragment整體返回到掛載目標(biāo)。實(shí)例代碼如下:
var dom = nodeToFragment(document.getElementById("app"));
console.log(dom);
function nodeToFragment (node, vm) {
var flag = document.createDocumentFragment();
var child;
while (child = node.firstChild) {
flag.appendChild(child); // 劫持node的所有節(jié)點(diǎn)
}
return flag;
}
document.getElementById("app").appendChild(dom);
有了文檔片段之后再看看初始化綁定鞋拟。
html代碼:
<input type="text" v-model="text">
{{text}}
</div>
js代碼
function compile (node, vm) {
var reg = /\{\{(.*)\}\}/;
// 節(jié)點(diǎn)類(lèi)型為元素
if (node.nodeType === 1) {
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.value = vm.data[name]; // 將data的值賦給該node
node.removeAttribute('v-model');
}
}
}
// 節(jié)點(diǎn)類(lèi)型為text
if (node.nodeType === 3) {
if (reg.test(node.nodeValue)) {
var name = RegExp.$1; // 獲取匹配到的字符串
name = name.trim()
node.nodeValue = vm.data[name]; // 將該data的值付給該node
}
}
}
function nodeToFragment (node, vm) {
var flag = document.createDocumentFragment();
var child;
while (child = node.firstChild) {
compile(child, vm);
// 將子節(jié)點(diǎn)劫持到文檔片段中
flag.appendChild(child);
}
return flag;
}
// 構(gòu)造函數(shù)
function Vue (options) {
this.data = options.data;
var id = options.el;
var dom = nodeToFragment(document.getElementById(id), this);
// 編譯完成后把dom返回到app中
document.getElementById(id).appendChild(dom);
}
var vm = new Vue({
el: 'app',
data: {
text: 'hello world'
}
});
我們看到hello word已經(jīng)綁定到input標(biāo)簽和節(jié)點(diǎn)中了
先看compile方法骂维,這個(gè)方法主要負(fù)責(zé)給node節(jié)點(diǎn)賦值
- compile方法接收兩個(gè)參數(shù),第一個(gè)是DOM節(jié)點(diǎn)贺纲,第二個(gè)vm是當(dāng)前對(duì)象
- 判斷dom節(jié)點(diǎn)類(lèi)型航闺,如果是1,表示元素(這里判斷不太嚴(yán)謹(jǐn),只是為了說(shuō)明原理)潦刃,在node節(jié)點(diǎn)的所有屬性中查找nodeName為“v-model”的屬性侮措,找到屬性值,這里是“text”乖杠。用當(dāng)前對(duì)象中名字為“text”的屬性值給節(jié)點(diǎn)賦值分扎,最后刪除這個(gè)屬性,就是刪除節(jié)點(diǎn)的v-model屬性胧洒。
- 判斷dom節(jié)點(diǎn)類(lèi)型畏吓,如果是3,表示是節(jié)點(diǎn)內(nèi)容卫漫,用正則表達(dá)式判斷是“{{text}}”這樣的字符串菲饼,用當(dāng)前對(duì)象中名字為“text”的屬性值給節(jié)點(diǎn)賦值,直接覆蓋掉“{{text}}”
nodeToFragment方法負(fù)責(zé)創(chuàng)建文檔片段列赎,并將compile處理過(guò)的子節(jié)點(diǎn)劫持到這個(gè)文檔片段中
- 創(chuàng)建一個(gè)文檔片段
- 循環(huán)查找傳入的node節(jié)點(diǎn)宏悦,調(diào)用compile方法給節(jié)點(diǎn)賦值
- 將賦值后的節(jié)點(diǎn)劫持到文檔片段中
Vue構(gòu)造函數(shù)
- 用傳入?yún)?shù)的data屬性給當(dāng)前對(duì)象的data屬性賦值
- 用傳入?yún)?shù)的id標(biāo)記查找掛載節(jié)點(diǎn),調(diào)用nodeToFragment方法獲取劫持后的文檔片段包吝,這個(gè)過(guò)程稱(chēng)為編譯
- 編譯完成后饼煞,將文檔片段插入到指定的當(dāng)前節(jié)點(diǎn)中
實(shí)例化vue
- 實(shí)例化一個(gè)vue對(duì)象,el屬性為掛載節(jié)點(diǎn)的id漏策,data屬性為要綁定的屬性及屬性值
響應(yīng)式數(shù)據(jù)綁定
初始化綁定只是實(shí)現(xiàn)了第一步派哲,然后我們要實(shí)現(xiàn)的是在文本框中輸入內(nèi)容的時(shí)候臼氨,vue實(shí)例中的屬性值也跟著變化掺喻。思路是在文本框中輸入數(shù)據(jù)的時(shí)候,觸發(fā)文本框的input事件(也可以是keyup储矩,change)感耙,在相應(yīng)的事件處理程序中,獲取輸入內(nèi)容賦值給當(dāng)前vue實(shí)例vm的text屬性持隧。這里利用上面介紹的Object.defeinProperty()方法來(lái)給vue實(shí)例中data中的屬性重新定義為訪問(wèn)器屬性即硼,就是在定義這個(gè)屬性的時(shí)候添加get,set這兩個(gè)存取描述符屡拨,這樣給vm.text賦值的時(shí)候就會(huì)觸發(fā)set方法只酥。然后在set方法中更新vue實(shí)例屬性的值⊙嚼牵看下面的html裂允,js代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>響應(yīng)式數(shù)據(jù)綁定</title>
</head>
<body>
<div id="app">
<input type="text" v-model="text"/>
{{ text }}
</div>
<script>
/**
* 使用defineProperty將data中的text設(shè)置為vm的訪問(wèn)器屬性
* @param obj 對(duì)象
* @param 屬性名
* @param 屬性值
* */
function defineReactive (obj, key, val) {
Object.defineProperty(obj, key, {
get: function () {
return val
},
set: function (newVal) {
if (newVal === val) {
return
}
val = newVal
// 輸出日志
console.log(`set方法觸發(fā)屬性值變化${val}`)
}
})
}
/**
* 給vue實(shí)例定義訪問(wèn)器屬性
* @param obj vue實(shí)例中的數(shù)據(jù)
* @param vm vue對(duì)象
* */
function observe (obj, vm) {
Object.keys(obj).forEach(function (key) {
defineReactive(vm, key, obj[key]);
})
}
/**
* 編譯過(guò)程,給子節(jié)點(diǎn)初始化綁定vue實(shí)例中的屬性值
* @param node 子節(jié)點(diǎn)
* @param vm vue實(shí)例
* */
function compile (node, vm) {
let reg = /\{\{(.*)\}\}/
// 節(jié)點(diǎn)類(lèi)型為元素
if (node.nodeType === 1) {
let attr = node.attributes
// 解析屬性
for (let i = 0; i < attr.length; i++) {
if (attr[i].nodeName === 'v-model') {
// 獲取v-model綁定的屬性名
let name = attr[i].nodeValue
// 添加監(jiān)聽(tīng)事件
node.addEventListener('input', function (e) {
// 給相應(yīng)的data屬性賦值哥艇,進(jìn)而觸發(fā)該屬性的set方法
vm[name] = e.target.value;
});
// 將data的值賦給該node
node.value = vm.data[name];
node.removeAttribute('v-model')
}
}
}
// 節(jié)點(diǎn)類(lèi)型為text
if (node.nodeType === 3) {
if (reg.test(node.nodeValue)) {
// 獲取匹配到的字符串
let name = RegExp.$1
name = name.trim()
// 將data的值賦給該node
node.nodeValue = vm.data[name]
}
}
}
/**
* DocumentFragment文檔片段绝编,可以看作節(jié)點(diǎn)容器,它可以包含多個(gè)子節(jié)點(diǎn),當(dāng)將它插入到dom中時(shí)只有子節(jié)點(diǎn)插入到目標(biāo)節(jié)點(diǎn)中十饥。
* 使用documentfragment處理節(jié)點(diǎn)速度和性能要高于直接操作dom窟勃。vue編譯的時(shí)候,就是將掛載目標(biāo)的所有子節(jié)點(diǎn)劫持到documentfragment
* 中逗堵,經(jīng)過(guò)處理后再將documentfragment整體返回到掛載目標(biāo)中秉氧。
* @param node 節(jié)點(diǎn)
* @param vm vue實(shí)例
* */
function nodeToFragment (node, vm) {
var flag = document.createDocumentFragment();
var child;
while (child = node.firstChild) {
compile(child, vm);
flag.appendChild(child);
}
return flag;
}
/*vue類(lèi)*/
function Vue (options) {
this.data = options.data
let data = this.data
// 給vue實(shí)例的data定義訪問(wèn)器屬性,覆蓋原來(lái)的同名屬性
observe(data, this)
let id = options.el
let dom = nodeToFragment(document.getElementById(id), this)
// 編譯蜒秤,劫持完成后將dom返回到app中
document.getElementById(id).appendChild(dom)
}
/*定義一個(gè)vue實(shí)例*/
let vm = new Vue({
el: 'app',
// 這里的data屬性不是訪問(wèn)器屬性
data: {
text: 'hello world!'
}
})
</script>
</body>
</html>
下面不再逐句分析谬运,只說(shuō)重點(diǎn)的。
- 在defineReactive方法中垦藏,vue實(shí)例中的data的屬性重新定義為訪問(wèn)器屬性梆暖,并在set方法中將新的值更新到這個(gè)屬性上
- 在observe方法中,遍歷vue實(shí)例中data的屬性掂骏,逐一調(diào)用defineReactive方法轰驳,把他們定義為訪問(wèn)器屬性
- 在compile方法中,如果是input這樣的標(biāo)簽弟灼,給它添加事件(也可以是keyup级解,change),監(jiān)聽(tīng)input值變化田绑,并給vue實(shí)例中相應(yīng)的訪問(wèn)器屬性賦值
- 在Vue類(lèi)方法中勤哗,調(diào)用observer方法,傳入當(dāng)前實(shí)例對(duì)象和對(duì)象的data屬性掩驱,將data屬性中的子元素重新定義為當(dāng)前對(duì)象的訪問(wèn)器屬性
set方法被觸發(fā)之后芒划,vue實(shí)例的text屬性跟著變化,但是<span>的內(nèi)容并沒(méi)有變化欧穴,下面的內(nèi)容將會(huì)介紹“訂閱/發(fā)布模式”來(lái)解決這個(gè)問(wèn)題民逼。
4 雙向綁定的實(shí)現(xiàn)
在實(shí)現(xiàn)雙向綁定之前要先學(xué)習(xí)一下“訂閱/發(fā)布模式”。訂閱發(fā)布模式(又稱(chēng)為觀察者模式)定義一種一對(duì)多的關(guān)系涮帘,讓多個(gè)觀察者同時(shí)監(jiān)聽(tīng)一個(gè)主題對(duì)象拼苍,主題對(duì)象狀態(tài)發(fā)生改變的時(shí)候通知所有的觀察者。
發(fā)布者發(fā)出通知 => 主題對(duì)象收到通知并推送給訂閱者 => 訂閱者執(zhí)行相應(yīng)的操作
看下面的代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>訂閱/發(fā)布模式</title>
</head>
<body>
<script>
/**
* 定義一個(gè)發(fā)布者publisher
* */
var pub = {
publish: function () {
dep.notify();
}
}
/**
* 三個(gè)訂閱者
* */
var sub1 = {
update: function () {
console.log(1);
}
};
var sub2 = {
update: function () {
console.log(2);
}
};
var sub3 = {
update: function () {
console.log(3);
}
}
/**
* 一個(gè)主題對(duì)象
* */
function Dep () {
this.subs = [sub1, sub2, sub3];
}
Dep.prototype.notify = function () {
this.subs.forEach(function (sub) {
sub.update();
})
}
// 發(fā)布者發(fā)布消息调缨,主題對(duì)象執(zhí)行notifiy方法疮鲫,觸發(fā)所有訂閱者響應(yīng),執(zhí)行update
var dep = new Dep();
pub.publish();
</script>
</body>
</html>
- 定義發(fā)布者對(duì)象pub弦叶,對(duì)象中定義publish方法俊犯,方法調(diào)用主題對(duì)象實(shí)例dep的notify()方法
- 定義三個(gè)訂閱者對(duì)象,對(duì)象中定義update方法湾蔓,三個(gè)對(duì)象的update方法分別輸出1瘫析,2,3
- 定義一個(gè)主題方法類(lèi),主題對(duì)象中定義數(shù)組屬性subs贬循,包含三個(gè)訂閱者對(duì)象
- 在主題方法類(lèi)的原型對(duì)象上定義通知方法notify咸包,方法中循環(huán)調(diào)用三個(gè)訂閱者對(duì)象的update()方法
- 實(shí)例化主題方法類(lèi)得到實(shí)例dep
- 調(diào)用發(fā)布者對(duì)象的通知方法notifiy(),分別輸出1杖虾,2烂瘫,3
每當(dāng)創(chuàng)建一個(gè)Vue實(shí)例的時(shí)候,主要做了兩件事情奇适,第一是監(jiān)聽(tīng)數(shù)據(jù):observe(data)坟比,第二個(gè)是編譯HTML:nodeToFragment(id)。
在監(jiān)聽(tīng)數(shù)據(jù)過(guò)程中嚷往,為data的每一個(gè)屬性生成主題對(duì)象dep葛账。
在編譯HTML的過(guò)程中,為每個(gè)與數(shù)據(jù)綁定相關(guān)的節(jié)點(diǎn)生成一個(gè)訂閱者watcher皮仁,watcher會(huì)將自己添加到相應(yīng)屬性的dep中籍琳。
前面已經(jīng)實(shí)現(xiàn)了:修改輸入框內(nèi)容 => 在事件回調(diào)函數(shù)中修改屬性值 => 觸發(fā)屬性set方法。
接下來(lái)我們要實(shí)現(xiàn)的是:發(fā)出通知dep.notify() => 觸發(fā)訂閱者的updata方法 => 更新視圖贷祈,實(shí)現(xiàn)這個(gè)目標(biāo)的關(guān)鍵是如何將watcher添加到關(guān)聯(lián)屬性的dep中去趋急。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>雙向綁定的實(shí)現(xiàn)</title>
</head>
<body>
<div id="app">
<input type="text" v-model="text">
{{ text }}
</div>
<script>
/**
* 使用defineProperty將data中的text設(shè)置為vm的訪問(wèn)器屬性
* @param obj 對(duì)象
* @param key 屬性名
* @param val 屬性值
*/
function defineReactive (obj, key, val) {
var dep = new Dep();
Object.defineProperty(obj, key, {
get: function () {
// 如果主題對(duì)象類(lèi)的靜態(tài)屬性target有值, 此時(shí)Watcher方法被調(diào)用势誊,給主題對(duì)象添加訂閱者
if (Dep.target) dep.addSub(Dep.target);
return val;
},
set: function (newVal) {
if (newVal === val) return
val = newVal;
// 主題對(duì)象作為發(fā)布者收到通知推送給訂閱者
dep.notify();
}
})
}
/**
* 給vue實(shí)例定義訪問(wèn)器屬性
* @param obj vue實(shí)例中的數(shù)據(jù)
* @param vm vue對(duì)象
*/
function observe (obj, vm) {
Object.keys(obj).forEach(function (key) {
defineReactive(vm, key, obj[key])
})
}
/**
* DocumentFragment文檔片段呜达,可以看作節(jié)點(diǎn)容器,它可以包含多個(gè)子節(jié)點(diǎn)粟耻,當(dāng)將它插入到dom中時(shí)只有子節(jié)點(diǎn)插入到目標(biāo)節(jié)點(diǎn)中查近。
* 使用documentfragment處理節(jié)點(diǎn)速度和性能要高于直接操作dom。vue編譯的時(shí)候勋颖,就是將掛載目標(biāo)的所有子節(jié)點(diǎn)劫持到documentfragment
* 中嗦嗡,經(jīng)過(guò)處理后再將documentfragment整體返回到掛載目標(biāo)中。
* @param node 節(jié)點(diǎn)
* @param vm vue實(shí)例
* */
function nodeToFragment (node, vm) {
var flag = document.createDocumentFragment();
var child;
while (child = node.firstChild) {
compile(child, vm);
flag.appendChild(child);
}
return flag;
}
/**
* 給子節(jié)點(diǎn)初始化綁定vue實(shí)例中的屬性值
* @param node 子節(jié)點(diǎn)
* @param vm vue實(shí)例
*/
function compile (node, vm) {
var reg = /\{\{(.*)\}\}/;
// 節(jié)點(diǎn)類(lèi)型為元素
if (node.nodeType === 1) {
var attr = node.attributes;
// 解析屬性
for (var i = 0; i < attr.length; i++) {
if (attr[i].nodeName === 'v-model') {
// 獲取v-model綁定的屬性名
var name = attr[i].nodeValue;
node.addEventListener('input', function (e) {
// 給相應(yīng)的data屬性賦值饭玲,觸發(fā)set方法
vm[name] = e.target.value
});
// 將data的值賦給該node
node.value = vm[name];
node.removeAttribute('v-model');
}
}
new Watcher(vm, node, name, 'input')
}
if (node.nodeType === 3) {
if (reg.test(node.nodeValue)) {
var name = RegExp.$1; // 獲取匹配到的字符串
name = name.trim();
// 將data的值賦給該node
new Watcher(vm, node, name, 'text');
}
}
}
/**
* 編譯 HTML 過(guò)程中,為每個(gè)與 data 關(guān)聯(lián)的節(jié)點(diǎn)生成一個(gè) Watcher
* @param vm
* @param node
* @param name
* @param nodeType
* @constructor
*/
function Watcher (vm, node, name, nodeType) {
// 將當(dāng)前對(duì)象賦值給全局變量Dep.target
Dep.target = this;
this.name = name;
this.node = node;
this.vm = vm;
this.nodeType = nodeType;
this.update();
Dep.target = null;
}
Watcher.prototype = {
update: function () {
this.get();
if (this.nodeType === 'text') {
this.node.nodeValue = this.value;
}
if (this.nodeType === 'input') {
this.node.value = this.value;
}
},
get: function () {
this.value = this.vm[this.name];
}
}
/**
* 定義一個(gè)主題對(duì)象
* @constructor
*/
function Dep () {
this.subs = [];
}
/**
* 定義主題對(duì)象的添加方法和通知變化方法
* @type {{addSub: Dep.addSub, notify: Dep.notify}}
*/
Dep.prototype = {
addSub: function (sub) {
this.subs.push(sub);
},
notify: function () {
this.subs.forEach(function (sub) {
sub.update();
});
}
};
/**
* 定義Vue類(lèi)
* @param options Vue參數(shù)選項(xiàng)
* @constructor
*/
function Vue (options) {
this.data = options.data;
var data = this.data;
observe(data, this);
var id = options.el;
var dom = nodeToFragment(document.getElementById(id), this);
// 編譯完成后叁执,將dom返回到app中
document.getElementById(id).appendChild(dom);
}
// 定義Vue實(shí)例
var vm = new Vue({
el: 'app',
data: {
text: 'hello world'
}
})
</script>
</body>
</html>
這里不再逐句分析茄厘,只把重點(diǎn)說(shuō)明一下
- 定義主題對(duì)象Dep,對(duì)象中有addSub和notify兩個(gè)方法谈宛,前者負(fù)責(zé)向當(dāng)前對(duì)象中添加訂閱者次哈,后者輪詢(xún)訂閱者,調(diào)用訂閱者的更新方法update()
- 定義觀察者對(duì)象方法Watcher吆录,在方法中先將自己賦給一個(gè)全局變量Dep.target窑滞,其實(shí)是給主題類(lèi)Dep定義了一個(gè)靜態(tài)屬性target,可以直接使用Dep.target訪問(wèn)這個(gè)靜態(tài)屬性。然后給類(lèi)定義共有屬性name(vue實(shí)例中的訪問(wèn)器屬性名“text”)哀卫,node(html標(biāo)簽巨坊,如<input>,{{text}})此改,vm(當(dāng)前vue實(shí)例)趾撵,nodeType(html標(biāo)簽類(lèi)型),其次執(zhí)行update方法共啃,進(jìn)而執(zhí)行了原型對(duì)象上的get方法占调,get方法中的this.vm[this.name]讀取了vm中的訪問(wèn)器屬性,從而觸發(fā)了訪問(wèn)器屬性的get方法移剪,get方法中將wathcer添加到對(duì)應(yīng)訪問(wèn)器屬性的dep中究珊,同時(shí)將屬性值賦給臨時(shí)變量value。再者纵苛,獲取屬性的值(保存在臨時(shí)變量value中)苦银,然后更新視圖。最后將Dep.target設(shè)為空赶站。因?yàn)樗侨肿兞酷B玻彩莣atcher與dep關(guān)聯(lián)的唯一橋梁,任何時(shí)刻都必須保證Dep.target只有一個(gè)值贝椿。
- 在編譯方法compile中想括,劫持子節(jié)點(diǎn)的時(shí)候,在節(jié)點(diǎn)上定義一個(gè)觀察者對(duì)象Watcher
- defineReactive方法中烙博,定義訪問(wèn)器屬性的時(shí)候瑟蜈,在存取描述符get中,如果主題對(duì)象類(lèi)的靜態(tài)屬性target有值渣窜, 此時(shí)Watcher方法被調(diào)用铺根,給主題對(duì)象添加訂閱者。
data中的數(shù)據(jù)重新定義為訪問(wèn)器屬性乔宿,get中將當(dāng)前數(shù)據(jù)對(duì)應(yīng)的節(jié)點(diǎn)添加到主題對(duì)象中位迂,set方法中通知數(shù)據(jù)對(duì)應(yīng)的節(jié)點(diǎn)更新。編譯過(guò)程將data數(shù)據(jù)生成數(shù)據(jù)節(jié)點(diǎn)详瑞,并生成一個(gè)觀察者來(lái)觀察節(jié)點(diǎn)變化掂林。
總結(jié)
本文介紹了vue.js的簡(jiǎn)單實(shí)現(xiàn)以及相關(guān)的知識(shí),包含MVC坝橡,MVP泻帮,MVVM的原理,對(duì)象的訪問(wèn)器屬性计寇,html的文檔片段(DocumentFragment)锣杂,觀察者模式脂倦。vue.js的實(shí)現(xiàn)主要介紹數(shù)據(jù)編譯(compile),通過(guò)文檔片段實(shí)現(xiàn)數(shù)據(jù)劫持掛載元莫,通過(guò)觀察者模式(訂閱發(fā)布模式)的實(shí)現(xiàn)數(shù)據(jù)雙向綁定等內(nèi)容赖阻。
參考
https://www.cnblogs.com/tylerdonet/p/9893065.html
http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html