一文徹底搞懂Vue的MVVM響應(yīng)式原理
前言
這些天都在面試,每當(dāng)我被面試官們問到Vue響應(yīng)式原理時,回答得都很膚淺。如果您在回答時也只是停留在MVVM框架是model層、view層和viewmodel層這樣的雙向數(shù)據(jù)綁定,那么建議您徹底搞定Vue的MVVM響應(yīng)式原理。
(全文約13900字,閱讀時間約25分鐘。建議有一定vue基礎(chǔ)后再閱讀)
怎么來的?
要想清楚的知道某件事物的原理,就該追根溯源,刨根問底。在Vue之前,各框架都是怎么去實(shí)現(xiàn)MVVM雙向綁定的呢?
大致分為以下幾種:
- 發(fā)布者-訂閱者模式(backbone.js)臟值檢查(angular.js)數(shù)據(jù)劫持(vue.js)
- 發(fā)布者-訂閱者模式,通過sub、pub實(shí)現(xiàn)視圖的監(jiān)聽綁定,通常的做法是vm.$set(‘property’, value)。
臟值檢查,內(nèi)部其實(shí)就是setnterval,當(dāng)然,為了節(jié)約性能,不顯的那么low,一般是對特定的事件執(zhí)行臟值檢查:
DOM事件,如輸入文本、點(diǎn)擊按鈕(ng-click)XHR響應(yīng)事件($http)瀏覽器locaton 變更事件($location)Timer事件($timeout, $interval)執(zhí)行$digest()或 $apply()
vue則是采用發(fā)布者-訂閱者模式,通過Object.defineProperty()來劫持各個屬性的getter和setter,在數(shù)據(jù)變動時發(fā)布消息給訂閱者,觸發(fā)相應(yīng)的監(jiān)聽回調(diào)。
Vue的MVVM原理
話不多說,先上圖

首先,請盡可能記住這一張圖,并能夠自己畫出來,后面所有原理都是圍繞這張圖展開。感覺很懵逼對么?不過,相信許多人在Vue官方文檔里看過這張圖:

