所謂Vue.js的響應式系統也就是指,在修改數據模型的時候,視圖會自動變化,而用戶不須要像命令式編程那樣再手動去操做視圖的變化。用高大上的說法就是:使用訂閱者模式,達成聲明式編程的目的。javascript
Vue.js的響應式系統很是適合用訂閱者模式的緣由就在於,每次在修改數據的時候,對應通知到使用到該數據的組件,就實現了響應式系統。java
那麼Vue.js 是如何實如今每次數據改變的時候,自動通知到對應的組件的呢?react
帶着這個問題,咱們來看下面的代碼演示編程
代碼只是拿來作展現原理的,真實的源代碼複雜的多,可是原理類似數組
首先咱們來定義兩個類:閉包
class Dep {
constructor () {
this.subs = [];
}
addSub (sub) {
this.subs.push(sub);
}
notify () {
this.subs.forEach((sub) => {
sub.update();
})
}
}
class Watcher {
constructor () {
Dep.target = this;
}
update () {
console.log("視圖更新啦~");
}
}
複製代碼
Dep
類是拿來存放訂閱者的容器,它裏面有個subs
數組拿來存放有哪些訂閱者,addSub(sub)
就是添加對應的訂閱者。notify()
則是通知全部的訂閱者,數據變化了。函數
Watcher
類是訂閱者類(又叫觀察者,二者是一樣的意思)。裏面的 update()
函數就是拿來作對應的視圖更新,具體的在這裏不展開。至於 Dep.target = this;
這行代碼到後面再解釋。性能
咱們再來看看 Vue.js
是如何將數據模型變得響應的,ui
function defineReactive (obj, key, val) {
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
dep.addSub(Dep.target);
return val;
},
set: function reactiveSetter (newVal) {
if (newVal === val) return;
val = newVal;
dep.notify();
}
});
}
複製代碼
這裏應用到了原生 JavaScript
的 Object.defineProperty()
函數,若是對於這塊不熟悉的,參考這裏this
defineReactive()
函數返回的時候,會生成一個閉包,而這個閉包裏面只有 dep
這一個屬性,而且該閉包只能被 obj[key]
屬性的 get
和 set
方法所使用。dep
就是拿來存放 obj[key]
這個數據所對應的訂閱者的。
你會發現,咱們添加訂閱者的操做就是在 get
方法中完成的,而爲何添加的訂閱者是 Dep.target
, 這點在後面解釋。
set
函數就是拿來修改數據,而且使用 dep.notify();
通知對應的訂閱者。
這個函數是對讓 Vue 實例中的 data
中的一個屬性變得響應的,那麼固然咱們須要將 data
中的全部屬性都變得響應式。這時候,就須要用到 observer()
函數了。
function observer (value) {
if (!value || (typeof value !== 'object')) {
return;
}
Object.keys(value).forEach((key) => {
defineReactive(value, key, value[key]);
});
}
複製代碼
observer()
這個函數很簡單,接受對應的 data
, 並將裏面的屬性都變得響應式。
好了,前面的基礎都打好了,如今咱們來看看 Vue
是如何在構造函數中,將整個響應式系統打造好的,其實也很是簡單:
class Vue {
constructor(options) {
this._data = options.data;
observer(this._data);
new Watcher();
console.log('render~', this._data.test);
}
}
let o = new Vue({
data: {
test: "I am test."
}
});
複製代碼
咱們先執行對應的observer()
函數,將 data
裏面的屬性都變得響應式。
而後再生成一個 Watcher() 對象,這裏就能夠解釋前面留下的問題,也就是爲何 Watcher
類的構造函數裏面要執行 Dep.target = this;
的操做,也就是將 Dep.target
指向在當前 Vue實例中生成的 Watcher
對象;而咱們留下的第二個問題,也就是爲何 dep.addSub(Dep.target);
添加的訂閱者是 Dep.target
, 其實也就是當前 Vue
實例中生成的 Watcher
對象。
可是這時 data
中的每一個屬性的訂閱者其實尚未添加,因此咱們要跑一次 render function
來獲取一次數據,也就是調用 data
每一個屬性的 get
方法。console.log('render~', this._data.test);
是拿來模擬 render function
的調用的。
這裏還有一個問題,那就是,若是 render function
被調用了屢次,那麼訂閱者就會被添加屢次,因此在最後還須要執行如下操做:
Dep.target = null;
複製代碼
你會發現,咱們這裏是對每一個 Vue
實例(或者 Vue 組件)使用一個 Watcher
對象,這是Vue2.0的特性;而在Vue1.0 中是針對每一個標籤實現一個Watcher
,數據過大時,就會有不少個watcher,會出現性能問題。