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

資訊專欄INFORMATION COLUMN

做面試的不倒翁:淺談 Vue 中 computed 實(shí)現(xiàn)原理

Anonymous1 / 3173人閱讀

摘要:當(dāng)某個(gè)屬性發(fā)生變化,觸發(fā)攔截函數(shù),然后調(diào)用自身消息訂閱器的方法,遍歷當(dāng)前中保存著所有訂閱者的數(shù)組,并逐個(gè)調(diào)用的方法,完成響應(yīng)更新。

編者按:我們會(huì)不時(shí)邀請(qǐng)工程師談?wù)動(dòng)幸馑嫉募夹g(shù)細(xì)節(jié),希望知其所以然能讓大家在面試有更出色表現(xiàn)。也給面試官提供更多思路。

雖然目前的技術(shù)棧已由 Vue 轉(zhuǎn)到了 React,但從之前使用 Vue 開發(fā)的多個(gè)項(xiàng)目實(shí)際經(jīng)歷來看還是非常愉悅的,Vue 文檔清晰規(guī)范,api 設(shè)計(jì)簡潔高效,對(duì)前端開發(fā)人員友好,上手快,甚至個(gè)人認(rèn)為在很多場景使用 Vue 比 React 開發(fā)效率更高,之前也有斷斷續(xù)續(xù)研讀過 Vue 的源碼,但一直沒有梳理總結(jié),所以在此做一些技術(shù)歸納同時(shí)也加深自己對(duì) Vue 的理解,那么今天要寫的便是 Vue 中最常用到的 API 之一 computed 的實(shí)現(xiàn)原理。

基本介紹

話不多說,一個(gè)最基本的例子如下:

{{fullName}}

new Vue({
    data: {
        firstName: "Xiao",
        lastName: "Ming"
    },
    computed: {
        fullName: function () {
            return this.firstName + " " + this.lastName
        }
    }
})

Vue 中我們不需要在 template 里面直接計(jì)算 {{this.firstName + " " + this.lastName}},因?yàn)樵谀0嬷蟹湃胩嗦暶魇降倪壿嫊?huì)讓模板本身過重,尤其當(dāng)在頁面中使用大量復(fù)雜的邏輯表達(dá)式處理數(shù)據(jù)時(shí),會(huì)對(duì)頁面的可維護(hù)性造成很大的影響,而 computed 的設(shè)計(jì)初衷也正是用于解決此類問題。

對(duì)比偵聽器 watch

當(dāng)然很多時(shí)候我們使用 computed 時(shí)往往會(huì)與 Vue 中另一個(gè) API 也就是偵聽器 watch 相比較,因?yàn)樵谀承┓矫嫠鼈兪且恢碌?,都是?Vue 的依賴追蹤機(jī)制為基礎(chǔ),當(dāng)某個(gè)依賴數(shù)據(jù)發(fā)生變化時(shí),所有依賴這個(gè)數(shù)據(jù)的相關(guān)數(shù)據(jù)或函數(shù)都會(huì)自動(dòng)發(fā)生變化或調(diào)用。

雖然計(jì)算屬性在大多數(shù)情況下更合適,但有時(shí)也需要一個(gè)自定義的偵聽器。這就是為什么 Vue 通過 watch 選項(xiàng)提供了一個(gè)更通用的方法來響應(yīng)數(shù)據(jù)的變化。當(dāng)需要在數(shù)據(jù)變化時(shí)執(zhí)行異步或開銷較大的操作時(shí),這個(gè)方式是最有用的。

從 Vue 官方文檔對(duì) watch 的解釋我們可以了解到,使用 watch 選項(xiàng)允許我們執(zhí)行異步操作(訪問一個(gè) API)或高消耗性能的操作,限制我們執(zhí)行該操作的頻率,并在我們得到最終結(jié)果前,設(shè)置中間狀態(tài),而這些都是計(jì)算屬性無法做到的。

下面還另外總結(jié)了幾點(diǎn)關(guān)于 computedwatch 的差異:

