国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

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

Anonymous1 / 3118人閱讀

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

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

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

基本介紹

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

{{fullName}}

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

Vue 中我們不需要在 template 里面直接計算 {{this.firstName + " " + this.lastName}},因為在模版中放入太多聲明式的邏輯會讓模板本身過重,尤其當(dāng)在頁面中使用大量復(fù)雜的邏輯表達(dá)式處理數(shù)據(jù)時,會對頁面的可維護(hù)性造成很大的影響,而 computed 的設(shè)計初衷也正是用于解決此類問題。

對比偵聽器 watch

當(dāng)然很多時候我們使用 computed 時往往會與 Vue 中另一個 API 也就是偵聽器 watch 相比較,因為在某些方面它們是一致的,都是以 Vue 的依賴追蹤機(jī)制為基礎(chǔ),當(dāng)某個依賴數(shù)據(jù)發(fā)生變化時,所有依賴這個數(shù)據(jù)的相關(guān)數(shù)據(jù)或函數(shù)都會自動發(fā)生變化或調(diào)用。

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

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

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

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

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

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

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

原理分析

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

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

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

Vue 響應(yīng)系統(tǒng),其核心有三點:observewatcherdep

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

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

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

對響應(yīng)式系統(tǒng)有一個初步了解后,我們再來分析計算屬性。
首先我們找到計算屬性的初始化是在 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 )并傳入兩個參數(shù) vm 實例和 opt.computed 開發(fā)者定義的 computed 選項,轉(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)
      }
    }
  }
}

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

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

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

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

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

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

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

為了簡潔突出重點,這里我手動去掉了我們暫時不需要關(guān)心的代碼片段。
觀察 Watcherconstructor ,結(jié)合剛才講到的 new Watcher 傳入的第四個參數(shù) {computed:true} 知道,對于計算屬性而言 watcher 會執(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 中實例化了 dep 并向 dep.subs 中添加了訂閱者,dep 通過 notify 遍歷了 dep.subs 通知每個 watcher 更新。

defineComputed 定義計算屬性

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

因為 computed 屬性是直接掛載到實例對象中的,所以在定義之前需要判斷對象中是否已經(jīng)存在重名的屬性,defineComputed 傳入了三個參數(shù):vm 實例、計算屬性的 key 以及 userDef 計算屬性的定義(對象或函數(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 方法,其中傳入的第三個參數(shù)是屬性描述符sharedPropertyDefinition,初始化為:

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

隨后根據(jù) Object.defineProperty 前面的代碼可以看到 sharedPropertyDefinitionget/set 方法在經(jīng)過 userDefshouldCache 等多重判斷后被重寫,當(dāng)非服務(wù)端渲染時,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)計算屬性被調(diào)用時便會執(zhí)行 get 訪問函數(shù),從而關(guān)聯(lián)上觀察者對象 watcher 然后執(zhí)行 wather.depend() 收集依賴和 watcher.evaluate() 計算求值。

分析完所有步驟,我們再來總結(jié)下整個流程:

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

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

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

調(diào)用計算屬性時會觸發(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ù)里面的屬性(比如來自 dataprops 或其他 computed)時,會同樣觸發(fā)它們的 get 訪問器函數(shù)從而將該計算屬性的 watcher 添加到求值函數(shù)中屬性的 watcher 的消息訂閱器 dep 中,當(dāng)這些操作完成,最后關(guān)閉 Dep.target 賦為 null 并返回求值函數(shù)結(jié)果。

當(dāng)某個屬性發(fā)生變化,觸發(fā) set 攔截函數(shù),然后調(diào)用自身消息訂閱器 depnotify 方法,遍歷當(dāng)前 dep 中保存著所有訂閱者 wathcersubs 數(shù)組,并逐個調(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ā)一線的分享,請搜索關(guān)注我們的微信公眾號:創(chuàng)宇前端(KnownsecFED)。歡迎留言討論,我們會盡可能回復(fù)。

感謝您的閱讀。

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

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/97739.html

相關(guān)文章

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

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

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

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

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

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

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

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

    Scorpion 評論0 收藏0
  • 2018 淺談前端面試那些事

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

    LiuRhoRamen 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<