本來vue的響應式應該才是重中之重。但是網上的文章很多很多。在看computed的實現之前。肯定還是要把vue的響應式如何實現好好看一下。或者説兩者根本就是一樣的東西。這邊推薦幾篇文章關於vue的響應式。
vue響應式簡單實現
vue慕課響應式手記
還是看看官網對於響應式的解釋:
總的來説。vue實現響應式的關鍵有三個:watcher,dep,observe;
-
observe:遍歷data中的屬性。在get,set方法中設置核心數據劫持
-
dep:每個屬性都有一個自己的dep(消息訂閲起)用於訂製該屬性上的所有觀察者
-
watcher:觀察者,通過dep實現對響應屬性的監聽觀察。觀察得到結果後,主動觸發自己的回調
可以去看看vue2.3的這三部分源碼。中間還是有很多精美的設計。比如一個全局唯一的Dep.target,在任何時候都是唯一的值。以確保同一時間只有一個觀察者在訂閲。再比如,watcher中也會存下相關的訂閲器,實現去重和實現同一個觀察者的分組(這裏是實現computed的關鍵),再如。watcher中的id也會唯一。用於異步更新的時候不同時出發相同的訂閲。仔細看看會收穫不小。改天我把所有的響應式的代碼也整理一下。
在理解了響應式的情況下。我們來看看computed的實現。最簡單的一個demo如下:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<div id="app">
<div name="test">{{computeA}}</div>
</div>
</body>
<script src="vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#app',
data: function () {
return {
firstName: 111,
lastName: 222
}
},
computed: {
computeA: function () {
return this.firstName + ' ' + this.lastName
}
},
created(){
setTimeout(
() => {
this.firstName = 333;
},1000
)
}
})
</script>
</html>
我們來從源碼的角度看看發生了什麼:
-
在初始化實例創建響應式的時候。對options中的computed做了特殊處理:
function initComputed (vm, computed) {
var watchers = vm._computedWatchers = Object.create(null);
for (var key in computed) {
var userDef = computed[key];
var getter = typeof userDef === 'function' ? userDef : userDef.get;
{
if (getter === undefined) {
warn(
("No getter function has been defined for computed property \"" + key + "\"."),
vm
);
getter = noop;
}
}
// create internal watcher for the computed property.
watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions);//為每一個computed項目訂製一個watcher
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
if (!(key in vm)) {
defineComputed(vm, key, userDef);
} else {
if (key in vm.$data) {
warn(("The computed property \"" + key + "\" is already defined in data."), vm);
} else if (vm.$options.props && key in vm.$options.props) {
warn(("The computed property \"" + key + "\" is already defined as a prop."), vm);
}
}
function defineComputed (target, key, userDef) {
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = createComputedGetter(key);
sharedPropertyDefinition.set = noop;
} else {
sharedPropertyDefinition.get = userDef.get
? userDef.cache !== false
? createComputedGetter(key)
: userDef.get
: noop;
sharedPropertyDefinition.set = userDef.set
? userDef.set
: noop;
}
Object.defineProperty(target, key, sharedPropertyDefinition);
}
function createComputedGetter (key) {//構造該computed的get函數
return function computedGetter () {
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) {
watcher.evaluate();//收集該watcher的訂閲
}
if (Dep.target) {
watcher.depend();//同一為這一組訂閲再加上組件re-render的訂閲(該訂閲負責更新組件)
}
return watcher.value
}
}
}
總的來説。理解了響應式的構建之後。再來看computed的實現還是很直觀的。組件初始化的時候。computed項和data中的分別建立響應式。data中的數據直接對屬性的get,set做數據攔截。而computed則建立一個新的watcher,在組件渲染的時候。先touch一下這個computed的getter函數。將這個watcher訂閲起來。這裏相當於這個computed的watcher訂閲了firstname和lastname。touch完後。Dep.target此時又變為之前那個用於更新組件的。再通過watcher.depend()將這個組統一加上這個訂閲。這樣一旦firstname和lastname變了。同時會觸發兩個訂閲更新。其中一個便是更新組件。重新re-render的函數。感覺看的還不夠細啊