Vue computed實現原理深入講解
在從vue(v2.7.10)源碼分析vue是如何收集依賴和觸發(fā)依賴這篇文章中我們講了vue是怎么收集依賴的。其中computed的實現原理和這個密切相關,接下來我們看看computed的實現原理。
基礎代碼如下:
<template>
<div>
{{getA}}
<button @click="addModule">新增</button>
</div>
</template>
<script>
export default {
name: "TestWebpackTest",
mounted() {
console.log(this);
},
data() {
return {
num: 1,
a:2
};
},
computed:{
getA(){
return this.a
}
},
methods: {
addModule() {
this.a++;
}
}
};
</script>
<style lang="scss">
div {
.test {
width: 10px;
height: 15px;
background-color: blue;
}
}
</style>定義依賴
直接上代碼,在執(zhí)行render函數實例化TestWebpackTest組件的時候會執(zhí)行下面的方法:
Vue.extend = function (extendOptions) {
...
if (Sub.options.computed) {
initComputed(Sub);
}
...
};在該方法中會執(zhí)行initComputed方法,該方法遍歷computed并執(zhí)行defineComputed方法:
function initComputed(Comp) {
var computed = Comp.options.computed;
for (var key in computed) {
defineComputed(Comp.prototype, key, computed[key]);
}
}
var sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
};
// key: "getA" target: Vue {constructor: ?} userDef:? getA()
function defineComputed(target, key, userDef) {
var shouldCache = !isServerRendering(); // true
if (isFunction(userDef)) {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef);
sharedPropertyDefinition.set = noop;
}
else {
...
}
...
Object.defineProperty(target, key, sharedPropertyDefinition);
}
主要執(zhí)行了createComputedGetter方法設置computed函數getA的get方法:
// key: "getA"
function createComputedGetter(key) {
return function computedGetter() {
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
if (Dep.target.onTrack) {
Dep.target.onTrack({
effect: Dep.target,
target: this,
type: "get" /* TrackOpTypes.GET */,
key: key
});
}
watcher.depend();
}
return watcher.value;
}
};
}
該方法執(zhí)行過程在收集依賴部分分析。返回該函數后執(zhí)行Object.defineProperty(target, key, sharedPropertyDefinition),給當前的vm設置getA響應式。至此設置響應完畢。
收集依賴
收集依賴發(fā)生在執(zhí)行組件渲染過程,會通過_vm.getA觸發(fā)computed的get方法。
var render = function render() {
var _vm = this,
_c = _vm._self._c
return _c("div", [
_vm._v("\n " + _vm._s(_vm.getA) + "\n "),
_c("button", { on: { click: _vm.addModule } }, [_vm._v("新增")]),
])
}
會獲取當前的computed函數生成的watcher,繼續(xù)執(zhí)行watcher.evaluate()方法:
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) { // true
watcher.evaluate();
}
if (Dep.target) {
if (Dep.target.onTrack) {
Dep.target.onTrack({
effect: Dep.target,
target: this,
type: "get" /* TrackOpTypes.GET */,
key: key
});
}
watcher.depend();
}
return watcher.value;
}
Watcher.prototype.evaluate = function () {
this.value = this.get();
this.dirty = false;
};
調用當前watcher的get方法,首先設置當前的Dep.target為當前的watcher,然后執(zhí)行this.getter方法,該方法為getA方法,相當于執(zhí)行該函數。
Watcher.prototype.get = function () {
pushTarget(this);
var value;
var vm = this.vm;
try {
value = this.getter.call(vm, vm);
}
catch (e) {
...
}
finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value;
};
由于依賴于a變量,所以會觸發(fā)a變量的get方法,并執(zhí)行dep.depend方法
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
{
dep.depend({
target: obj,
type: "get" /* TrackOpTypes.GET */,
key: key
});
}
...
}
return isRef(value) && !shallow ? value.value : value;
...
Dep.prototype.depend = function (info) {
if (Dep.target) {
Dep.target.addDep(this);
if (info && Dep.target.onTrack) {
Dep.target.onTrack(__assign({ effect: Dep.target }, info));
}
}
};
...
Watcher.prototype.addDep = function (dep) {
var id = dep.id;
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id);
this.newDeps.push(dep);
if (!this.depIds.has(id)) {
dep.addSub(this);
}
}
};
當前的Dep.target是getA的watcher實例,a變量會找到訂閱者(getA的watcher),并在該watcher的newDeps里添加該變量的dep(this.newDeps.push(dep)),然后該變量會把getA的watcher加入自己的依賴(dep.addSub(this))。從而建立了getA和num之間的聯系。接下來執(zhí)行popTarget()和this.cleanupDeps()將當前Dep.target置為組件的watcher,然后newDeps的值賦給deps。至此watcher.evaluate()執(zhí)行完后this.getter.call(vm, vm)執(zhí)行完畢,返回a的value。
觸發(fā)依賴
當變量發(fā)生變化時會觸發(fā)該變量的set方法:
function reactiveSetter(newVal) {
var value = getter ? getter.call(obj) : val;
...
else if (!shallow && isRef(value) && !isRef(newVal)) {
value.value = newVal;
return;
}
else {
val = newVal;
}
childOb = !shallow && observe(newVal, false, mock);
{
dep.notify({
type: "set" /* TriggerOpTypes.SET */,
target: obj,
key: key,
newValue: newVal,
oldValue: value
});
}
}
主要執(zhí)行dep.notify方法:
Dep.prototype.notify = function (info) {
// stabilize the subscriber list first
var subs = this.subs.slice();
...
for (var i = 0, l = subs.length; i < l; i++) {
...
subs[i].update();
}
};
Watcher.prototype.update = function () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
}
else if (this.sync) {
this.run();
}
else {
queueWatcher(this);
}
};主要執(zhí)行subs[i].update()方法,當前的變量a有兩個dep,computed的lazy為true不會繼續(xù)執(zhí)行。第二個dep為組件的watcher,執(zhí)行該watcher的update方法重新渲染刷新頁面。
總結
- 在組件實例化的時候會遍歷computed設置computed的get方法并設置Dep.target為當前computed的watcher。
- 在執(zhí)行渲染模板方法的時候會觸發(fā)該computed的get方法。
- 會執(zhí)行computed函數,當該函數里獲取變量時會觸發(fā)變量的get方法。該變量會通過Dep.target獲取當前的watcher并添加自己的dep(相當于記錄了訂閱了哪些變量),也就是說獲取變量的時候會訂閱該變量,該變量也會在自己的依賴dep添加watcher(記錄訂閱者,發(fā)生變化時會通知訂閱者)。
- 當前變量發(fā)生改變時會循環(huán)觸發(fā)該變量的dep的update方法刷新頁面。其中computed方法由于已經獲取最新值所以只需要執(zhí)行組件的update方法。
到此這篇關于Vue computed實現原理深入講解的文章就介紹到這了,更多相關Vue computed內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
vue使用Vue.extend創(chuàng)建全局toast組件實例
這篇文章主要介紹了vue使用Vue.extend創(chuàng)建全局toast組件實例,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03