computed 是計(jì)算一個(gè)新的屬性,并將該屬性掛載到 vm(Vue 實(shí)例)上,而 watch 是監(jiān)聽已經(jīng)存在且已掛載到 vm 上的數(shù)據(jù),所以用 watch 同樣可以監(jiān)聽 computed 計(jì)算屬性的變化(其它還有 data、props

computed 本質(zhì)是一個(gè)惰性求值的觀察者,具有緩存性,只有當(dāng)依賴變化后,第一次訪問 computed 屬性,才會(huì)計(jì)算新的值,而 watch 則是當(dāng)數(shù)據(jù)發(fā)生變化便會(huì)調(diào)用執(zhí)行函數(shù)

從使用場景上說,computed 適用一個(gè)數(shù)據(jù)被多個(gè)數(shù)據(jù)影響,而 watch 適用一個(gè)數(shù)據(jù)影響多個(gè)數(shù)據(jù);

以上我們了解了 computedwatch 之間的一些差異和使用場景的區(qū)別,當(dāng)然某些時(shí)候兩者并沒有那么明確嚴(yán)格的限制,最后還是要具體到不同的業(yè)務(wù)進(jìn)行分析。

原理分析

言歸正傳,回到文章的主題 computed 身上,為了更深層次地了解計(jì)算屬性的內(nèi)在機(jī)制,接下來就讓我們一步步探索 Vue 源碼中關(guān)于它的實(shí)現(xiàn)原理吧。

在分析 computed 源碼之前我們先得對(duì) Vue 的響應(yīng)式系統(tǒng)有一個(gè)基本的了解,Vue 稱其為非侵入性的響應(yīng)式系統(tǒng),數(shù)據(jù)模型僅僅是普通的 JavaScript 對(duì)象,而當(dāng)你修改它們時(shí),視圖便會(huì)進(jìn)行自動(dòng)更新。

當(dāng)你把一個(gè)普通的 JavaScript 對(duì)象傳給 Vue 實(shí)例的 data 選項(xiàng)時(shí),Vue 將遍歷此對(duì)象所有的屬性,并使用 Object.defineProperty 把這些屬性全部轉(zhuǎn)為 getter/setter,這些 getter/setter 對(duì)用戶來說是不可見的,但是在內(nèi)部它們讓 Vue 追蹤依賴,在屬性被訪問和修改時(shí)通知變化,每個(gè)組件實(shí)例都有相應(yīng)的 watcher 實(shí)例對(duì)象,它會(huì)在組件渲染的過程中把屬性記錄為依賴,之后當(dāng)依賴項(xiàng)的 setter 被調(diào)用時(shí),會(huì)通知 watcher 重新計(jì)算,從而致使它關(guān)聯(lián)的組件得以更新。

Vue 響應(yīng)系統(tǒng),其核心有三點(diǎn):observe、watcherdep

observe:遍歷 data 中的屬性,使用 Object.defineProperty 的 get/set 方法對(duì)其進(jìn)行數(shù)據(jù)劫持;

dep:每個(gè)屬性擁有自己的消息訂閱器 dep,用于存放所有訂閱了該屬性的觀察者對(duì)象;

watcher:觀察者(對(duì)象),通過 dep 實(shí)現(xiàn)對(duì)響應(yīng)屬性的監(jiān)聽,監(jiān)聽到結(jié)果后,主動(dòng)觸發(fā)自己的回調(diào)進(jìn)行響應(yīng)。

對(duì)響應(yīng)式系統(tǒng)有一個(gè)初步了解后,我們?cè)賮矸治鲇?jì)算屬性。
首先我們找到計(jì)算屬性的初始化是在 src/core/instance/state.js 文件中的 initState 函數(shù)中完成的

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)
  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)
  }
}

調(diào)用了 initComputed 函數(shù)(其前后也分別初始化了 initDatainitWatch )并傳入兩個(gè)參數(shù) vm 實(shí)例和 opt.computed 開發(fā)者定義的 computed 選項(xiàng),轉(zhuǎn)到 initComputed 函數(shù):

