主頁 > 企業開發 > 如何理解Vue計算屬性?80%的人都沒搞懂

如何理解Vue計算屬性?80%的人都沒搞懂

2020-09-18 01:39:41 企業開發

Vue計算屬性

1、提到計算屬性,我們馬上就會想到它的一個特性:快取,Vue 檔案也如是說:

計算屬性是基于它們的回應式依賴進行快取的/

2,很榮幸給大家分享,我是一名08年出道的前端高級老鳥,大家如果想跟我交流學習經驗,可以進我的扣扣裙 519293536 有問題我都會盡力幫大家,喜歡中高級問題,小白勿進哦
接下來正題:
   那么計算屬性如何快取的呢?計算屬性的觀察者是如何進行依賴收集的呢,接下來深入原理看一下,
    本文需要對基礎的回應式原理和幾個關鍵角色 Observer、Dep、Watcher 等有一定的了解,可參考 Observer、Dep、Watcher 傻傻搞不清楚

以一個簡單的例子開始:

<div id="app">
    <h2>{{ this.text }}</h2>
    <button @click="changeName">Change name</button>
</div>

const vm = new Vue({
    el: '#app',
    data() {
        return {
            name: 'xiaoming',
        }
    },
    computed: {
        text() {
            return `Hello, ${this.name}!`
        }
    },
    methods: {
        changeName() {
            this.name = 'onlyil'
        },
    },
})
復制代碼

初始展示 Hello, xiaoming!,點擊按鈕后展示 Hello, onlyil!

Vue 初始化

還是從 vue 初始化看起,從 new Vue() 開始,建構式會執行 this._init,在 _init 中會進行合并配置、初始化生命周期、事件、渲染等,最后執行 vm.$mount 進行掛載,

// src/core/instance/index.js
function Vue (options) {
    // ...
    this._init(options)
}

// src/core/instance/init.js
Vue.prototype._init = function (options?: Object) {
    // 合并選項
    // ...
    // 一系列初始化
    // ...
    initState(vm)
    // ...
    
    // 掛載
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
}
復制代碼

計算屬性的初始化就在 initState 中:

// src/core/instance/state.js
export function initState (vm: Component) {
    const opts = vm.$options
    // ...
    // 初始化 computed
    if (opts.computed) initComputed(vm, opts.computed)
    // ...
}
復制代碼

computed 初始化

看一下 initComputed :

function initComputed(vm, computed) {
    const watchers = vm._computedWatchers = Object.create(null)
    // 遍歷 computed 選項,依次進行定義
    for (const key in computed) {
        const getter = computed[key]

        // 為計算屬性創建內部 watcher
        watchers[key] = new Watcher(
            vm,
            getter || noop, // 計算屬性 text 函式
            noop,
            computedWatcherOptions // { lazy: true } ,指定 lazy 屬性,表示要實體化 computedWatcher
        )

        // 為計算屬性定義 getter
        defineComputed(vm, key, userDef)
    }
}
復制代碼

1. 定義 _computedWatchers

首先定義一個 watchers 空物件,同時掛在 vm._computedWatchers 上,用來存放該 vm 實體的所有 computedWatcher,

2. 實體化 computedWatcher

遍歷 computed 選項并實體化 watcher,引數中的 getter 就是上邊示例中計算屬性 text 對應的函式:

function () {
    return `Hello, ${this.name}!`
}
復制代碼

引數中的 computedWatcherOptions 為 { lazy: true } ,指定 lazy 屬性,表示要實體化的是 computedWatcher,

實體化 computedWatcher :

class Watcher {
    constructor(vm, expOrFn, cb, options) {
        // options 為 { lazy: true }
        if (options) {
            // ...
            this.lazy = !!options.lazy
            // ...
        }
        this.dirty = this.lazy // for lazy watchers, 初始 dirty 為 true

        this.getter = expOrFn

        // lazy 為 true,不進行求值,直接回傳 undefined
        this.value = https://www.cnblogs.com/chengxuyuanaa/p/this.lazy
            ? undefined
            : this.get()
    }
}
復制代碼

執行建構式時指定 lazy dirty 為 true,最后執行 watcher.value = https://www.cnblogs.com/chengxuyuanaa/p/undefined 并未執行 get 方法進行求值,什么時候求值呢?后面就知道了,

回到上邊,為計算屬性創建內部 watcher 之后的 watchers 物件是這樣的:

{
    text: Watcher {
        lazy: true,
        dirty: true,
        deps: [],
        getter: function () {
            return `Hello, ${this.name}!`
        },
        value: undefined, // 直接賦值為 undefined ,
    }
}
復制代碼

3. 定義計算屬性的 getter

看一下 defineComputed 做了什么:

function defineComputed(target, key, userDef) {
    Object.defineProperty(target, key, {
        get: function () {
            const watcher = this._computedWatchers && this._computedWatchers[key]
            if (watcher) {
                if (watcher.dirty) {
                    watcher.evaluate()
                }
                if (Dep.target) {
                    watcher.depend()
                }
                return watcher.value
            }
        }
    })
}
復制代碼