其實(shí),這兩張圖要表達(dá)的是一個意思——二者都表示了雙向數(shù)據(jù)綁定的原理流程,官方文檔中展示的更為簡潔一些。看您更能接受哪種描述,后面自己實(shí)現(xiàn)響應(yīng)式原理后,這兩張圖都能記得住了。
這里就用第一張圖來介紹,在我們創(chuàng)建一個vue實(shí)例時,其實(shí)vue做了這些事情:
創(chuàng)建了入口函數(shù),分別new了一個數(shù)據(jù)觀察者Observer和一個指令解析器Compile;Compile解析所有DOM節(jié)點(diǎn)上的vue指令,提交到更新器Updater(實(shí)際上是一個對象);Updater把數(shù)據(jù)(如{{}},msg,@click)替換,完成頁面初始化渲染;Observer使用Object.defineProperty劫持?jǐn)?shù)據(jù),其中的getter和setter通知變化給依賴器Dep;Dep中加入觀察者Watcher,當(dāng)數(shù)據(jù)發(fā)生變化時,通知Watcher更新;Watcher取到舊值和新值,在回調(diào)函數(shù)中通知Updater更新視圖;Compile中每個指令都new了一個Watcher,用于觸發(fā)Watcher的回調(diào)函數(shù)進(jìn)行更新。 簡單實(shí)現(xiàn)Vue的響應(yīng)式原理 完整源碼:詳見
按照前面的思路,下面我們來一步一步實(shí)現(xiàn)一個簡單的MVVM響應(yīng)式系統(tǒng),加深我們對響應(yīng)式原理的理解。
創(chuàng)建一個html示例
現(xiàn)在我們創(chuàng)建了一個簡單的Vue渲染示例,我們要做的就是使用自己的MVue去把里面的data、msg、htmlStr、methods中的數(shù)據(jù)都渲染到標(biāo)簽上。完成數(shù)據(jù)驅(qū)動視圖、視圖驅(qū)動數(shù)據(jù)驅(qū)動視圖。
<!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 ="app">
<h2>{{person.name}} -- {{person.age}}</h2>
<h3>{{person.fav}}</h3>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<h3>{{msg}}</h3>
<div v-text="person.fav"></div>
<div v-text="msg"></div>
<div v-html="htmlStr"></div>
<input type="text" v-model="msg">
<button v-on:click="handleClick">click</button>
<button @click="handleClick">@click2</button>
</div>
<script src="./Observer.js"></script>
<script src="./MVue.js"></script>
<script>
//創(chuàng)建Vue實(shí)例,得到 ViewModel
const vm = new MVue({
el: '#app',
data: {
person: {
name: "我的vue",
age: 18,
fav: "坦克世界"
},
msg: "學(xué)習(xí)MVVM框架原理",
htmlStr: "<h3>熱愛前端,金子總會發(fā)光</h3>"
},
methods: {
handleClick() {
console.log(this);
}
}
});
</script>
</body>
</html>在MVue.js中創(chuàng)建MVue入口
class MVue {
constructor(options) {
this.$el = options.el;
this.$data = options.data;
this.$options = options;
if (this.$el) {
// 1、實(shí)現(xiàn)一個數(shù)據(jù)觀察者
// 2、實(shí)現(xiàn)一個指令觀察者
new Compile(this.$el, this);
}
}思路:
首先自然是要構(gòu)建MVue這一個類,MVue類構(gòu)造函數(shù)中需要用到options參數(shù)和其中的el、data。
然后需要保證el存在條件下,先實(shí)現(xiàn)一個指令解析器Compile,后面再去實(shí)現(xiàn)Observer觀察者。
顯然,Compile應(yīng)該需要傳入MVue實(shí)例的el和整個MVue實(shí)例,用來解析標(biāo)簽的指令。
創(chuàng)建Compile
思路:在解析標(biāo)簽指令之前,我們首先做的是:
判斷el是不是元素節(jié)點(diǎn),如果不是,就要取到el這個標(biāo)簽,然后傳入vm實(shí)例;遞歸拿到所有子節(jié)點(diǎn),便于下一步去解析它們?!咀⒁猓哼@一步會頻繁觸發(fā)頁面的回流和重繪,所以我們需要把節(jié)點(diǎn)先存入文檔碎片對象中,就相當(dāng)于把他們放到了內(nèi)存中,減少了頁面的回流和重繪?!吭谖臋n碎片對象中編譯好模板;最后再把文檔碎片對象追加到根元素上。
class Compile {
constructor(el, vm) {
this.el = this.isElementNode(el) ? el : document.querySelector(el);
this.vm = vm;
// 獲取文檔碎片對象 放入內(nèi)存中會減少頁面的回流和重繪
const fragment = this.node2Fragment(this.el);
// 編譯模板
this.compile(fragment);
// 追加子元素到根元素
this.el.appendChild(fragment);
}這里我們先自己定義了幾個方法:
- 判斷是否是元素節(jié)點(diǎn)
isElementNode(el)、 - 存入文檔碎片對象
node2Fragment(el)、 - 編譯模板
compile(fragment)
分別在構(gòu)造函數(shù)之后去實(shí)現(xiàn):
node2Fragment(el) {
// 創(chuàng)建文檔碎片對象
const f = document.createDocumentFragment();
// 遞歸放入
let firstChild;
while ((firstChild = el.firstChild)) {
f.appendChild(firstChild);
}
return f;
}
isElementNode(node) {
return node.nodeType === 1;
}編譯模板compile(fragment)實(shí)現(xiàn)思路:遞歸獲取所有子節(jié)點(diǎn),判斷節(jié)點(diǎn)是元素節(jié)點(diǎn)還是文本節(jié)點(diǎn),再分別定義兩個方法compileElement(child)和compileText(child)去處理這兩種節(jié)點(diǎn)。
compile(fragment) {
// 獲取子節(jié)點(diǎn)
const childNodes = fragment.childNodes;
[...childNodes].forEach((child) => {
if (this.isElementNode(child)) {
// 是元素節(jié)點(diǎn)
// 編譯元素節(jié)點(diǎn)
// console.log("元素節(jié)點(diǎn)",child);
this.compileElement(child);
} else {
// 是文本節(jié)點(diǎn)
// 編譯文本節(jié)點(diǎn)
// console.log("文本節(jié)點(diǎn)", child);
this.compileText(child);
}
// 一層一層遞歸遍歷
if (child.childNodes && child.childNodes.length) {
this.compile(child);
}
});
}好了,現(xiàn)在Compile的一個基本框架已經(jīng)搭好了。希望看到這里的您還沒有犯困,打起精神來!現(xiàn)在,我們繼續(xù)往下淦元素節(jié)點(diǎn)和文本節(jié)點(diǎn)的處理。
1.處理元素節(jié)點(diǎn)compileElement(child)
思路:
拿到標(biāo)簽里的每個vue指令,如v-text v-html v-model v-on:click,顯然它們都是以v-開頭的,當(dāng)然還有@開頭的指令也不要忘記把節(jié)點(diǎn)、節(jié)點(diǎn)值、vm實(shí)例、(on的事件名)傳入compileUtil對象,后面用它處理每個指令,屬性對應(yīng)指令方法;別忘了,最后的視圖標(biāo)簽上是沒有vue指令的,所以我們要把它們從節(jié)點(diǎn)屬性中刪去。
compileElement(node) {
const attributes = node.attributes;
[...attributes].forEach((attr) => {
const { name, value } = attr;
if (this.isDirective(name)) {
// 是一個指令 v-text v-html v-model v-on:click
const [, directive] = name.split("-"); // text html model on:click
const [dirName, eventName] = directive.split(":"); // text html model on
// 更新數(shù)據(jù) 數(shù)據(jù)驅(qū)動視圖
compileUtil[dirName](node, value, this.vm, eventName);
// 刪除有指令標(biāo)簽上的屬性
node.removeAttribute("v-" + directive);
} else if (this.isEventName(name)) {
// @click='handleClick'
let [, eventName] = name.split('@');
compileUtil["on"](node, value, this.vm, eventName);
}
});
}判斷是否是指令,以v-開頭
isDirective(attrName) {
return attrName.startsWith("v-");
}2.處理文本節(jié)點(diǎn)compileText(child)
主要使用正則匹配雙大括號即可:
compileText(node) {
// {{}} v-text
const content = node.textContent;
if (/\{\{(.+?)\}\}/.test(content)) {
compileUtil["text"](node, content, this.vm);
}
}3.實(shí)現(xiàn)compileUtil指令處理
思路:
每個指令對應(yīng)各自方法,除了on需要額外傳入事件名稱,其他的指令處理函數(shù)只需要傳節(jié)點(diǎn)、值(或表達(dá)式expr)、vm
實(shí)例:
const compileUtil = {
text(node, expr, vm) {
},
html(node, expr, vm) {
},
model(node, expr, vm) {
},
on(node, expr, vm, eventName) {
}
};沒有一下子放出代碼來的話,骨架原來這么簡單啊,繼續(xù)逐個擊破它們!
v-html指令處理,思路:拿到值,把值傳給updater更新器,更新,完事兒。
html(node, expr, vm) {
const value = this.getVal(expr, vm);
this.updater.htmlUpdater(node, value);
},v-model指令處理,同上。先實(shí)現(xiàn)數(shù)據(jù)=>視圖這條線,雙向綁定最后實(shí)現(xiàn)。
model(node, expr, vm) {
const value = this.getVal(expr, vm);
this.updater.modelUpdater(node, value);
},比較復(fù)雜的,v-on,思路:獲取事件名,從methods中取到對應(yīng)的函數(shù),添加到事件中,注意this要綁定給vm實(shí)例,false默認(rèn)事件冒泡。
on(node, expr, vm, eventName) {
// 獲取事件名, 從method里面取函數(shù)
let fn = vm.$options.methods && vm.$options.methods[expr];
node.addEventListener(eventName, fn.bind(vm), false)
},v-text指令處理:
text(node, expr, vm) {
// expr:msg: "學(xué)習(xí)MVVM框架原理"
// 對傳入不同的字符串不同操作 <div v-text="person.name"></div>
// {{}}
let value;
if (expr.indexOf('{{') !== -1) {
// {{person.name}} -- {{person.age}}
value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
return this.getVal(args[1], vm)
})
} else {
value = this.getVal(expr, vm);
}
this.updater.textUpdater(node, value);
},用到args這個數(shù)組,console.log一下args,發(fā)現(xiàn)args[1]就有我們要找的具體屬性:


例如,取到person.name后,傳入到this.getVal('person.name',vm),最后能取到vm.$data.person.name。
怎么拿到它們對應(yīng)的值呢?
顯然,不論是htmlStr、msg、person,它們都在實(shí)例vm的data內(nèi),在自定義方法getVal中,可以使用split分割小圓點(diǎn)“.”得到數(shù)組,再用高逼格的reduce方法去遍歷找到data每個屬性(對象)下的每個屬性的值,像這樣:
getVal(expr, vm) {
return expr.split(".").reduce((data, currentVal) => {
return data[currentVal];
}, vm.$data);
},(不記得怎么用reduce?請?jiān)谟疑辖切陆?biāo)簽頁,去CSDN上補(bǔ)一補(bǔ)。)進(jìn)階拿到雙大括號內(nèi)對應(yīng)的屬性的值:
getContentVal(expr, vm) {
return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
console.log(args);
return this.getVal(args[1], vm);
});
},更新器Updater更新數(shù)據(jù)
在指令方法的后面接著創(chuàng)建一個updater屬性,實(shí)則是一個類,我們把它親切地稱作更新器,長得還很一目了然,您馬上就能記住它的樣子:
// 更新的函數(shù)
updater: {
textUpdater(node, value) {
node.textContent = value;
},
htmlUpdater(node, value) {
node.innerHTML = value;
},
modelUpdater(node, value){
node.value = value;
}
},在每個指令方法取到值后,更新到node節(jié)點(diǎn)上。
至此,我們已經(jīng)完成了原理圖上的MVVM到Compile到Updater這一條線:

實(shí)現(xiàn)數(shù)據(jù)觀察者Observer
class MVue {
constructor(options) {
this.$el = options.el;
this.$data = options.data;
this.$options = options;
if (this.$el) {
// 1、實(shí)現(xiàn)一個數(shù)據(jù)觀察者
new Observer(this.$data);
// 2、實(shí)現(xiàn)一個指令觀察者
new Compile(this.$el, this);
}
}Observer類構(gòu)造函數(shù)應(yīng)該傳什么給它?對,Observer要監(jiān)聽所有數(shù)據(jù),所以我們將vm實(shí)例的data作為參數(shù)傳入。
- 遞歸,將data中所有的屬性、對象、子對象……都遍歷出來
- 對每個key,使用Object.defineProperty劫持?jǐn)?shù)據(jù)(Object.defineProperty()的作用就是直接在一個對象上定義一個新屬性,或者修改一個已經(jīng)存在的屬性)
- Object.defineProperty下有g(shù)et方法和set方法,也就是官方原理圖上的getter和stter啦
- 在劫持?jǐn)?shù)據(jù)之前,創(chuàng)建依賴器Dep實(shí)例dep
- 對于gettter,訂閱數(shù)據(jù)變化時,往dep中添加觀察者;
- 對于setter,當(dāng)數(shù)據(jù)變化時,將newVal賦值為新值,并用notify通知dep變化。(此處正好對應(yīng)官方原理圖)
4、5、6這最后三點(diǎn)可以說是MVVM實(shí)現(xiàn)中最關(guān)鍵、最巧妙的3步,正是這畫龍點(diǎn)睛的三筆,把整個系統(tǒng)橋梁成功架起來,注意它們各自放置在代碼中位置。
class Observer {
constructor(data) {
this.observer(data);
}
observer(data) {
/**
{
person:{
name:'張三',
fav: {
a: '愛好1',
b: '愛好2'
}
}
}
*/
if (data && typeof data === "object") {
Object.keys(data).forEach((key) => {
this.defineReactive(data, key, data[key]);
});
}
}
defineReactive(obj, key, value) {
// 遞歸遍歷
this.observer(value);
const dep = new Dep();
// 劫持?jǐn)?shù)據(jù)
Object.defineProperty(obj, key, {
// 是否可遍歷
enumerable: true,
// 是否可以更改編寫
configurable: false,
// 編譯之前,初始化的時候
get() {
// 訂閱數(shù)據(jù)變化時,往Dep中添加觀察者
Dep.target && dep.addSub(Dep.target);
return value;
},
// 外界修改數(shù)據(jù)的時候
set: (newVal) => {
// 新值也要劫持
this.observer(newVal); // 這里的this要指向當(dāng)前的實(shí)例,所以改用箭頭函數(shù)向上查找
// 判斷新值是否有變化
if (newVal !== value) {
value = newVal;
}
// 告訴Dep通知變化
dep.notify();
},
});
}
}數(shù)據(jù)依賴器Dep
主要作用:
- 收集要更新的觀察者
- 通知每個觀察者去更新
// 數(shù)據(jù)依賴器
class Dep {
constructor() {
this.subs = [];
}
// 收集觀察者
addSub(watcher) {
this.subs.push(watcher);
}
// 通知觀察者去更新
notify() {
console.log("通知了觀察者", this.subs);
this.subs.forEach(w =>w.update())
}
}觀察者Watcher
注意Dep.target = this;這一步,是為了把觀察者掛載到Dep實(shí)例上,關(guān)聯(lián)起來。所以當(dāng)觀察者Watcher獲取舊值后,應(yīng)該解除關(guān)聯(lián),否則會重復(fù)地添加觀察者,以下是未取消關(guān)聯(lián)的錯誤示范:

最后,使用callback回調(diào)函數(shù)傳遞要處理的新值給Updater即可。
class Watcher {
constructor(vm, expr, callback) {
// 把新值通過cb傳出去
this.vm = vm;
this.expr = expr;
this.callback = callback;
// 先把舊值保存起來
this.oldVal = this.getOldVal();
}
getOldVal() {
// 把觀察者掛載到Dep實(shí)例上,關(guān)聯(lián)起來
Dep.target = this;
const oldVal = compileUtil.getVal(this.expr, this.vm);
// 獲取舊值后,取消關(guān)聯(lián),就不會重復(fù)添加
Dep.target = null;
return oldVal;
}
update() {
// 更新,要取舊值和新值
const newVal = compileUtil.getVal(this.expr, this.vm);
if (newVal !== this.oldVal) {
this.callback(newVal);
}
}
}如何Updater如何接收從Watcher傳來的新值做回調(diào)處理呢?
只需要在剛剛寫好的compileUtil對象的每個指令處理方法內(nèi)都new(添加)一個Watcher實(shí)例即可。注意text指令方法下new Watcher實(shí)例的value參數(shù),可以用args[1]傳入,重新處理newVal。
const compileUtil = {
getVal(expr, vm) {
return expr.split(".").reduce((data, currentVal) => {
return data[currentVal];
}, vm.$data);
},
setVal(expr, vm, inputVal) {
return expr.split(".").reduce((data, currentVal) => {
data[currentVal] = inputVal; // 把當(dāng)前新值復(fù)制給舊值
}, vm.$data);
},
getContentVal(expr, vm) {
return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
console.log(args);
return this.getVal(args[1], vm);
});
},
text(node, expr, vm) {
// expr:msg: "學(xué)習(xí)MVVM框架原理"
// 對傳入不同的字符串不同操作 <div v-text="person.name"></div>
// {{}}
let value;
if (expr.indexOf('{{') !== -1) {
// {{person.name}} -- {{person.age}}
value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
new Watcher(vm, args[1], () => {
// 額外處理expr: {{person.name}} -- {{person.age}}
// 還要重新處理newVal
this.updater.textUpdater(node, this.getContentVal(expr, vm));
});
return this.getVal(args[1], vm)
})
} else {
value = this.getVal(expr, vm);
}
this.updater.textUpdater(node, value);
},
html(node, expr, vm) {
const value = this.getVal(expr, vm);
// 綁定觀察者,將來數(shù)據(jù)發(fā)生變化 出發(fā)這里的回調(diào) 進(jìn)行更新
new Watcher(vm, expr, newVal => {
this.updater.htmlUpdater(node, newVal);
})
this.updater.htmlUpdater(node, value);
},
model(node, expr, vm) {
const value = this.getVal(expr, vm);
// 綁定更新函數(shù) 數(shù)據(jù)=>驅(qū)動視圖
new Watcher(vm, expr, (newVal) => {
this.updater.modelUpdater(node, newVal);
});
// 視圖 => 數(shù)據(jù) => 視圖
node.addEventListener('input', e => {
// 設(shè)置值
this.setVal(expr, vm, e.target.value);
})
this.updater.modelUpdater(node, value);
},
on(node, expr, vm, eventName) {
// 獲取事件名, 從method里面取函數(shù)
let fn = vm.$options.methods && vm.$options.methods[expr];
node.addEventListener(eventName, fn.bind(vm), false)
},
bind(node, expr, vm, attrName) {
// 類似on。。。
},
// 更新的函數(shù)
updater: {
textUpdater(node, value) {
node.textContent = value;
},
htmlUpdater(node, value) {
node.innerHTML = value;
},
modelUpdater(node, value){
node.value = value;
}
},
};實(shí)現(xiàn)視圖驅(qū)動數(shù)據(jù)驅(qū)動視圖
還是借著上面這個代碼塊,我們只需要在model指令方法下,為input標(biāo)簽綁定事件,并自定義setVal方法為node賦值即可。
到這里,我們已經(jīng)基本完整實(shí)現(xiàn)了Vue的MVVM雙向數(shù)據(jù)綁定
小改進(jìn):
在MVue實(shí)例中,我們一開始使用的是$data獲取到數(shù)據(jù),這里可以做一層代理proxy,便于我們省略$data
methods: {
handleClick() {
// console.log(this);
this.person.name = "這是做了一層代理"
// 把this.$data 代理成 this
this.$data.person.name = "數(shù)據(jù)更改了"
}
}還是使用Object.defineProperty數(shù)據(jù)劫持,遍歷data下的每個key,讓getter返回data[key],setter設(shè)置data[key]直接等于newVal即可。
class MVue {
constructor(options) {
this.$el = options.el;
this.$data = options.data;
this.$options = options;
if (this.$el) {
// 1、實(shí)現(xiàn)一個數(shù)據(jù)觀察者
new Observer(this.$data);
// 2、實(shí)現(xiàn)一個指令觀察者
new Compile(this.$el, this);
this.proxyData(this.$data);
}
}
proxyData(data) {
for(const key in data) {
Object.defineProperty(this, key, {
get() {
return data[key]
},
set(newVal) {
data[key] = newVal;
}
})
}
}
}總結(jié)
再次體會官方文檔對響應(yīng)式原理的描述:
當(dāng)我們把一個普通的 JavaScript 對象傳入 Vue 實(shí)例作為 data 選項(xiàng),Vue 將遍歷此對象所有的 property,并使用 Object.defineProperty 把這些 property 全部轉(zhuǎn)為 getter/setter。Object.defineProperty 是 ES5 中一個無法 shim 的特性,這也就是 Vue 不支持 IE8 以及更低版本瀏覽器的原因。
這些 getter/setter 對用戶來說是不可見的,但是在內(nèi)部它們讓 Vue 能夠追蹤依賴,在 property
被訪問和修改時通知變更。這里需要注意的是不同瀏覽器在控制臺打印數(shù)據(jù)對象時對 getter/setter 的格式化并不同,所以建議安裝
vue-devtools 來獲取對檢查數(shù)據(jù)更加友好的用戶界面。每個組件實(shí)例都對應(yīng)一個 watcher 實(shí)例,它會在組件渲染的過程中把“接觸”過的數(shù)據(jù) property 記錄為依賴。之后當(dāng)依賴項(xiàng)的
setter 觸發(fā)時,會通知 watcher,從而使它關(guān)聯(lián)的組件重新渲染。
以及開頭時我自己總結(jié)的原理描述:
在我們創(chuàng)建一個vue實(shí)例時,其實(shí)vue做了這些事情:
創(chuàng)建了入口函數(shù),分別new了一個數(shù)據(jù)觀察者
- Observer和一個指令解析器Compile;
- Compile解析所有DOM節(jié)點(diǎn)上的vue指令,提交到更新器Updater(實(shí)際上是一個對象);
- Updater把數(shù)據(jù)(如{{}},msg,@click)替換,完成頁面初始化渲染;Observer使用Object.defineProperty劫持?jǐn)?shù)據(jù),其中的getter和setter通知變化給依賴器Dep;
- Dep中加入觀察者Watcher,當(dāng)數(shù)據(jù)發(fā)生變化時,通知Watcher更新;
- Watcher取到舊值和新值,在回調(diào)函數(shù)中通知Updater更新視圖;
- Compile中每個指令都new了一個Watcher,用于觸發(fā)Watcher的回調(diào)函數(shù)進(jìn)行更新。
在實(shí)現(xiàn)代碼的過程中,我們能深刻地體會到Vue的數(shù)據(jù)驅(qū)動視圖,視圖驅(qū)動數(shù)據(jù)驅(qū)動視圖 這一核心的巧妙,也知道了Object.defineProperty具體應(yīng)用場景。
到此這篇關(guān)于一文徹底搞懂Vue的MVVM響應(yīng)式原理的文章就介紹到這了,更多相關(guān) Vue的MVVM響應(yīng)式 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Vue2+Echarts實(shí)現(xiàn)多種圖表數(shù)據(jù)可視化Dashboard(附源碼)
本篇文章主要介紹了詳解Vue2+Echarts實(shí)現(xiàn)多種圖表數(shù)據(jù)可視化Dashboard(附源碼),具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-03-03
vue實(shí)現(xiàn)PC端錄音功能的實(shí)例代碼
這篇文章主要介紹了vue實(shí)現(xiàn)PC端錄音功能的實(shí)例代碼,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2019-06-06
快速解決Vue項(xiàng)目在IE瀏覽器中顯示空白的問題
今天小編就為大家分享一篇快速解決Vue項(xiàng)目在IE瀏覽器中顯示空白的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09
在vue中解決提示警告 for循環(huán)報(bào)錯的方法
今天小編就為大家分享一篇在vue中解決提示警告 for循環(huán)報(bào)錯的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09
vue實(shí)現(xiàn)用戶動態(tài)權(quán)限登錄的代碼示例
這篇文章主要介紹了vue如何實(shí)現(xiàn)用戶動態(tài)權(quán)限登錄,文中的代碼示例介紹的非常詳細(xì),對大家學(xué)習(xí)vue有一定的幫助,需要的朋友可以參考閱讀2023-05-05

