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

資訊專欄INFORMATION COLUMN

淺析Vue響應(yīng)式原理(一)

lookSomeone / 2484人閱讀

摘要:淺析響應(yīng)式原理一的特點(diǎn)之一是響應(yīng)式,視圖隨著數(shù)據(jù)的更新而更新,在視圖中修改數(shù)據(jù)后實(shí)例中的數(shù)據(jù)也會(huì)同步更新。對(duì)于每個(gè)響應(yīng)式數(shù)據(jù),會(huì)有兩個(gè)實(shí)例,第一個(gè)是在中的閉包遍歷,用途顯而易見(jiàn)。接收一個(gè)回調(diào)函數(shù),會(huì)在重新求值且值更新后執(zhí)行。

淺析Vue響應(yīng)式原理(一)

Vue的特點(diǎn)之一是響應(yīng)式,視圖隨著數(shù)據(jù)的更新而更新,在視圖中修改數(shù)據(jù)后Vue實(shí)例中的數(shù)據(jù)也會(huì)同步更新。內(nèi)部借助依賴(下文中的Dep類)來(lái)實(shí)現(xiàn),數(shù)據(jù)的獲取(即get操作)會(huì)觸發(fā)收集依賴,而對(duì)數(shù)據(jù)賦值(即set操作)會(huì)通知依賴數(shù)據(jù)更新,重新渲染視圖。對(duì)數(shù)據(jù)的get/set操作的攔截借助的是ES5的Object.defineProperty

總體架構(gòu)簡(jiǎn)介

在Vue源碼內(nèi),Dep類作為依賴,Watcher類則用來(lái)收集依賴和通知依賴重新求值。對(duì)于在實(shí)例化時(shí)傳入的數(shù)據(jù),使用工廠函數(shù)defineReactive令其響應(yīng)式。而在實(shí)例后再通過(guò)Vue.set/vm.$set添加的響應(yīng)式數(shù)據(jù),則需要借助Observer類來(lái)使其成為響應(yīng)式數(shù)據(jù),最后也是通過(guò)defineReactive實(shí)現(xiàn)響應(yīng)式。

對(duì)于每個(gè)響應(yīng)式數(shù)據(jù),會(huì)有兩個(gè)Dep實(shí)例,第一個(gè)是在defineReactive中的閉包遍歷,用途顯而易見(jiàn)。而第二個(gè)Dep則在響應(yīng)式數(shù)組的__ob__屬性值中,這個(gè)值是Observer實(shí)例,其實(shí)例屬性dep是Dep實(shí)例,在執(zhí)行Vue.set/vm.$set添加響應(yīng)式數(shù)據(jù)后,會(huì)通知依賴更新。

在講defineReactive之前,先講一下這些輔助類的實(shí)現(xiàn)和用處。

Dep

我們都知道,Vue響應(yīng)式的實(shí)現(xiàn),會(huì)在getter中收集響應(yīng)式數(shù)據(jù)的依賴,在setter中通知依賴數(shù)據(jù)更新,重新計(jì)算數(shù)據(jù)然后來(lái)更新視圖。在Vue內(nèi)部,使用Dep實(shí)例表示依賴,讓我們看一下Dep類是怎么定義的。

Dep有兩個(gè)實(shí)例屬性,一個(gè)靜態(tài)屬性。靜態(tài)屬性targetWatcher實(shí)例,功能是重新求值和通知視圖更新,下文我們會(huì)講到。實(shí)例屬性id是Dep實(shí)例的唯一標(biāo)識(shí),無(wú)需多說(shuō);屬性subs是Watcher實(shí)例數(shù)組,用于收集Watcher實(shí)例,當(dāng)依賴更新時(shí),這些Watcher實(shí)例就會(huì)重新求值。

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

  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 () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

方法addSub用于添加Watcher實(shí)例到subs中,方法removeSub用于從subs移除Watcher實(shí)例。

