欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

vue中的computed?和?vm.$data?原理解析

 更新時間:2022年08月10日 10:16:58   作者:Kaku_fe  
這篇文章主要介紹了vue中的computed?和?vm.$data?原理,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

使用vuex中store中的數(shù)據(jù),基本上離不開vue中一個常用的屬性computed。

官方一個最簡單的例子如下

var vm = new Vue({
? el: '#example',
? data: {
? ? message: 'Hello'
? },
? computed: {
? ? // 計算屬性的 getter
? ? reversedMessage: function () {
? ? ? // `this` 指向 vm 實例
? ? ? return this.message.split('').reverse().join()
? ? }
? }
})

不知大家有沒有思考過,vue的computed是如何更新的,為什么當vm.message發(fā)生變化時,vm.reversedMessage也會自動發(fā)生變化?

我們來看看vue中data屬性和computed相關的源代碼。

// src/core/instance/state.js
// 初始化組件的state
export function initState (vm: Component) {
? vm._watchers = []
? const opts = vm.$options
? if (opts.props) initProps(vm, opts.props)
? if (opts.methods) initMethods(vm, opts.methods)
? // 當組件存在data屬性
? if (opts.data) {
? ? initData(vm)
? } else {
? ? observe(vm._data = {}, true /* asRootData */)
? }
? // 當組件存在 computed屬性
? if (opts.computed) initComputed(vm, opts.computed)
? if (opts.watch && opts.watch !== nativeWatch) {
? ? initWatch(vm, opts.watch)
? }
}

initState方法當組件實例化時會自動觸發(fā),該方法主要完成了初始化data,methods,props,computed,watch這些我們常用的屬性,我們來看看我們需要關注的initData和initComputed(為了節(jié)省時間,去除了不太相關的代碼)

先看看 initData 這條線

// src/core/instance/state.js
function initData (vm: Component) {
? let data = vm.$options.data
? data = vm._data = typeof data === 'function'
? ? ? getData(data, vm)
? ? : data || {}
? // .....省略無關代碼
??
? // 將vue的data傳入observe方法
? observe(data, true /* asRootData */)
}
// src/core/observer/index.js
export function observe (value: any, asRootData: ?boolean): Observer | void {
? if (!isObject(value)) {
? ? return
? }
? let ob: Observer | void
? // ...省略無關代碼
? ob = new Observer(value)
? if (asRootData && ob) {
? ? ob.vmCount++
? }
? return ob
}

在初始化的時候observe方法本質上是實例化了一個Observer對象,這個對象的類是這樣的

// src/core/observer/index.js
export class Observer {
? value: any;
? dep: Dep;
? vmCount: number; // number of vms that has this object as root $data
? constructor (value: any) {
? ? this.value = value
? ? // 關鍵代碼 new Dep對象
? ? this.dep = new Dep()
? ? this.vmCount = 0
? ? def(value, '__ob__', this)
? ? // ...省略無關代碼
? ? this.walk(value)
? }
? walk (obj: Object) {
? ? const keys = Object.keys(obj)
? ? for (let i = 0; i < keys.length; i++) {
? ? ? // 給data的所有屬性調用defineReactive
? ? ? defineReactive(obj, keys[i], obj[keys[i]])
? ? }
? }
}

在對象的構造函數(shù)中,最后調用了walk方法,該方法即遍歷data中的所有屬性,并調用defineReactive方法,defineReactive方法是vue實現(xiàn) MDV(Model-Driven-View)的基礎,本質上就是代理了數(shù)據(jù)的set,get方法,當數(shù)據(jù)修改或獲取的時候,能夠感知(當然vue還要考慮數(shù)組,Object中嵌套Object等各種情況,本文不在分析)。

我們具體看看defineReactive的源代碼

