computed 原理解析

在 Vue 官網(wǎng)文檔里面揽惹,對 computed 有這么一句描述:

計算屬性的結(jié)果會被緩存,除非依賴的響應式屬性變化才會重新計算。注意评疗,如果某個依賴 (比如非響應式屬性) 在該實例范疇之外母剥,則計算屬性是不會被更新的滞诺。

這句話非常重要,涵蓋了 computed 最關(guān)鍵的知識點:

  • computed 會搜集并記錄依賴环疼。
  • 依賴發(fā)生了變化才會重新計算 computed 习霹,由于 computed 是有緩存的,所以當依賴變化之后炫隶,第一次訪問 computed 屬性的時候淋叶,才會計算新的值。
  • 只能搜集到響應式屬性依賴伪阶,無法搜集到非響應式屬性依賴煞檩。
  • 無法搜集到當前 vm 實例之外的屬性依賴。

如果僅局限于知道上述規(guī)則望门,而不理解內(nèi)部機制形娇,那么在實際開發(fā)中難免步步驚心,不敢甩手大干筹误。

Vue 內(nèi)部是怎么知道 computed 依賴的桐早?

對于在 RequireJS 時代摸爬滾打過不少年的同學來說,可能一下就會聯(lián)想到 RequireJS 獲取依賴的原理,使用 Function.prototype.toString() 方法將函數(shù)轉(zhuǎn)換成字符串哄酝,然后借助正則從字符串中查找 require('xxx') 這樣的代碼友存,最終分析出來依賴。

這種方式實際上有非常多的限制的:

  • 如果在注釋里面出現(xiàn)了 require('xxx') 陶衅,豈不是會匹配出多余的依賴屡立。
  • 在開發(fā)中,描述依賴的時候搀军,必須要寫成 require('xxxx') 的形式膨俐,require 中的字符串參數(shù)不能是各種動態(tài)的、復雜的字符串拼接罩句,否則就無法解析了焚刺。

Vue 顯然沒有使用這么低效不準確的方式。

我們可以先看一段偽代碼:

const vm = {
    dependencies: [],
    
    obj: {
        get a() {
            // this 指向 vm 對象门烂。
            this.dependencies.push('a');
            return this.obj.b;
        },
        get b() {
            this.dependencies.push('b');
            return 1;
        }
    },
    computed: {
        c: {
            get() {
                // this 指向 vm 對象乳愉。
                return this.obj.a;
            }
        }
    }
};

vm.dependencies = [];
console.log(vm.c);
console.log('vm.c 依賴項:', vm.dependencies); // 輸出: vm.c 依賴項: a, b

在上述代碼中,訪問 vm.c 之前屯远,清空了一下 vm.dependencies 數(shù)組蔓姚,訪問 vm.c 的時候,會調(diào)用相應的 get() 方法慨丐,在 get() 方法中坡脐,訪問了 this.obj.a ,而對于 this.obj.a 的訪問房揭,又會調(diào)用相應的 get 方法挨措,在該 get 方法中,有一句代碼 this.dependencies.push('a') 崩溪,往 vm.dependencies 中放置了當前執(zhí)行流程中依賴到的屬性浅役,然后以此類推,在 vm.c 訪問結(jié)束之后伶唯, vm.dependencies 里面就記錄了 vm.c 的依賴 ['a', 'b'] 了觉既。

到這里,有的同學可能會產(chǎn)生新的疑問:如果在 vm.obj.a 中出現(xiàn)條件分支語句乳幸,豈不是會出現(xiàn)依賴搜集不完整的情況瞪讼?且看如下修改后的代碼:

const vm = {
    dependencies: [],
    
    obj: {
        get a() {
            // this 指向 vm 對象。
            this.dependencies.push('a');
            if (this.obj.d) {
                return this.obj.b;
            }
            
            return 2;
        },
        get b() {
            this.dependencies.push('b');
            return 1;
        },
        get d() {
            this.dependencies.push('d');
            return this._d;
        },
        set d(val) {
            this._d = val;
        }
    },
    computed: {
        c: {
            get() {
                // this 指向 vm 對象粹断。
                return this.obj.a;
            }
        }
    }
};

vm.dependencies = [];
vm.obj.d = false;
console.log(vm.c);
console.log('vm.c 依賴項:', vm.dependencies); // 輸出: vm.c 依賴項: a, d

vm.dependencies = [];
vm.obj.d = true;
console.log(vm.c);
console.log('vm.c 依賴項:', vm.dependencies); // 輸出: vm.c 依賴項: a, d, b

從上述代碼中看到符欠,第一處依賴項輸出只有 ad 瓶埋,并不是我們初步期望的是 a 希柿、 d 诊沪、 b

實際上曾撤,這并不會帶來什么問題端姚,相反,還能在一些場景下提升性能挤悉,為什么這么說呢渐裸?

在第一次訪問 vm.c 的時候,雖然只記錄了 a 装悲、 d 昏鹃,兩個依賴項,但是并不會引起 bug 诀诊,表面上看此時 vm.obj.b 變化了盆顾,應該重新計算 vm.c 的值,但是由于 vm.obj.d 還是 false 畏梆,所以 vm.obj.a 的值并不會改變,因此 vm.c 的值也不會改變奈懒,所以重新計算 vm.c 并沒有意義奠涌。所以在這個時候,只有 a 磷杏、 d 發(fā)生變化的時候溜畅,才應該去重新計算 vm.c 。第二次訪問 vm.c 极祸,在 vm.obj.d 變?yōu)?true 之后慈格,就能搜集到依賴為 adb ,此時重新掉之前的依賴項糠聪,后續(xù)按照新的依賴項來標記 vm.c 是否應該重新計算徙硅。