方法depond會(huì)在收集依賴的時(shí)候調(diào)用,實(shí)際上執(zhí)行了Watcher的實(shí)例方法addDep,在addDep內(nèi)除了調(diào)用dep實(shí)例的addSup方法外,還做了避免重復(fù)收集Watcher實(shí)例的工作。這個(gè)方法會(huì)在Vue為響應(yīng)式數(shù)據(jù)設(shè)置的自定義getter中執(zhí)行。

notify方法則遍歷subs,執(zhí)行Watcher實(shí)例方法update來(lái)重新求值。這個(gè)方法會(huì)在Vue為響應(yīng)式數(shù)據(jù)設(shè)置的自定義setter中執(zhí)行。

有人可能有疑問(wèn),target是靜態(tài)屬性,那不是每個(gè)實(shí)例的target都一樣的?實(shí)際上,重新求值的操作在Watcher實(shí)例方法get內(nèi)實(shí)現(xiàn)。在get方法內(nèi),會(huì)先調(diào)用pushTarget來(lái)更新Dep.target,使其指向當(dāng)前Watcher實(shí)例,之前的`Dep.target會(huì)被保存targetStack末尾(相當(dāng)于入棧操作),完成操作后會(huì)執(zhí)行popTarget函數(shù),從targetStack取出最后一個(gè)元素來(lái)還原Dep.target(相當(dāng)于出棧操作)。

Dep.target = null
const targetStack = []

export function pushTarget (_target: ?Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

export function popTarget () {
  Dep.target = targetStack.pop()
}
Watcher

當(dāng)依賴更新時(shí),Watcher類會(huì)重新求值,并可能觸發(fā)重渲染。

constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    // 與渲染相關(guān)的watcher
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.computed = !!options.computed
      this.sync = !!options.sync
      this.before = options.before
    } else {
      this.deep = this.user = this.computed = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.computed // for computed watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== "production"
      ? expOrFn.toString()
      : ""
    // parse expression for getter
    if (typeof expOrFn === "function") {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = function () {}
        process.env.NODE_ENV !== "production" && warn(
          `Failed watching path: "${expOrFn}" ` +
          "Watcher only accepts simple dot-delimited paths. " +
          "For full control, use a function instead.",
          vm
        )
      }
    }
    if (this.computed) {
      this.value = undefined
      this.dep = new Dep()
    } else {
      this.value = this.get()
    }
  }

構(gòu)造函數(shù)接受五個(gè)參數(shù),vm是掛載的Component實(shí)例;expOrFn是觀察的屬性,當(dāng)是字符串時(shí)表示屬性名,是函數(shù)時(shí)會(huì)被當(dāng)成屬性的get方法;cb是屬性更新后執(zhí)行的回調(diào)函數(shù);options是配置項(xiàng);isRenderWatcher表示當(dāng)前實(shí)例是否與渲染相關(guān)。

在構(gòu)造函數(shù)內(nèi),先將實(shí)例屬性vm指向傳入的Component實(shí)例vm,如果當(dāng)前Watcher實(shí)例與渲染相關(guān),會(huì)將其保存在vm._watcher中。接著將當(dāng)前實(shí)例添加到vm._watchers中,同時(shí)根據(jù)傳入的配置項(xiàng)options初始化實(shí)例屬性。實(shí)例屬性getter是監(jiān)聽(tīng)屬性的getter函數(shù),如果expOrFn是函數(shù),直接賦值,否則會(huì)調(diào)用parsePath來(lái)獲取屬性的getter。

parsePath內(nèi)部會(huì)先使用正則來(lái)判斷屬性名,如果有除數(shù)字、字母、.$以外的字符時(shí)視為非法屬性名,直接返回,所以屬性只能是以.分隔的屬性。如果屬性名合法,則parsePath返回一個(gè)閉包函數(shù),調(diào)用時(shí)會(huì)傳入vm,即objvm的引用,這個(gè)閉包函數(shù)最終的目的是從vm實(shí)例里獲取屬性。

const bailRE = /[^w.$]/
export function parsePath (path: string): any {
  if (bailRE.test(path)) {
    return
  }
  const segments = path.split(".")
  return function (obj) {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return
      obj = obj[segments[i]]
    }
    return obj
  }
}

初始化完成之后,如果不是計(jì)算屬性相關(guān)的Watcher實(shí)例,會(huì)調(diào)用實(shí)例方法get求值。

get方法

執(zhí)行g(shù)etter方法求值,完成依賴收集的過(guò)程。

方法開(kāi)始時(shí),執(zhí)行pushTarget(this),將Dep.target指向當(dāng)前Watcher實(shí)例。然后執(zhí)行getter收集依賴,最后將Dep.target復(fù)原,并執(zhí)行cleanDeps遍歷deps。在每次求值之后,都會(huì)調(diào)用cleanupDeps方法重置依賴,具體如何重置,稍后再講。

實(shí)際上,Dep.target指向的實(shí)例是即將要收集的目標(biāo)。

getter的執(zhí)行,除了會(huì)獲取值外,還會(huì)觸發(fā)在defineReactive中為屬性設(shè)置的getter,完成依賴的收集。

  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }
addDep

addDep的功能是將當(dāng)前Watcher實(shí)例添加到傳入的Dep實(shí)例屬性subs數(shù)組里去。

addDep接受一個(gè)Dep實(shí)例作為參數(shù),如果 dep.id 沒(méi)有在集合 newDepIds 之中,則添加。如果不在集合 depIds 中,則將當(dāng)前實(shí)例添加到 dep.subs 中。 簡(jiǎn)單來(lái)說(shuō),這里的操作會(huì)避免重復(fù)收集依賴,這也是不直接調(diào)用dep.addSub(Dep.target)的原因。

  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }

從這里可以看出來(lái)Dep實(shí)例和Watcher實(shí)例會(huì)相互引用。Dep實(shí)例將Watcher實(shí)例保存在實(shí)例屬性subs中,在響應(yīng)式屬性調(diào)用setter時(shí),執(zhí)行notify方法,通知Watcher實(shí)例重新求值。

Watcher實(shí)例將Dep實(shí)例保存在集合newDeps,目的是避免重復(fù)收集依賴,同時(shí)會(huì)執(zhí)行Dep實(shí)例方法addDep,將當(dāng)前Watcher實(shí)例添加到Dep實(shí)例屬性subs中。

cleanupDeps

對(duì)于Watcher來(lái)說(shuō),每次求值的依賴并不一定與上一次的相同,在每次執(zhí)行get之后,都會(huì)調(diào)用cleanupDeps來(lái)重置收集的依賴。Watcher有四個(gè)實(shí)例屬性用于記錄依賴,分別是newDeps/newDepIdsdeps/depIdsnewDepsdeps是保存依賴的數(shù)組,newDepIdsdepIds是保存依賴Id的集合。記錄上一次求值依賴的屬性是deps/depIds,記錄下一次求值依賴的屬性是newDeps/newDepIds(執(zhí)行cleanupDeps時(shí)已經(jīng)調(diào)用過(guò)getter重新求值了,所以說(shuō)是上一次求值,下一次指的是下一次調(diào)用get的時(shí)候)。

  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    // 交換depIds和newDepIds
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    // 交換deps和newDeps
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

首先遍歷deps,如果此次求值的依賴在下一次求值中并不存在,則需要調(diào)用removeSub方法,從subs數(shù)組中移除當(dāng)前Watcher實(shí)例。

接著交換newDeps/newDepIdsdeps/depIds,并清空交換后的newDeps/newDepIds

update

Dep類的notify方法用于通知觀察者重新求值,該方法內(nèi)部實(shí)際是遍歷subs數(shù)組,執(zhí)行Watcher的update方法。

update 方法定義如下。當(dāng)實(shí)例與計(jì)算屬性相關(guān)時(shí),xxx。如果不是計(jì)算屬性相關(guān)時(shí),判斷是否需要同步觸發(fā),同步觸發(fā)時(shí)調(diào)用run,否則執(zhí)行queueWatcher(this),交由調(diào)度模塊統(tǒng)一調(diào)度。

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

銷毀當(dāng)前Watcher實(shí)例。$watch方法返回一個(gè)函數(shù),函數(shù)內(nèi)部就是Watcher實(shí)例調(diào)用teardown方法。

先判斷Watcher實(shí)例是否在活躍狀態(tài)。首先要從Vue實(shí)例的觀察者隊(duì)列_watchers中移除當(dāng)前實(shí)例,如果vm正在銷毀,因?yàn)樾阅艿膯?wèn)題會(huì)跳過(guò)這一操作。接著遍歷deps,取消這些Dep實(shí)例對(duì)當(dāng)前Watcher實(shí)例的訂閱。最后令this.active = false,表示當(dāng)前Watcher實(shí)例已被銷毀。

  teardown () {
    if (this.active) {
      // remove self from vm"s watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
getAndInvoke

不論是同步或異步更新,或者是計(jì)算屬性相關(guān)的Wathcer實(shí)例,最終求值都是通過(guò)getAndInvoke方法。

getAndInvoke接收一個(gè)回調(diào)函數(shù),會(huì)在重新求值且值更新后執(zhí)行。

當(dāng)新值與當(dāng)前值不同時(shí)會(huì)被判定為值已更新。當(dāng)值是對(duì)象時(shí)且this.deep為真時(shí)也判定為值已更新,盡管引用不發(fā)生改變,但其屬性卻可能發(fā)生變化,為避免屬性發(fā)生改變而Watcher判斷未更新的情況出現(xiàn)。

  getAndInvoke (cb: Function) {
    const value = this.get()
    if (
      value !== this.value ||
      // Deep watchers and watchers on Object/Arrays should fire even
      // when the value is the same, because the value may
      // have mutated.
      isObject(value) ||
      this.deep
    ) {
      // set new value
      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)
      }
    }
  }
run

run方法內(nèi)部只是對(duì)getAndInvoke的封裝,傳入的回調(diào)函數(shù)是實(shí)例化時(shí)傳入的函數(shù)。執(zhí)行之前會(huì)先判斷Watcher實(shí)例是否已棄用。

  run () {
    if (this.active) {
      this.getAndInvoke(this.cb)
    }
  }
小結(jié)

由于篇幅的原因,本文只簡(jiǎn)單分析了輔助類和工廠函數(shù)的源碼和功能。干巴巴地講了這么多,現(xiàn)在來(lái)稍微捋一下。

Watcher類會(huì)保存響應(yīng)式數(shù)據(jù)的getter函數(shù),這個(gè)getter函數(shù)可能是實(shí)例化參數(shù)expOrFn(當(dāng)其是函數(shù)類型時(shí)),也可能是執(zhí)行parsePath(expOrFn)獲取到的getter函數(shù)。實(shí)例方法update對(duì)外暴露,用于重新求值,實(shí)際上執(zhí)行真正求值操作的get方法。方法addDep接受一個(gè)Dep實(shí)例參數(shù),在執(zhí)行訂閱操作前還會(huì)執(zhí)行兩個(gè)if判斷,避免重復(fù)訂閱。

Dep類代表依賴,實(shí)例屬性subs是Watcher數(shù)組,代表訂閱了當(dāng)前Dep實(shí)例的觀察者實(shí)例,depond方法收集依賴,notify方法通知觀察者實(shí)例重新求值。訂閱列表中可能會(huì)有與渲染相關(guān)的觀察者,所以可能會(huì)觸發(fā)重渲染。

Observer類與Vue.set/vm.$set的聯(lián)系比較大,所以分析放在后面。

參考鏈接

Vue技術(shù)內(nèi)幕|揭開(kāi)數(shù)據(jù)響應(yīng)系統(tǒng)的面紗

Vue.js源碼

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

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

相關(guān)文章

  • 淺析Vue響應(yīng)原理(三)

    摘要:響應(yīng)式原理之不論如何,最終響應(yīng)式數(shù)據(jù)都要通過(guò)來(lái)實(shí)現(xiàn),實(shí)際要借助新增的。在函數(shù)內(nèi),首先實(shí)例化一個(gè)實(shí)例,會(huì)在稍后添加為響應(yīng)式數(shù)據(jù)自定義的中發(fā)揮作用。只有數(shù)組和對(duì)象才可能是響應(yīng)式,才能返回實(shí)例。參考鏈接技術(shù)內(nèi)幕揭開(kāi)數(shù)據(jù)響應(yīng)系統(tǒng)的面紗源碼 Vue響應(yīng)式原理之defineReactive defineReactive 不論如何,最終響應(yīng)式數(shù)據(jù)都要通過(guò)defineReactive來(lái)實(shí)現(xiàn),實(shí)際要借助...

    tomener 評(píng)論0 收藏0
  • 從數(shù)組入手淺析Vue響應(yīng)原理

    摘要:響應(yīng)式原理為了探究這一切的原因,我再次點(diǎn)開(kāi)了的官網(wǎng)。在官網(wǎng)很下面的位置,找到了關(guān)于響應(yīng)式原理的說(shuō)明。因此,新添加到數(shù)組中的對(duì)象中的屬性,就成了非響應(yīng)式的屬性了,改變它自然不會(huì)讓組件重新渲染。響應(yīng)式屬性的對(duì)象,有這個(gè)對(duì)象就代表是響應(yīng)式的。 ??最近在用Vue開(kāi)發(fā)一個(gè)后臺(tái)管理的demo,有一個(gè)非常常規(guī)的需求。然而這個(gè)常規(guī)的需求中,包含了大量的知識(shí)點(diǎn)。有一個(gè)產(chǎn)品表格,用來(lái)顯示不同產(chǎn)品的信息。...

    dkzwm 評(píng)論0 收藏0
  • 淺析Vue響應(yīng)原理(二)

    摘要:響應(yīng)式原理之之前簡(jiǎn)單介紹了和類的代碼和作用,現(xiàn)在來(lái)介紹一下類和。對(duì)于數(shù)組,響應(yīng)式的實(shí)現(xiàn)稍有不同。不存在時(shí),說(shuō)明不是響應(yīng)式數(shù)據(jù),直接更新。如果對(duì)象是響應(yīng)式的,確保刪除能觸發(fā)更新視圖。 Vue響應(yīng)式原理之Observer 之前簡(jiǎn)單介紹了Dep和Watcher類的代碼和作用,現(xiàn)在來(lái)介紹一下Observer類和set/get。在Vue實(shí)例后再添加響應(yīng)式數(shù)據(jù)時(shí)需要借助Vue.set/vm.$se...

    rockswang 評(píng)論0 收藏0
  • 前方來(lái)報(bào),八月最新資訊--關(guān)于vue2&3的最佳文章推薦

    摘要:哪吒別人的看法都是狗屁,你是誰(shuí)只有你自己說(shuō)了才算,這是爹教我的道理。哪吒去他個(gè)鳥(niǎo)命我命由我,不由天是魔是仙,我自己決定哪吒白白搭上一條人命,你傻不傻敖丙不傻誰(shuí)和你做朋友太乙真人人是否能夠改變命運(yùn),我不曉得。我只曉得,不認(rèn)命是哪吒的命。 showImg(https://segmentfault.com/img/bVbwiGL?w=900&h=378); 出處 查看github最新的Vue...

    izhuhaodev 評(píng)論0 收藏0
  • Vue.nextTick淺析

    摘要:淺析的特點(diǎn)之一就是響應(yīng)式,但數(shù)據(jù)更新時(shí),并不會(huì)立即更新。盡管已經(jīng)更新,但新增的元素并不立即插入到中。實(shí)際在中,執(zhí)行了,這也是自動(dòng)綁定到執(zhí)行上下文的原因。在內(nèi),使用數(shù)組保存回調(diào)函數(shù),表示當(dāng)前狀態(tài),使用函數(shù)來(lái)執(zhí)行回調(diào)隊(duì)列。 Vue.nextTick 淺析 Vue 的特點(diǎn)之一就是響應(yīng)式,但數(shù)據(jù)更新時(shí),DOM 并不會(huì)立即更新。當(dāng)我們有一個(gè)業(yè)務(wù)場(chǎng)景,需要在 DOM 更新之后再執(zhí)行一段代碼時(shí),可以...

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

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

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<