成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專(zhuān)欄INFORMATION COLUMN

vue計(jì)算屬性Computed的小秘密

adie / 2348人閱讀

摘要:中小秘密的發(fā)現(xiàn)之旅首先我們看一段代碼請(qǐng)問(wèn)會(huì)間隔的打印出來(lái)嗎中去掉,再問(wèn)會(huì)間隔的打印出來(lái)嗎如果第二問(wèn)沒(méi)有打印出來(lái),那么在第二問(wèn)的基礎(chǔ)上怎么修改才能再次打印出來(lái)呢我先來(lái)揭曉答案會(huì)打印出來(lái)不會(huì)打印出來(lái)可以用過(guò)添加監(jiān)聽(tīng),來(lái)打印請(qǐng)問(wèn)為什么呢以下是我的

vue中computed小秘密的發(fā)現(xiàn)之旅 首先我們看一段代碼

    
{{ count }}
請(qǐng)問(wèn)

console.log(1)會(huì)間隔的打印出來(lái)嗎?

html中去掉{{ count }},再問(wèn)console.log(1)會(huì)間隔的打印出來(lái)嗎?

如果第二問(wèn)沒(méi)有打印出來(lái),那么在第二問(wèn)的基礎(chǔ)上怎么修改才能再次打印出來(lái)呢?

我先來(lái)揭曉答案

會(huì)打印出來(lái)

不會(huì)打印出來(lái)