const computedWatcherOptions = { computed: true }

function initComputed (vm: Component, computed: Object) {
  // $flow-disable-line
  const watchers = vm._computedWatchers = Object.create(null)
  // computed properties are just getters during SSR
  const isSSR = isServerRendering()

  for (const key in computed) {
    const userDef = computed[key]
    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) {
      // create internal watcher for the computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    if (!(key in vm)) {
      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)
      }
    }
  }
}

從這段代碼開始我們觀察這幾部分:

獲取計(jì)算屬性的定義 userDefgetter 求值函數(shù)

const userDef = computed[key]
const getter = typeof userDef === "function" ? userDef : userDef.get

定義一個(gè)計(jì)算屬性有兩種寫法,一種是直接跟一個(gè)函數(shù),另一種是添加 setget 方法的對(duì)象形式,所以這里首先獲取計(jì)算屬性的定義 userDef,再根據(jù) userDef 的類型獲取相應(yīng)的 getter 求值函數(shù)。

計(jì)算屬性的觀察者 watcher 和消息訂閱器 dep

watchers[key] = new Watcher(
    vm,
    getter || noop,
    noop,
    computedWatcherOptions
)

這里的 watchers 也就是 vm._computedWatchers 對(duì)象的引用,存放了每個(gè)計(jì)算屬性的觀察者 watcher 實(shí)例(注:后文中提到的“計(jì)算屬性的觀察者”、“訂閱者”和 watcher 均指代同一個(gè)意思但注意和 Watcher 構(gòu)造函數(shù)區(qū)分),Watcher 構(gòu)造函數(shù)在實(shí)例化時(shí)傳入了 4 個(gè)參數(shù):vm 實(shí)例、getter 求值函數(shù)、noop 空函數(shù)、computedWatcherOptions 常量對(duì)象(在這里提供給 Watcher 一個(gè)標(biāo)識(shí) {computed:true} 項(xiàng),表明這是一個(gè)計(jì)算屬性而不是非計(jì)算屬性的觀察者,我們來到 Watcher 構(gòu)造函數(shù)的定義:

class Watcher {
  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    if (options) {
      this.computed = !!options.computed
    } 

    if (this.computed) {
      this.value = undefined
      this.dep = new Dep()
    } else {
      this.value = this.get()
    }
  }
  
  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      
    } finally {
      popTarget()
    }
    return value
  }
  
  update () {
    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)
    }
  }

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

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

為了簡潔突出重點(diǎn),這里我手動(dòng)去掉了我們暫時(shí)不需要關(guān)心的代碼片段。
觀察 Watcherconstructor ,結(jié)合剛才講到的 new Watcher 傳入的第四個(gè)參數(shù) {computed:true} 知道,對(duì)于計(jì)算屬性而言 watcher 會(huì)執(zhí)行 if 條件成立的代碼 this.dep = new Dep(),而 dep 也就是創(chuàng)建了該屬性的消息訂閱器。

export default class Dep {
  static target: ?Watcher;
  subs: Array;

  constructor () {
    this.id = uid++
    this.subs = []
  }

  addSub (sub: Watcher) {
    this.subs.push(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++) {
      subs[i].update()
    }
  }
}

Dep.target = null
  

Dep 同樣精簡了部分代碼,我們觀察 WatcherDep 的關(guān)系,用一句話總結(jié)

watcher 中實(shí)例化了 dep 并向 dep.subs 中添加了訂閱者,dep 通過 notify 遍歷了 dep.subs 通知每個(gè) watcher 更新。

defineComputed 定義計(jì)算屬性

if (!(key in vm)) {
  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)
  }
}