// src/core/observer/index.js
export function defineReactive (
? obj: Object,
? key: string,
? val: any,
? customSetter?: ?Function,
? shallow?: boolean
) {
? // 重點,在給具體屬性調用該方法時,都會為該屬性生成唯一的dep對象
? const dep = new Dep()
? // 獲取該屬性的描述對象
? // 該方法會返回對象中某個屬性的具體描述
? // api地址https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor
? const property = Object.getOwnPropertyDescriptor(obj, key)
? // 如果該描述不能被更改,直接返回,因為不能更改,那么就無法代理set和get方法,無法做到響應式
? if (property && property.configurable === false) {
? ? return
? }
? // cater for pre-defined getter/setters
? const getter = property && property.get
? const setter = property && property.set
? let childOb = !shallow && observe(val)
? // 重新定義data當中的屬性,對get和set進行代理。
? Object.defineProperty(obj, key, {
? ? enumerable: true,
? ? configurable: true,
? ? get: function reactiveGetter () {
? ? ? const value = getter ? getter.call(obj) : val
? ? ? // 收集依賴, reversedMessage為什么會跟著message變化的原因
? ? ? if (Dep.target) {
? ? ? ? dep.depend()
? ? ? ? if (childOb) {
? ? ? ? ? childOb.dep.depend()
? ? ? ? }
? ? ? ? if (Array.isArray(value)) {
? ? ? ? ? dependArray(value)
? ? ? ? }
? ? ? }
? ? ? return value
? ? },
? ? set: function reactiveSetter (newVal) {
? ? ? const value = getter ? getter.call(obj) : val
? ? ? /* eslint-disable no-self-compare */
? ? ? if (newVal === value || (newVal !== newVal && value !== value)) {
? ? ? ? return
? ? ? }
? ? ? if (setter) {
? ? ? ? setter.call(obj, newVal)
? ? ? } else {
? ? ? ? val = newVal
? ? ? }
? ? ? childOb = !shallow && observe(newVal)
? ? ? // 通知依賴進行更新
? ? ? dep.notify()
? ? }
? })
}

我們可以看到,在所代理的屬性的get方法中,當dep.Target存在的時候會調用dep.depend()方法,這個方法非常的簡單,不過在說這個方法之前,我們要認識一個新的類Dep

Dep 是 vue 實現(xiàn)的一個處理依賴關系的對象,

主要起到一個紐帶的作用,就是連接 reactive data 與 watcher,代碼非常的簡單

// src/core/observer/dep.js
export default class Dep {
? static target: ?Watcher;
? id: number;
? subs: Array<Watcher>;
? constructor () {
? ? this.id = uid++
? ? this.subs = []
? }
? addSub (sub: Watcher) {
? ? this.subs.push(sub)
? }
? removeSub (sub: Watcher) {
? ? remove(this.subs, sub)
? }
? depend () {
? ? if (Dep.target) {
? ? ? Dep.target.addDep(this)
? ? }
? }
? notify () {
? ? const subs = this.subs.slice()
? ? for (let i = 0, l = subs.length; i < l; i++) {
? ? ? // 更新 watcher 的值,與 watcher.evaluate() 類似,
? ? ? // 但 update 是給依賴變化時使用的,包含對 watch 的處理
? ? ? subs[i].update()
? ? }
? }
}
// 當首次計算 computed 屬性的值時,Dep 將會在計算期間對依賴進行收集
Dep.target = null
const targetStack = []
export function pushTarget (_target: Watcher) {
? // 在一次依賴收集期間,如果有其他依賴收集任務開始(比如:當前 computed 計算屬性嵌套其他 computed 計算屬性),
? // 那么將會把當前 target 暫存到 targetStack,先進行其他 target 的依賴收集,
? if (Dep.target) targetStack.push(Dep.target)
? Dep.target = _target
}
export function popTarget () {
? // 當嵌套的依賴收集任務完成后,將 target 恢復為上一層的 Watcher,并繼續(xù)做依賴收集
? Dep.target = targetStack.pop()
}