可以用過(guò)添加watch監(jiān)聽(tīng)count,來(lái)打印`console.log(1)

    watch: {
        count: function (oldValue, newValue) {

        }
    }
請(qǐng)問(wèn)為什么呢?

以下是我的理解,有誤還請(qǐng)指出,共同進(jìn)步

一句話總結(jié)就是computed是惰性求值,在new watcher時(shí)是計(jì)算屬性時(shí),this.value=undefined所以一開(kāi)始不會(huì)觸發(fā)get進(jìn)行依賴收集即僅僅定義computed的話是沒(méi)有進(jìn)行計(jì)算屬性count的依賴收集(可以類(lèi)似看成data中的數(shù)值,僅僅進(jìn)行了響應(yīng)式get,set的定義,并沒(méi)有觸發(fā)dep.depend,所以當(dāng)值發(fā)生變化的時(shí)候,他并不知道要通知誰(shuí),也就不會(huì)執(zhí)行相應(yīng)的回調(diào)函數(shù)了)

源碼中有這么一段:

depend () {
  if (this.dep && Dep.target) {  //因?yàn)槎栊郧笾担訢ep.target為false
    this.dep.depend()
  }
}

所以如果僅僅是computed的初始化的話并Dep.target就是undefined,所以實(shí)例化的watch并不會(huì)加入dep的中

看看Computed的實(shí)現(xiàn)

computed初始化

function initComputed (vm: Component, computed: Object) {
    const watchers = vm._computedWatchers = Object.create(null)  //(標(biāo)記1)新建一個(gè)沒(méi)有原型鏈的對(duì)象,用來(lái)存`computed`對(duì)象每個(gè)值的watch實(shí)例對(duì)象
    const isSSR = isServerRendering()  //與服務(wù)端渲染有關(guān),暫時(shí)忽略
    for (const key in computed) {
        const userDef = computed[key]  //取key的值,該值大部分是function類(lèi)型
        //下面主要作用就是在非生產(chǎn)環(huán)境中沒(méi)有g(shù)etter,保警告
        const getter = typeof userDef === "function" ? userDef : userDef.get
        if (process.env.NODE_ENV !== "production" && getter == null) {
          warn(
            `Getter is missing for computed property "${key}".`,
            vm
          )
        }
    }
    if (!isSSR) {
      //computed中不同的key,也就是計(jì)算屬性生成watch實(shí)例,
      //watch作用:簡(jiǎn)單看就是當(dāng)值發(fā)生變化時(shí)會(huì)觸通知到watch,觸發(fā)更新,執(zhí)行回調(diào)函數(shù)
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }
    if (!(key in vm)) {
      //作用是將{key: userDef}變成響應(yīng)式,重寫(xiě)其get和set
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== "production") {
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      }
    }
}

defineComputed 先看這個(gè)函數(shù)做了什么

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}
export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
    const shouldCache = !isServerRendering()
    if (typeof userDef === "function") {
      sharedPropertyDefinition.get = shouldCache
        ? createComputedGetter(key)
        : userDef
      sharedPropertyDefinition.set = noop
    } else {
      sharedPropertyDefinition.get = userDef.get
        ? shouldCache && userDef.cache !== false
          ? createComputedGetter(key)
          : userDef.get
        : noop
      sharedPropertyDefinition.set = userDef.set
        ? userDef.set
        : noop
    }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

上面函數(shù)的作用就是改寫(xiě)get與set,關(guān)鍵就是這個(gè)createComputedGetter在做什么?
早版本createComputedGetter的實(shí)現(xiàn)是:

function createComputedGetter(){
    return function computedGetter () {
        //這個(gè)就是之前用來(lái)收集watch實(shí)例的一個(gè)對(duì)象,可看注釋?zhuān)簶?biāo)記1
        const watcher = this._computedWatchers && this._computedWatchers[key]
        if(watcher) {
            if(watcher.dirty) {
                watcher.evaluate()
            }
            if(Dep.target){ //這里也可以看出Dep.target為false時(shí)是不會(huì)觸發(fā)depend,即添加依賴
                watcher.depend()
            }
            return watcher.value
        }
    }
}
重點(diǎn)看看watch
export default class Watcher {

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    //進(jìn)行初始化的定義,忽略無(wú)關(guān)代碼
      if(options) {
          this.lazy = !!options.lazy
      }else {
          this.lazy = false
      }
      this.getter = parsePath(expOrFn) //返回一個(gè)取data值得函數(shù)
      this.dirty = this.lazy   //true
      this.value = this.lazy ? undefined : this.get()  //undefined,當(dāng)不會(huì)執(zhí)行g(shù)et時(shí)也就不會(huì)觸發(fā)get實(shí)例方法中的depend的了
    }

  get () {
    // 偽代碼
    Dep.target = this
    //取值也就是訪問(wèn)觸發(fā)屬性的get,get中又觸發(fā)dep.depend(),而dep.depend內(nèi)部觸發(fā)的是Dep.target.addDep(this),這里的this其實(shí)是Dep實(shí)例
    let value = this.getter.call(vm, vm) 
    Dep.target = undefined
  }

  addDep (dep: Dep) {
    //偽代碼
    const id = dep.id
    if(!this.depIds.has(id)) {
        this.depIds.add(id)
        this.deps.push(dep)
        dep.addSub(this)  //this是watch實(shí)例對(duì)象
    }
  }

  update () {
    // 省略...
  }

  getAndInvoke (cb: Function) {
    // 省略...
  }

  evaluate () {
    this.value = this.get()
    this.dirty = false
  }

  depend () {
    let i = this.deps.length
    while(i --) {
        this.deps[i].depend()
    }
  }
  ...

}

總結(jié): 1.watcher.dirty默認(rèn)為true,執(zhí)行watcher.evaluate()所以computed第一次默認(rèn)會(huì)渲染,與watch不同;2.當(dāng)默認(rèn)渲染,觸發(fā)了get,Dep.target就不是false,就會(huì)執(zhí)行watcher.depend()

watcher.depend() 早版的實(shí)現(xiàn),它有什么問(wèn)題

this.dep這個(gè)數(shù)組中元素都是Dep的實(shí)例對(duì)象,watcher所依賴的所有Dep實(shí)例化列表;
舉個(gè)例子:當(dāng)計(jì)算屬性中return this.num + this.num1,當(dāng)讀取計(jì)算屬性時(shí)會(huì)分別觸發(fā)num與num1的get,get中又觸發(fā)dep.depend(),而dep.depend內(nèi)部觸發(fā)的是Dep.target.addDep(this),這里的this其實(shí)是Dep實(shí)例,這樣就會(huì)分別將不同編號(hào)的num與num1的dep,加入到deps中,最后將計(jì)算屬性的依賴加入到num,num1的Dep中,this.deps[i].depend()也會(huì)加,但之前已加入改id所以猜測(cè)會(huì)直接return掉

這樣當(dāng)num發(fā)生改變,觸發(fā)set,觸發(fā)其notify 方法即遍歷dep.subDeps數(shù)組(subDeps中放的是各種依賴),觸發(fā)依賴的update方法。但之前的update方法看了一下

update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }
}  

可以看出直接走queueWatcher(this)所以就算內(nèi)容沒(méi)有變化,也會(huì)走渲染流程,這就造成了浪費(fèi)

新版本,發(fā)生了變化

第一個(gè)createComputedGetter

function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      watcher.depend()
      return watcher.evaluate()
    }
  }
}

第二個(gè)watcher.depend()

  if (this.dep && Dep.target) {  
    this.dep.depend()
  }
}

上面這里的dep又是哪里來(lái)的呢?在watch類(lèi)中加了下面代碼

if (this.computed) {
    this.value = undefined
    this.dep = new Dep()   //類(lèi)似一個(gè)Object對(duì)象,進(jìn)行observer設(shè)置get,set響應(yīng)式時(shí)會(huì)進(jìn)let dep = new Dep, 來(lái)收集改值得依賴
  } else {
    this.value = this.get()
  }

所以從上面的實(shí)現(xiàn)可以看出,對(duì)當(dāng)前計(jì)算屬性自身也生成一個(gè)dep列表進(jìn)行收集;完全可以把一個(gè)computed的初始化看出data中數(shù)據(jù)的初始化,只不過(guò)該值又依賴多個(gè)依賴

第三個(gè)evaluate

evaluate () {
  if (this.dirty) {
    this.value = this.get()
    this.dirty = false
  }
  return this.value
}

關(guān)鍵的update也做了修改,

update () {
  /* istanbul ignore else */
  if (this.computed) {
    if (this.dep.subs.length === 0) {
      this.dirty = true
    } else {
      this.getAndInvoke(() => {
        this.dep.notify()
      })
    }
  } else if (this.sync) {
    this.run()
  } else {
    queueWatcher(this)
  }
},
//當(dāng)計(jì)算屬性的值發(fā)生變化時(shí),改觸發(fā)回調(diào)函數(shù)或者進(jìn)行渲染,而不是通過(guò)之前值(例如num改變)變化就觸發(fā)回調(diào)
getAndInvoke (cb: Function) {
    const value = this.get()
    if (
      value !== this.value ||
      isObject(value) ||
      this.deep
    ) {
      const oldValue = this.value
      this.value = value
      this.dirty = false
      if (this.user) {
        try {
          cb.call(this.vm, value, oldValue)
        } catch (e) {
          handleError(e, this.vm, `callback for watcher "${this.expression}"`)
        }
      } else {
        cb.call(this.vm, value, oldValue)
      }
    }
  }

當(dāng)觸發(fā)update時(shí)首先通過(guò)getAndInvoke函數(shù)進(jìn)行值得比較,看是否發(fā)生變化,即只有在變化時(shí)才會(huì)執(zhí)行,執(zhí)行的是this.dep.notify(),而這邊打的this是當(dāng)前watch實(shí)例對(duì)象;因?yàn)橹熬吞砑恿艘蕾?b>this.dep.depend()所以接著觸發(fā)cb.call(this.vm, value, oldValue)cb是:this.dep.notify()但this指向了vm用來(lái)觸發(fā)渲染更新

總結(jié)

計(jì)算屬性的觀察者是惰性求值,需要手動(dòng)通過(guò)get

怎么手動(dòng)get,所以有了問(wèn)題的第二問(wèn),和第三問(wèn)

觸發(fā)了get,也就是觸發(fā)了createComputedGetter函數(shù),就會(huì)去取值this.value = this.get(),進(jìn)行第一次渲染或取值;同時(shí)watcher.depend(),將計(jì)算屬性的依賴添加至dep中,

值發(fā)送變化時(shí),輸出watch.update,首先判斷是否存在依賴,存在則只需watcher.getAndInvoke(cb),

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/109508.html

相關(guān)文章

  • 從 VantComponent 談 小程序維護(hù)

    摘要:不多廢話,先說(shuō)結(jié)論小程序組件寫(xiě)法這里就不再介紹。在官方文檔中,我們可以看到使用構(gòu)造器構(gòu)造頁(yè)面事實(shí)上,小程序的頁(yè)面也可以視為自定義組件。經(jīng)過(guò)一番測(cè)試,得出結(jié)果為為了簡(jiǎn)便。畢竟官方標(biāo)準(zhǔn),不用擔(dān)心其他一系列后續(xù)問(wèn)題。 在開(kāi)發(fā)小程序的時(shí)候,我們總是期望用以往的技術(shù)規(guī)范和語(yǔ)法特點(diǎn)來(lái)書(shū)寫(xiě)當(dāng)前的小程序,所以才會(huì)有各色的小程序框架,例如 mpvue、taro 等這些編譯型框架。當(dāng)然這些框架本身對(duì)于新開(kāi)...

    worldligang 評(píng)論0 收藏0
  • Vue.js-計(jì)算屬性和class與style綁定

    摘要:每一個(gè)計(jì)算屬性都包含一個(gè)和一個(gè)。使用計(jì)算屬性的原因在于它的依賴緩存。及與綁定的主要用法是動(dòng)態(tài)更新元素上的屬性。測(cè)試文字當(dāng)?shù)谋磉_(dá)式過(guò)長(zhǎng)或邏輯復(fù)雜時(shí),還可以綁定一個(gè)計(jì)算屬性。 學(xué)習(xí)筆記:前端開(kāi)發(fā)文檔 計(jì)算屬性 所有的計(jì)算屬性都以函數(shù)的形式寫(xiě)在Vue實(shí)例中的computed選項(xiàng)內(nèi),最終返回計(jì)算后的結(jié)果。 計(jì)算屬性的用法 在一個(gè)計(jì)算屬性中可以完成各種復(fù)雜的邏輯,包括運(yùn)算、函數(shù)調(diào)用等,只要最終...

    Shihira 評(píng)論0 收藏0
  • vue.js的小知識(shí)

    摘要:對(duì)于最終的結(jié)果,兩種方式確實(shí)是相同的。然而,不同的是計(jì)算屬性是基于它們的依賴進(jìn)行緩存的。這就意味著只要還沒(méi)有發(fā)生改變,多次訪問(wèn)計(jì)算屬性會(huì)立即返回之前的計(jì)算結(jié)果,而不必再次執(zhí)行函數(shù)。 vue.js vue.js 的構(gòu)造 new Vue({}) new new MyComponent() 屬性與方法 vue會(huì)代理其data對(duì)象里所有的屬性 例如 data.a=vn.a vm.$i...

    light 評(píng)論0 收藏0
  • 美團(tuán)小程序框架mpvue蹲坑指南

    摘要:這個(gè)是我們約定的額外的配置這個(gè)字段下的數(shù)據(jù)會(huì)被填充到頂部欄的統(tǒng)一配置美團(tuán)汽車(chē)票同時(shí),這個(gè)時(shí)候,我們會(huì)根據(jù)的頁(yè)面數(shù)據(jù),自動(dòng)填充到中的字段。 美團(tuán)小程序框架mpvue(花名:沒(méi)朋友)蹲坑指南 第一次接觸小程序大概是17年初,當(dāng)時(shí)小程序剛剛內(nèi)側(cè),當(dāng)時(shí)就被各種限制折騰的死去活來(lái)的,單向綁定,沒(méi)有promise,請(qǐng)求數(shù)限制,包大小限制,各種反人類(lèi),...反正我是感受到了滿滿的惡意.最近接到一個(gè)工...

    AprilJ 評(píng)論0 收藏0
  • 淺談Vue計(jì)算屬性computed的實(shí)現(xiàn)原理

    摘要:雖然計(jì)算屬性在大多數(shù)情況下更合適,但有時(shí)也需要一個(gè)自定義的偵聽(tīng)器。當(dāng)某個(gè)屬性發(fā)生變化,觸發(fā)攔截函數(shù),然后調(diào)用自身消息訂閱器的方法,遍歷當(dāng)前中保存著所有訂閱者的數(shù)組,并逐個(gè)調(diào)用的方法,完成響應(yīng)更新。 雖然目前的技術(shù)棧已由Vue轉(zhuǎn)到了React,但從之前使用Vue開(kāi)發(fā)的多個(gè)項(xiàng)目實(shí)際經(jīng)歷來(lái)看還是非常愉悅的,Vue文檔清晰規(guī)范,api設(shè)計(jì)簡(jiǎn)潔高效,對(duì)前端開(kāi)發(fā)人員友好,上手快,甚至個(gè)人認(rèn)為在很多...

    laznrbfe 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<