Vue computed實(shí)現(xiàn)原理深入講解
在從vue(v2.7.10)源碼分析vue是如何收集依賴和觸發(fā)依賴這篇文章中我們講了vue是怎么收集依賴的。其中computed的實(shí)現(xiàn)原理和這個(gè)密切相關(guān),接下來(lái)我們看看computed的實(shí)現(xiàn)原理。
基礎(chǔ)代碼如下:
<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函數(shù)實(shí)例化TestWebpackTest組件的時(shí)候會(huì)執(zhí)行下面的方法:
Vue.extend = function (extendOptions) {
...
if (Sub.options.computed) {
initComputed(Sub);
}
...
};在該方法中會(huì)執(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方法設(shè)置computed函數(shù)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í)行過(guò)程在收集依賴部分分析。返回該函數(shù)后執(zhí)行Object.defineProperty(target, key, sharedPropertyDefinition),給當(dāng)前的vm設(shè)置getA響應(yīng)式。至此設(shè)置響應(yīng)完畢。
收集依賴
收集依賴發(fā)生在執(zhí)行組件渲染過(guò)程,會(huì)通過(guò)_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("新增")]),
])
}
會(huì)獲取當(dāng)前的computed函數(shù)生成的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;
};
調(diào)用當(dāng)前watcher的get方法,首先設(shè)置當(dāng)前的Dep.target為當(dāng)前的watcher,然后執(zhí)行this.getter方法,該方法為getA方法,相當(dāng)于執(zhí)行該函數(shù)。
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變量,所以會(huì)觸發(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);
}
}
};
當(dāng)前的Dep.target是getA的watcher實(shí)例,a變量會(huì)找到訂閱者(getA的watcher),并在該watcher的newDeps里添加該變量的dep(this.newDeps.push(dep)),然后該變量會(huì)把getA的watcher加入自己的依賴(dep.addSub(this))。從而建立了getA和num之間的聯(lián)系。接下來(lái)執(zhí)行popTarget()和this.cleanupDeps()將當(dāng)前Dep.target置為組件的watcher,然后newDeps的值賦給deps。至此watcher.evaluate()執(zhí)行完后this.getter.call(vm, vm)執(zhí)行完畢,返回a的value。
觸發(fā)依賴
當(dāng)變量發(fā)生變化時(shí)會(huì)觸發(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()方法,當(dāng)前的變量a有兩個(gè)dep,computed的lazy為true不會(huì)繼續(xù)執(zhí)行。第二個(gè)dep為組件的watcher,執(zhí)行該watcher的update方法重新渲染刷新頁(yè)面。
總結(jié)
- 在組件實(shí)例化的時(shí)候會(huì)遍歷computed設(shè)置computed的get方法并設(shè)置Dep.target為當(dāng)前computed的watcher。
- 在執(zhí)行渲染模板方法的時(shí)候會(huì)觸發(fā)該computed的get方法。
- 會(huì)執(zhí)行computed函數(shù),當(dāng)該函數(shù)里獲取變量時(shí)會(huì)觸發(fā)變量的get方法。該變量會(huì)通過(guò)Dep.target獲取當(dāng)前的watcher并添加自己的dep(相當(dāng)于記錄了訂閱了哪些變量),也就是說(shuō)獲取變量的時(shí)候會(huì)訂閱該變量,該變量也會(huì)在自己的依賴dep添加watcher(記錄訂閱者,發(fā)生變化時(shí)會(huì)通知訂閱者)。
- 當(dāng)前變量發(fā)生改變時(shí)會(huì)循環(huán)觸發(fā)該變量的dep的update方法刷新頁(yè)面。其中computed方法由于已經(jīng)獲取最新值所以只需要執(zhí)行組件的update方法。
到此這篇關(guān)于Vue computed實(shí)現(xiàn)原理深入講解的文章就介紹到這了,更多相關(guān)Vue computed內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue使用Vue.extend創(chuàng)建全局toast組件實(shí)例
這篇文章主要介紹了vue使用Vue.extend創(chuàng)建全局toast組件實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
Vue.js原理分析之nextTick實(shí)現(xiàn)詳解
這篇文章主要給大家介紹了關(guān)于Vue.js原理分析之nextTick實(shí)現(xiàn)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
解決vue-cli輸入命令vue ui沒(méi)效果的問(wèn)題
這篇文章主要介紹了解決vue-cli輸入命令vue ui沒(méi)效果的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11
vue3引入highlight.js進(jìn)行代碼高亮的方法實(shí)例
最近忙著開發(fā)自己的開發(fā)腳手架,在做代碼生成器的時(shí)候,有個(gè)預(yù)覽功能,需要讓代碼高亮,下面這篇文章主要給大家介紹了關(guān)于vue3引入highlight.js進(jìn)行代碼高亮的相關(guān)資料,需要的朋友可以參考下2022-04-04
更強(qiáng)大的vue ssr實(shí)現(xiàn)預(yù)取數(shù)據(jù)的方式
這篇文章主要介紹了更強(qiáng)大的 vue ssr 預(yù)取數(shù)據(jù)的方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07