代碼非常的簡單,回到調用dep.depend()方法的時候,當Dep.Target存在,就會調用,而depend方法則是將該dep加入watcher的newDeps中,同時,將所訪問當前屬性的dep對象中的subs插入當前Dep.target的watcher.看起來有點繞,不過沒關系,我們一會跟著例子講解一下就清楚了。

講完了代理的get,方法,我們講一下代理的set方法,set方法的最后調用了dep.notify(),當設置data中具體屬性值的時候,就會調用該屬性下面的dep.notify()方法,通過class Dep了解到,notify方法即將加入該dep的watcher全部更新,也就是說,當你修改data中某個屬性值時,會同時調用dep.notify()來更新依賴該值的所有watcher。

介紹完了initData這條線,我們繼續(xù)來介紹initComputed這條線,這條線主要解決了什么時候去設置Dep.target的問題(如果沒有設置該值,就不會調用dep.depend(), 即無法獲取依賴)。

// src/core/instance/state.js
const computedWatcherOptions = { lazy: true }
function initComputed (vm: Component, computed: Object) {
? // 初始化watchers列表
? const watchers = vm._computedWatchers = Object.create(null)
? const isSSR = isServerRendering()
? for (const key in computed) {
? ? const userDef = computed[key]
? ? const getter = typeof userDef === 'function' ? userDef : userDef.get
? ? if (!isSSR) {
? ? ? // 關注點1,給所有屬性生成自己的watcher, 可以在this._computedWatchers下看到
? ? ? watchers[key] = new Watcher(
? ? ? ? vm,
? ? ? ? getter || noop,
? ? ? ? noop,
? ? ? ? computedWatcherOptions
? ? ? )
? ? }
? ? if (!(key in vm)) {
? ? ? // 關注點2
? ? ? defineComputed(vm, key, userDef)
? ? }
? }
}

在初始化computed時,有2個地方需要去關注

  • 對每一個屬性都生成了一個屬于自己的Watcher實例,并將 { lazy: true }作為options傳入
  • 對每一個屬性調用了defineComputed方法(本質和data一樣,代理了自己的set和get方法,我們重點關注代理的get方法)

我們看看Watcher的構造函數(shù)

// src/core/observer/watcher.js
constructor (
? ? vm: Component,
? ? expOrFn: string | Function,
? ? cb: Function,
? ? options?: Object
? ) {
? ? this.vm = vm
? ? vm._watchers.push(this)
? ? if (options) {
? ? ? this.deep = !!options.deep
? ? ? this.user = !!options.user
? ? ? this.lazy = !!options.lazy
? ? ? this.sync = !!options.sync
? ? } else {
? ? ? this.deep = this.user = this.lazy = this.sync = false
? ? }
? ? this.cb = cb
? ? this.id = ++uid // uid for batching
? ? this.active = true
? ? this.dirty = this.lazy // 如果初始化lazy=true時(暗示是computed屬性),那么dirty也是true,需要等待更新
? ? this.deps = []
? ? this.newDeps = []
? ? this.depIds = new Set()
? ? this.newDepIds = new Set()
? ? this.getter = expOrFn // 在computed實例化時,將具體的屬性值放入this.getter中
? ? // 省略不相關的代碼
? ? this.value = this.lazy
? ? ? ? undefined
? ? ? : this.get()
? }

除了日常的初始化外,還有2行重要的代碼

this.dirty = this.lazy
this.getter = expOrFn

在computed生成的watcher,會將watcher的lazy設置為true,以減少計算量。

因此,實例化時,this.dirty也是true,標明數(shù)據(jù)需要更新操作。

我們先記住現(xiàn)在computed中初始化對各個屬性生成的watcher的dirty和lazy都設置為了true。

同時,將computed傳入的屬性值(一般為funtion),放入watcher的getter中保存起來。

我們在來看看第二個關注點defineComputed所代理屬性的get方法是什么