緩存

在得知 computed 屬性發(fā)生變化之后, Vue 內(nèi)部并不立即去重新計算出新的 computed 屬性值耀怜,而是僅僅標記為 dirty ,下次訪問的時候,再重新計算页眯,然后將計算結(jié)果緩存起來。

這樣的設計厢呵,會避免一些不必要的計算窝撵,比如有以下 Vue 代碼:

<template>
    <div class="my-component">
        ...
    </div>
</template>

<script>
export default {
    data() {
        return {
            a: 1,
            b: 2
        };
    },
    computed: {
        c() {
            return this.a + this.b;
        }
    },
    created() {
        console.log(this.c);
        
        setInterval(() => {
            this.a++;
        },1000);
    }
};
</script>

第一次訪問 this.c 的時候,記錄了依賴項 a 襟铭、 b 碌奉,雖然后續(xù)通過 setInterval 不停地修改 this.a 短曾,造成 this.c 一直是 dirty 狀態(tài),但是由于并沒有再訪問 this.c 道批,所以重新計算 this.c 的值是毫無意義的错英,如果不做無意義的計算反倒會提升一些性能。

記錄的響應式屬性都在當前實例范疇內(nèi)

舉個例子:

import Vue from 'vue';

Vue.component('Child', {
    data() {
        return {
            a: 1
        };
    },
    created() {
        setInterval(() => {
            this.a++;
        }, 1000);
    },
    template: '<div>{{ a }}</div>'
});

const App = {
    el: '#app',
    template: '<div>{{ b }} - <Child ref="child" /></div>',
    computed: {
        b() {
            return this.$refs.child && this.$refs.child.a;
        }
    }
};

new Vue(App);

從上述例子可以發(fā)現(xiàn)隆豹, Child 組件輸出的 a 是不斷變化的椭岩,而 App 組件輸出的 b 是一直不會有什么內(nèi)容的。

這應該是 Vue 的一種設計策略璃赡,開發(fā)當前組件的時候判哥,就關(guān)注當前組件的數(shù)據(jù)就行了,不要牽連到其他地方的數(shù)據(jù)碉考,不然會增加耦合度塌计,和組件的解耦合初衷相違背。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末侯谁,一起剝皮案震驚了整個濱河市锌仅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌墙贱,老刑警劉巖热芹,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異惨撇,居然都是意外死亡伊脓,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門魁衙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來报腔,“玉大人,你說我怎么就攤上這事剖淀〈慷辏” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵纵隔,是天一觀的道長茅撞。 經(jīng)常有香客問我,道長巨朦,這世上最難降的妖魔是什么米丘? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮糊啡,結(jié)果婚禮上拄查,老公的妹妹穿的比我還像新娘。我一直安慰自己棚蓄,他們只是感情好堕扶,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布碍脏。 她就那樣靜靜地躺著,像睡著了一般稍算。 火紅的嫁衣襯著肌膚如雪典尾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天糊探,我揣著相機與錄音钾埂,去河邊找鬼。 笑死科平,一個胖子當著我的面吹牛褥紫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瞪慧,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼髓考,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了弃酌?” 一聲冷哼從身側(cè)響起氨菇,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎妓湘,沒想到半個月后查蓉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡多柑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了楣责。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片竣灌。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖秆麸,靈堂內(nèi)的尸體忽然破棺而出初嘹,到底是詐尸還是另有隱情,我是刑警寧澤沮趣,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布屯烦,位于F島的核電站,受9級特大地震影響房铭,放射性物質(zhì)發(fā)生泄漏驻龟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一缸匪、第九天 我趴在偏房一處隱蔽的房頂上張望翁狐。 院中可真熱鬧,春花似錦凌蔬、人聲如沸露懒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽懈词。三九已至蛇耀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間坎弯,已是汗流浹背纺涤。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留荞怒,地道東北人洒琢。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像褐桌,于是被迫代替她去往敵國和親衰抑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 1.安裝 可以簡單地在頁面引入Vue.js作為獨立版本荧嵌,Vue即被注冊為全局變量呛踊,可以在頁面使用了。 如果希望搭建...
    Awey閱讀 11,016評論 4 129
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理啦撮,服務發(fā)現(xiàn)谭网,斷路器,智...
    卡卡羅2017閱讀 134,654評論 18 139
  • 下載安裝搭建環(huán)境 可以選npm安裝赃春,或者簡單下載一個開發(fā)版的vue.js文件 瀏覽器打開加載有vue的文檔時愉择,控制...
    冥冥2017閱讀 6,037評論 0 42
  • 現(xiàn)在網(wǎng)上有很多早起打卡,早起俱樂部之類的活動织中。當然一些健身的App也有打卡的功能锥涕。還有一些習慣養(yǎng)成的App,比如狭吼;...
    青木俠閱讀 2,685評論 4 0
  • 1层坠、市場分析(關(guān)鍵詞:眾籌、粉絲經(jīng)濟) 2刁笙、用戶分層破花,用戶需求,產(chǎn)品策略/定位 3疲吸、產(chǎn)品功能邏輯座每,交互體驗 4、競...
    韋歌wege閱讀 2,418評論 0 9