因?yàn)?computed 屬性是直接掛載到實(shí)例對(duì)象中的,所以在定義之前需要判斷對(duì)象中是否已經(jīng)存在重名的屬性,defineComputed 傳入了三個(gè)參數(shù):vm 實(shí)例、計(jì)算屬性的 key 以及 userDef 計(jì)算屬性的定義(對(duì)象或函數(shù))。
然后繼續(xù)找到 defineComputed 定義處:

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
  }
  if (process.env.NODE_ENV !== "production" &&
      sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        "Computed property "${key}" was assigned to but it has no setter.",
        this
      )
    }
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

在這段代碼的最后調(diào)用了原生 Object.defineProperty 方法,其中傳入的第三個(gè)參數(shù)是屬性描述符sharedPropertyDefinition,初始化為:

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}

隨后根據(jù) Object.defineProperty 前面的代碼可以看到 sharedPropertyDefinitionget/set 方法在經(jīng)過 userDefshouldCache 等多重判斷后被重寫,當(dāng)非服務(wù)端渲染時(shí),sharedPropertyDefinitionget 函數(shù)也就是 createComputedGetter(key) 的結(jié)果,我們找到 createComputedGetter 函數(shù)調(diào)用結(jié)果并最終改寫 sharedPropertyDefinition 大致呈現(xiàn)如下:

sharedPropertyDefinition = {
    enumerable: true,
    configurable: true,
    get: function computedGetter () {
        const watcher = this._computedWatchers && this._computedWatchers[key]
        if (watcher) {
            watcher.depend()
            return watcher.evaluate()
        }
    },
    set: userDef.set || noop
}

當(dāng)計(jì)算屬性被調(diào)用時(shí)便會(huì)執(zhí)行 get 訪問函數(shù),從而關(guān)聯(lián)上觀察者對(duì)象 watcher 然后執(zhí)行 wather.depend() 收集依賴和 watcher.evaluate() 計(jì)算求值。

分析完所有步驟,我們?cè)賮砜偨Y(jié)下整個(gè)流程:

當(dāng)組件初始化的時(shí)候,computeddata 會(huì)分別建立各自的響應(yīng)系統(tǒng),Observer 遍歷 data 中每個(gè)屬性設(shè)置 get/set 數(shù)據(jù)攔截

初始化 computed 會(huì)調(diào)用 initComputed 函數(shù)