// src/core/instance/state.js
function createComputedGetter (key) {
? return function computedGetter () {
? ? const watcher = this._computedWatchers && this._computedWatchers[key]
? ? // 如果找到了該屬性的watcher
? ? if (watcher) {
? ? ? // 和上文對應,初始化時,該dirty為true,也就是說,當?shù)谝淮卧L問computed中的屬性的時候,會調用 watcher.evaluate()方法;
? ? ? if (watcher.dirty) {
? ? ? ? watcher.evaluate()
? ? ? }
? ? ? if (Dep.target) {
? ? ? ? watcher.depend()
? ? ? }
? ? ? return watcher.value
? ? }
? }
}

當?shù)谝淮卧L問computed中的值時,會因為初始化watcher.dirty = watcher.lazy的原因,從而調用evalute()方法,evalute()方法很簡單,就是調用了watcher實例中的get方法以及設置dirty = false,我們將這兩個方法放在一起

// src/core/instance/state.js
evaluate () {
? this.value = this.get()
? this.dirty = false
}
??
get () { ?
// 重點1,將當前watcher放入Dep.target對象
? pushTarget(this)
? let value
? const vm = this.vm
? try {
? ? // 重點2,當調用用戶傳入的方法時,會觸發(fā)什么?
? ? value = this.getter.call(vm, vm)
? } catch (e) {
? } finally {
? ? popTarget()
? ? // 去除不相關代碼
? }
? return value
}

在get方法中中,第一行就調用了pushTarget方法,其作用就是將Dep.target設置為所傳入的watcher,即所訪問的computed中屬性的watcher,

然后調用了value = this.getter.call(vm, vm)方法,想一想,調用這個方法會發(fā)生什么?

this.getter 在Watcher構建函數(shù)中提到,本質就是用戶傳入的方法,也就是說,this.getter.call(vm, vm)就會調用用戶自己聲明的方法,那么如果方法里面用到了 this.data中的值或者其他被用defineReactive包裝過的對象,那么,訪問this.data.或者其他被defineReactive包裝過的屬性,是不是就會訪問被代理的該屬性的get方法。我們在回頭看看

get方法是什么樣子的。

注意:我講了其他被用defineReactive,這個和后面的vuex有關系,我們后面在提

get: function reactiveGetter () {
? ? ? const value = getter ? getter.call(obj) : val
? ? ? // 這個時候,有值了
? ? ? if (Dep.target) {
? ? ? ? // computed的watcher依賴了this.data的dep
? ? ? ? dep.depend()
? ? ? ? if (childOb) {
? ? ? ? ? childOb.dep.depend()
? ? ? ? }
? ? ? ? if (Array.isArray(value)) {
? ? ? ? ? dependArray(value)
? ? ? ? }
? ? ? }
? ? ? return value
? ? }

代碼注釋已經(jīng)寫明了,就不在解釋了,這個時候我們走完了一個依賴收集流程,知道了computed是如何知道依賴了誰。最后根據(jù)this.data所代理的set方法中調用的notify,就可以改變this.data的值,去更新所有依賴this.data值的computed屬性value了。

那么,我們根據(jù)下面的代碼,來簡易拆解獲取依賴并更新的過程

var vm = new Vue({
? el: '#example',
? data: {
? ? message: 'Hello'
? },
? computed: {
? ? // 計算屬性的 getter
? ? reversedMessage: function () {
? ? ? // `this` 指向 vm 實例
? ? ? return this.message.split('').reverse().join()
? ? }
? }
})
vm.reversedMessage // => ?olleH
vm.message = 'World' //?
vm.reversedMessage // => ?dlroW

初始化 data和computed,分別代理其set以及get方法, 對data中的所有屬性生成唯一的dep實例。

對computed中的reversedMessage生成唯一watcher,并保存在vm._computedWatchers中 訪問 reversedMessage,設置Dep.target指向reversedMessage的watcher,調用該屬性具體方法reversedMessage。