有沒有很熟悉,在定義回應式資料的 defineReactive 方法中也使用了 Object.defineProperty 方法來定義訪問器屬性,這里在該 vm 實體上定義了 text 屬性,每當訪問到 this.text 時就會執行對應的 getter ,函式做了什么暫時可以不看,那什么時候會讀取到 this.text 呢?答案在下邊,

總結一下 computed 初始化程序:

  • 定義 vm._computedWatchers 用來存放該 vm 實體的所有 computedWatcher
  • 遍歷 computed 選項并實體化 watcher,不求值,直接將 watcher.value = https://www.cnblogs.com/chengxuyuanaa/p/undefined
  • 通過 defineComputed 定義計算屬性的 getter ,等待后邊讀取時觸發

首次渲染

初始化完成后,會進入 mount 階段,在執行 render 生成 vnode 時會讀取到計算屬性 text ,本文示例的 render 函式是這樣:

function render() {
    var h = arguments[0];
    return h("div", [
        h("h2", [this.text]), // 這里讀取了計算屬性 text
        h("button", {
            "on": {
                "click": this.changeName
            }
        }, ["changeName"]),
    ]);
}
復制代碼

1. 觸發計算屬性的 getter

這時會觸發計算屬性的 getter ,也就是上邊定義的訪問器屬性:

get: function () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
        // 此時 dirty 為 true ,進行求值
        if (watcher.dirty) {
            // 求值,對 data 進行依賴收集,使 computedWatcher 訂閱 data
            // 這里的 data 就是 "this.name"
            watcher.evaluate()
        }
        if (Dep.target) {
            watcher.depend()
        }
        return watcher.value
    }
}
復制代碼

2. 求值 watcher.evaluate()

取出 vm._computedWatchers 中對應的 watcher ,此時 watcher.dirty 為 true,執行 watcher.evaluate() ,

// watcher.evaluate
evaluate() {
    this.value = https://www.cnblogs.com/chengxuyuanaa/p/this.get()
    this.dirty = false
}
復制代碼

這個函式做了兩件事:

  • 執行 get 進行求值,這里就解答了上面何時求值的問題;
  • 將 dirty 置為 false ,

先看求值:

// watcher.get
get() {
    pushTarget(this) // Dep.target 置為當前 computedWatcher
    let value
    const vm = this.vm
    try {
        // 觸發回應資料的依賴收集
        value = https://www.cnblogs.com/chengxuyuanaa/p/this.getter.call(vm, vm)
    } catch (e) {
        // ...
    } finally {
        popTarget() // Dep.target 重新置為渲染 watcher
    }
    return value
}
復制代碼

這里需要知道一個前置內容,全域的 Dep.target 存的是當前正在求值的 watcher,它是用一個堆疊 targetStack 來維護的,當前是在渲染程序中,所以此時堆疊是這樣:[ 渲染watcher ],

function pushTarget(target: ?Watcher) {
    targetStack.push(target)
    Dep.target = target
}
復制代碼

首先 pushTarget(this) , Dep.target 成為當前 computedWatcher,此時堆疊是這樣的:[ 渲染watcher, computedWatcher ],

然后執行 getter 觸發回應資料的依賴收集,再回顧一下 getter :

function () {
    return `Hello, ${this.name}!`
}
復制代碼

很顯然執行這個函式會讀取 this.name ,name 的 dep 就會收集當前 computedWatcher ,當前 computedWatcher 就會訂閱 name 的變化(這里不做詳細介紹,參考回應式原理的依賴收集),

收集完成后執行 popTarget() ,Dep.target 重新成為渲染 watcher ,此時的堆疊是這樣:[ 渲染watcher ],

然后回傳計算得出的值,再將 dirty 置為 false ,此時 name 的 dep 是這樣的:

{
    id: 3,
    subs: [ computedWatcher ], // 收集了 computedWatcher
}
復制代碼

computedWatcher 是這樣的:

{
    dirty: false, // 求值完成,dirty 置為 false
    deps: [ name 的 dep ], // 訂閱了 name
    value: "Hello, xiaoming!",
}
復制代碼

3. watcher.depend()

此時計算屬性的 get 訪問執行到了這里:

get: function () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
        // 求值...
        // 執行到了這里
        if (Dep.target) {
            watcher.depend()
        }
        return watcher.value
    }
}
復制代碼

此時 Dep.target 是渲染 watcher ,所以會執行 watcher.depend() ,

// watcher.depend
depend() {
    let i = this.deps.length
    while (i--) {
        this.deps[i].depend()
    }
}
復制代碼