注冊(cè)一個(gè) watcher 實(shí)例,并在內(nèi)實(shí)例化一個(gè) Dep 消息訂閱器用作后續(xù)收集依賴(比如渲染函數(shù)的 watcher 或者其他觀察該計(jì)算屬性變化的 watcher

調(diào)用計(jì)算屬性時(shí)會(huì)觸發(fā)其Object.definePropertyget訪問器函數(shù)

調(diào)用 watcher.depend() 方法向自身的消息訂閱器 depsubs 中添加其他屬性的 watcher

調(diào)用 watcherevaluate 方法(進(jìn)而調(diào)用 watcherget 方法)讓自身成為其他 watcher 的消息訂閱器的訂閱者,首先將 watcher 賦給 Dep.target,然后執(zhí)行 getter 求值函數(shù),當(dāng)訪問求值函數(shù)里面的屬性(比如來自 data、props 或其他 computed)時(shí),會(huì)同樣觸發(fā)它們的 get 訪問器函數(shù)從而將該計(jì)算屬性的 watcher 添加到求值函數(shù)中屬性的 watcher 的消息訂閱器 dep 中,當(dāng)這些操作完成,最后關(guān)閉 Dep.target 賦為 null 并返回求值函數(shù)結(jié)果。

當(dāng)某個(gè)屬性發(fā)生變化,觸發(fā) set 攔截函數(shù),然后調(diào)用自身消息訂閱器 depnotify 方法,遍歷當(dāng)前 dep 中保存著所有訂閱者 wathcersubs 數(shù)組,并逐個(gè)調(diào)用 watcherupdate 方法,完成響應(yīng)更新。

文 / 亦然
一枚向往詩與遠(yuǎn)方的 coder

編 / 熒聲

本文已由作者授權(quán)發(fā)布,版權(quán)屬于創(chuàng)宇前端。歡迎注明出處轉(zhuǎn)載本文。本文鏈接:https://knownsec-fed.com/2018...

想要訂閱更多來自知道創(chuàng)宇開發(fā)一線的分享,請(qǐng)搜索關(guān)注我們的微信公眾號(hào):創(chuàng)宇前端(KnownsecFED)。歡迎留言討論,我們會(huì)盡可能回復(fù)。

感謝您的閱讀。

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

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

相關(guān)文章

  • 淺談Vue計(jì)算屬性computed實(shí)現(xiàn)原理

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

    laznrbfe 評(píng)論0 收藏0
  • 前端最實(shí)用書簽(持續(xù)更新)

    摘要:前言一直混跡社區(qū)突然發(fā)現(xiàn)自己收藏了不少好文但是管理起來有點(diǎn)混亂所以將前端主流技術(shù)做了一個(gè)書簽整理不求最多最全但求最實(shí)用。 前言 一直混跡社區(qū),突然發(fā)現(xiàn)自己收藏了不少好文但是管理起來有點(diǎn)混亂; 所以將前端主流技術(shù)做了一個(gè)書簽整理,不求最多最全,但求最實(shí)用。 書簽源碼 書簽導(dǎo)入瀏覽器效果截圖showImg(https://segmentfault.com/img/bVbg41b?w=107...

    sshe 評(píng)論0 收藏0
  • 2019前端面試題匯總(主要為Vue

    摘要:畢業(yè)之后就在一直合肥小公司工作,沒有老司機(jī)沒有技術(shù)氛圍,在技術(shù)的道路上我只能獨(dú)自摸索。于是乎,我果斷辭職,在新年開工之際來到杭州,這里的互聯(lián)網(wǎng)公司應(yīng)該是合肥的幾十倍吧。。。。 畢業(yè)之后就在一直合肥小公司工作,沒有老司機(jī)、沒有技術(shù)氛圍,在技術(shù)的道路上我只能獨(dú)自摸索。老板也只會(huì)畫餅充饑,前途一片迷??床坏饺魏蜗MS谑呛?,我果斷辭職,在新年開工之際來到杭州,這里的互聯(lián)網(wǎng)公司應(yīng)該是合肥的幾十...

    arashicage 評(píng)論0 收藏0
  • Java學(xué)習(xí)路線總結(jié),搬磚工逆襲Java架構(gòu)師(全網(wǎng)最強(qiáng))

    摘要:哪吒社區(qū)技能樹打卡打卡貼函數(shù)式接口簡介領(lǐng)域優(yōu)質(zhì)創(chuàng)作者哪吒公眾號(hào)作者架構(gòu)師奮斗者掃描主頁左側(cè)二維碼,加入群聊,一起學(xué)習(xí)一起進(jìn)步歡迎點(diǎn)贊收藏留言前情提要無意間聽到領(lǐng)導(dǎo)們的談話,現(xiàn)在公司的現(xiàn)狀是碼農(nóng)太多,但能獨(dú)立帶隊(duì)的人太少,簡而言之,不缺干 ? 哪吒社區(qū)Java技能樹打卡?【打卡貼 day2...

    Scorpion 評(píng)論0 收藏0
  • 2018 淺談前端面試那些事

    摘要:聲明的變量不得改變值,這意味著,一旦聲明變量,就必須立即初始化,不能留到以后賦值。 雖然今年沒有換工作的打算 但為了跟上時(shí)代的腳步 還是忍不住整理了一份最新前端知識(shí)點(diǎn) 知識(shí)點(diǎn)匯總 1.HTML HTML5新特性,語義化瀏覽器的標(biāo)準(zhǔn)模式和怪異模式xhtml和html的區(qū)別使用data-的好處meta標(biāo)簽canvasHTML廢棄的標(biāo)簽IE6 bug,和一些定位寫法css js放置位置和原因...

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

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

0條評(píng)論

閱讀需要支付1元查看
<