方法中訪問this.message,即會調用this.message代理的get方法,將this.message的dep加入reversedMessage的watcher,同時該dep中的subs添加這個watcher

設置vm.message = 'World',調用message代理的set方法觸發(fā)dep的notify方法’

因為是computed屬性,只是將watcher中的dirty設置為true

最后一步vm.reversedMessage,訪問其get方法時,得知reversedMessage的watcher.dirty為true,調用watcher.evaluate()方法獲取新的值。

這樣,也可以解釋了為什么有些時候當computed沒有被訪問(或者沒有被模板依賴),當修改了this.data值后,通過vue-tools發(fā)現(xiàn)其computed中的值沒有變化的原因,因為沒有觸發(fā)到其get方法。

現(xiàn)在在回頭看 vuex的使用會有一種豁然開朗的感覺 回看vuex 的設計思路

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • Vue 3.0中jsx語法的使用

    Vue 3.0中jsx語法的使用

    這篇文章主要介紹了Vue 3.0 中 jsx 語法使用,幫助大家更好的理解和使用vue框架,感興趣的朋友可以了解下
    2020-11-11
  • Vue?插槽?Slots源碼解析與用法詳解

    Vue?插槽?Slots源碼解析與用法詳解

    這篇文章主要介紹了Vue?插槽?(Slots)?源碼解析與用法,通過實例,我們全面了解了默認插槽、具名插槽和作用域插槽的用法,并深入理解了其在Vue源碼中的實現(xiàn)原理,需要的朋友可以參考下
    2024-01-01
  • vue中利用simplemde實現(xiàn)markdown編輯器(增加圖片上傳功能)

    vue中利用simplemde實現(xiàn)markdown編輯器(增加圖片上傳功能)

    這篇文章主要介紹了vue中利用simplemde實現(xiàn)markdown編輯器(增加圖片上傳功能),本文通過實例代碼相結合的形式給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-04-04
  • vue實現(xiàn)下拉菜單效果

    vue實現(xiàn)下拉菜單效果

    這篇文章主要為大家詳細介紹了vue實現(xiàn)下拉菜單效果,運用了hover顯示與隱藏以及定位,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-09-09
  • Vue+ElementUI使用vue-pdf實現(xiàn)預覽功能

    Vue+ElementUI使用vue-pdf實現(xiàn)預覽功能

    這篇文章主要為大家詳細介紹了Vue+ElementUI使用vue-pdf實現(xiàn)預覽功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-11-11
  • Vue動態(tài)構建混合數(shù)據(jù)Treeselect選擇樹及巨樹問題的解決

    Vue動態(tài)構建混合數(shù)據(jù)Treeselect選擇樹及巨樹問題的解決

    這篇文章主要介紹了Vue動態(tài)構建混合數(shù)據(jù)Treeselect選擇樹及巨樹問題的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • 一鍵將Word文檔轉成Vue組件mammoth的應用詳解

    一鍵將Word文檔轉成Vue組件mammoth的應用詳解

    這篇文章主要為大家介紹了一鍵將Word文檔轉成Vue組件mammoth的應用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-02-02
  • vue創(chuàng)建項目卡住不動,vue?create?project卡住不動的解決

    vue創(chuàng)建項目卡住不動,vue?create?project卡住不動的解決

    這篇文章主要介紹了vue創(chuàng)建項目卡住不動,vue?create?project卡住不動的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • vue2.0開發(fā)實踐總結之疑難篇

    vue2.0開發(fā)實踐總結之疑難篇

    這篇文章主要為大家總結了vue2.0開發(fā)實踐的疑難,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-12-12
  • vue?請求后端數(shù)據(jù)的示例代碼

    vue?請求后端數(shù)據(jù)的示例代碼

    在vue中,我們如何通過請求接口來訪問后端的數(shù)據(jù)呢?在這里簡單總結了一個小示例,對vue請求后端數(shù)據(jù)實例代碼感興趣的朋友一起看看吧
    2022-09-09

最新評論