可以看到將 computedWatcher 訂閱的 deps 依次執行 dep.depend ,熟悉回應式原理的應該馬上就知道了,這是在對這些 dep 的回應式資料進行依賴收集,也就是對示例中的 name 進行依賴收集,收集的是誰呢?上面提到此時的 Dep.target 是渲染 watcher ,那么總結下來,這一步做的是:

讓 computedWatcher 訂閱的回應式資料收集渲染 watcher

這一步操作之后 name 的 dep 是這樣的:

{
    id: 3,
    subs: [ computedWatcher, 渲染 watcher ], // 收集了渲染 watcher
}
復制代碼

最后,回傳 watcher.value ,get 訪問結束,render 函式繼續往下走,之后渲染出最終頁面,

觸發更新

當點擊按鈕時,執行 this.name = 'onlyil' ,會觸發 name 的訪問器屬性 set ,執行 dep.notify() ,依次觸發它所收集的 watcher 的更新邏輯,也就是 [ computedWatcher, 渲染 watcher ] 的 update ,

1. 觸發 computedWatcher 更新

// watcher.update
update() {
    // computedWatcher 的 lazy 為 true
    if (this.lazy) {
        this.dirty = true
    }
    // ...
}
復制代碼

只做了一件事,就是將 dirty 置為 true,表示該計算屬性“臟”了,需要重新計算,什么時候重新求值呢,往下看,

2. 觸發渲染 watcher 更新

// watcher.update
update() {
    // ...
    // 
    queueWatcher(this)
}
復制代碼

這里就是加入異步更新佇列,最終又會執行到 render 函式來生成 vnode ,同首次渲染一樣,在 render 程序中又會讀取到計算屬性 text ,再次觸發它的 getter :

get: function () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
        // 此時 dirty 為 true ,"臟"了
        if (watcher.dirty) {
            // 重新求值
            watcher.evaluate()
        }
        // ...
        return watcher.value
    }
}
復制代碼

重新求值也就會重新執行我們在 computed 選項定義的函式,頁面就展示了新值 Hello, onlyil!

function () {
    return `Hello, ${this.name}!`
}
復制代碼

至此,更新結束,

如何快取

通過上邊的程序分析,可以做出如下總結:

  • 首次渲染時實體化 computedWatcher 并定義屬性 dirty: false ,在 render 程序中求值并進行依賴收集;
  • 當 computedWatcher 訂閱的回應式資料也就是 name 改變時,觸發 computedWatcher 的更新,修改 dirty: true ;
  • render 函式執行時讀取計算屬性 text ,發現 dirty 為 true ,重新求值,頁面視圖更新,

可以發現一個關鍵點,computedWatcher 的更新只做了一件事:修改 dirty: true ,求值操作始終都在 render 程序中,

現在我們修改示例,新增一個資料 count 和方法 add :

<div id="app">
    <h2>{{ this.text }}</h2>
    <h2>{{ this.count }}</h2>
    <button @click="changeName">Change name</button>
    <button @click="add">Add</button>
</div>

const vm = new Vue({
    el: '#app',
    data() {
        return {
            name: 'xiaoming',
			count: 0,
        }
    },
    computed: {
        text() {
            return `Hello, ${this.name}!`
        }
    },
    methods: {
        changeName() {
            this.name = 'onlyil'
        },
		add() {
            this.count += 1
        },
    },
})
復制代碼

點擊 Add 按鈕 count 會發生改變,那么在重渲染時 computedWatcher 會重新求值嗎?

答案是不會,快取的關鍵就在于此,回頭再看下文章開頭參考 Vue 檔案里一句話:

計算屬性是基于它們的回應式依賴進行快取的

結合上邊的分析是不是恍然大悟,計算屬性 text 的 getter 函式并沒有讀取 count ,所以它的 computedWatcher 不會訂閱 count 的變化,即 count 的 dep 也不會收集該 computedWatcher ,

所以當 count 改變時,不會觸發 computedWatcher 的更新,dirty 仍為 false ,說明這個計算屬性不“臟”,那在之后的 render 程序中讀取到計算屬性 text 時就不會重新求值,這樣就起到了快取的效果,

后記

1、我是一名08年出道的前端高級老鳥,大家如果想跟我交流學習經驗,可以進我的扣扣裙 519293536 有問題我都會盡力幫大家,喜歡中高級問題,小白勿進哦

2、整個程序下來腦子還是有點吃力的,但多來幾遍就會逐漸理解 computed 實作的巧妙之處,其實快取的原理很簡單,就是一個標志位而已~??

注:文中代碼做了刪減,結合原始碼斷點捋捋更佳,
本文的文字及圖片來源于網路加上自己的想法,僅供學習、交流使用,不具有任何商業用途,著作權歸原作者所有,如有問題請及時聯系我們以作處理

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/69807.html

標籤:JavaScript

上一篇:Vue首屏加載速度優化如何提升80%?本文詳解

下一篇:AntD框架的upload組件上傳圖片時使用customRequest方法自定義上傳行為

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more