成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Vue: Binding與Watcher

tyheist / 3470人閱讀

摘要:當數(shù)據(jù)改變時,我們不需要直接觸發(fā)所有的回調(diào)函數(shù),而是去通知對應(yīng)的數(shù)據(jù)的,然后由去執(zhí)行相應(yīng)的邏輯。對于其邏輯可能是一個指令用于連接與響應(yīng)式數(shù)據(jù)或者是一個偵聽器的回調(diào)函數(shù),這樣就能符合單一職責原則,解除模塊之間的耦合度,讓程序更易維護。

前言

  首先歡迎大家關(guān)注我的Github博客,也算是對我的一點鼓勵,畢竟寫東西沒法獲得變現(xiàn),能堅持下去也是靠的是自己的熱情和大家的鼓勵。接下來的日子我應(yīng)該會著力寫一系列關(guān)于Vue與React內(nèi)部原理的文章,感興趣的同學點個關(guān)注或者Star。

回顧

  上一篇文章Vue響應(yīng)式數(shù)據(jù): Observer模塊實現(xiàn)我們介紹Vue早期代碼中的Observer模塊,Observer模塊實現(xiàn)了數(shù)據(jù)的響應(yīng)化并且用監(jiān)聽者模式對外提供功能。任何模塊想要感知到數(shù)據(jù)變化,只要監(jiān)聽Observer模塊對應(yīng)的事件,從而將整個數(shù)據(jù)響應(yīng)化的過程與相應(yīng)的處理邏輯相獨立。

  其實我們可以思考一下,在Vue中一個響應(yīng)式數(shù)據(jù)發(fā)生改變可能會觸發(fā)那些邏輯呢?可能是一個對應(yīng)的DOM改變,也可能是一個watch偵聽器的回調(diào)函數(shù)的調(diào)用,或者是導致一個computed計算屬性函數(shù)的調(diào)用。其實在之前的文章響應(yīng)式數(shù)據(jù)與數(shù)據(jù)依賴基本原理我們就引入了一個DepWatcher的概念。同時還附上了一個概念圖:

  我們當時為了解耦響應(yīng)式數(shù)據(jù)和對應(yīng)的數(shù)據(jù)改變后處理邏輯,我們增加了DepWatcher的概念,每一個響應(yīng)式數(shù)據(jù)都有一個Dep用于集中管理和維護該數(shù)據(jù)改變時對應(yīng)執(zhí)行回調(diào)函數(shù)。當數(shù)據(jù)改變時,我們不需要直接觸發(fā)所有的回調(diào)函數(shù),而是去通知對應(yīng)的數(shù)據(jù)的Dep,然后由Dep去執(zhí)行相應(yīng)的邏輯。
  
  

  將這個概念抽象出現(xiàn)出來,其基本邏輯就是上圖所示。了解設(shè)計模式的同學,應(yīng)該很快就能意識到這是一個代理模式。引入Dep的目的其實也就是代理模式的優(yōu)點,分離調(diào)用者和被調(diào)用者的邏輯,降低耦合性??梢娫O(shè)計模式在軟件開發(fā)中作用是非常廣泛的,甚至有時候你都沒有意識到它的存在。

  我們前面說過,響應(yīng)式數(shù)據(jù)改變后可能對應(yīng)的是DOM修改的處理邏輯或者是watch函數(shù)對應(yīng)的處理邏輯。為了弱化不同類型的處理邏輯,我們引入了Watcher類。Dep并不會關(guān)心每一個不同的注冊者的邏輯,Dep只知道每一個注冊者都是一個Watcher的實例,每次發(fā)生改變時只需要調(diào)用對應(yīng)的update方法,具體的邏輯被隱藏在update函數(shù)之后。
  

Vue的早期實現(xiàn)思路

  Vue的內(nèi)部實現(xiàn)邏輯基本上和我們的邏輯是一樣的。由bindings模塊負責上面所講的Dep的功能。
  

bindings模塊

  在Vue組件的初始化函數(shù)_init中調(diào)用了:
  

this._initBindings()

  目的就是創(chuàng)建組件對應(yīng)的binding Tree,在研究_initBindings函數(shù)之前,我們先看看Binding:

function Binding () {
  this._subs = []
}

var p = Binding.prototype

p._addChild = function (key, child) {
  child = child || new Binding()
  this[key] = child
  return child
}

p._addSub = function (sub) {
  this._subs.push(sub)
}

p._removeSub = function (sub) {
  this._subs.splice(this._subs.indexOf(sub), 1)
}

p._notify = function () {
  for (var i = 0, l = this._subs.length; i < l; i++) {
    this._subs[i].update()
  }
}

  Binding類的定義非常簡單,內(nèi)部屬性_subs數(shù)組用來存儲對應(yīng)的訂閱者(subscription),也就是我們后面將要說的Watcher,原型方法分別是:
  

_addSub: 用來增加對應(yīng)的訂閱者

_removeSub: 用來刪除對應(yīng)的訂閱者

_notify: 通知所有的訂閱者,其實就是遍歷整個訂閱者數(shù)據(jù),并調(diào)用對應(yīng)的update方法。

_addChild: 用來增加一個屬性名為key值的子Binding,其實也就用來構(gòu)建Binding Tree。

  看完Binding類我們接著看_initBindings函數(shù)的定義:
  

var Binding = require("../binding")
var Path = require("../parse/path")
var Observer = require("../observe/observer")

exports._initBindings = function () {
  var root = this._rootBinding = new Binding()
  root._addChild("$data", root)
  
  if (this.$parent) {
    root._addChild("$parent", this.$parent._rootBinding)
    root._addChild("$root", this.$root._rootBinding)
  }

  this._observer
    // simple updates
    .on("set", this._updateBindingAt)
    .on("mutate", this._updateBindingAt)
    .on("delete", this._updateBindingAt)
    .on("add", this._updateAdd)
    .on("get", this._collectDep)
}

  _initBindings是在初始化Vue組件實例中調(diào)用的,因此this也就是指向的是當前的Vue實例對象。
首先我們看到給Vue的實例對象中創(chuàng)建了私有屬性_rootBinding,作為Bindings Tree的根節(jié)點,并且_rootBinding$data屬性指向就是根節(jié)點本身。如果當前的Vue實例中存在父節(jié)點($parent),則通過給給_rootBinding添加$parent屬性來構(gòu)建起與父級Bindings Tree的關(guān)聯(lián)。我們知道Bindings的主要作用就是在響應(yīng)式數(shù)據(jù)改變時觸發(fā)對應(yīng)的邏輯,因此_initBindings函數(shù)監(jiān)聽了實例屬性_observer的各個事件。

set: 監(jiān)聽響應(yīng)式數(shù)據(jù)對象屬性值修改

mutate: 監(jiān)聽響應(yīng)式數(shù)據(jù)數(shù)組修改

delete: 監(jiān)聽響應(yīng)式數(shù)據(jù)對象屬性刪除

add: 監(jiān)聽響應(yīng)式數(shù)據(jù)對象屬性增加

get: 監(jiān)聽響應(yīng)式數(shù)據(jù)某個屬性被調(diào)用

  我們看到對于setmutate、delete事件我們都調(diào)用了內(nèi)部的_updateBindingAt函數(shù),接著看
_updateBindingAt函數(shù)定義:

exports._updateBindingAt = function (path) {
  // root binding updates on any change
  this._rootBinding._notify()
  var binding = this._getBindingAt(path, true)
  if (binding) {
    binding._notify()
  }
}

exports._getBindingAt = function (path) {
  return Path.getFromObserver(this._rootBinding, path)
}

  假如說數(shù)據(jù)是下面格式:

var vm = new Vue({
    data: {
        a: {
            b: 1
        }   
    }
})

  當設(shè)置vm.a.b = 2時,我們調(diào)用_updateBindingAtpathab。_updateBindingAt函數(shù)首先會任何數(shù)據(jù)變化的時候都通知調(diào)用根級_rootBinding中的所有訂閱者,然后調(diào)用_getBindingAt函數(shù)去獲得當前路徑abbinding,如果存在,則通知調(diào)用所有的訂閱者(下面箭頭指向的就是對應(yīng)路徑abBinding)。關(guān)于Path模塊我們這里不做過多的介紹,我們只要知道Path.getFromObserver函數(shù)能遍歷Binding Tree找到對應(yīng)路徑的Binding

  接下來我們當響應(yīng)式數(shù)據(jù)觸發(fā)add事件時就會觸發(fā)_updateAdd函數(shù):
  

exports._updateAdd = function (path) {
  var index = path.lastIndexOf(Observer.pathDelimiter)
  if (index > -1) path = path.slice(0, index)
  this._updateBindingAt(path)
}

 
  假設(shè)是下列的數(shù)據(jù)格式:

var vm = new Vue({
    data: {
        a: {}   
    }
})

  當我們執(zhí)行vm.a.$add("b", 1)時,此時函數(shù)_updateAdd的參數(shù)pathab,但是對應(yīng)的binding還未創(chuàng)建,因此對應(yīng)的Watcher還沒有依賴到該Binding。對于這種不存在BindingWatcher,會暫時依賴于父級的Binding,因此函數(shù)_updateAdd也就是找到了對應(yīng)父級的Binding,然后通知調(diào)用所有的訂閱者。
  
  
  接下來觸發(fā)響應(yīng)式數(shù)據(jù)的get事件時,對應(yīng)調(diào)用函數(shù):
  

exports._collectDep = function (path) {
  var watcher = this._activeWatcher
  if (watcher) {
    watcher.addDep(path)
  }
}

  函數(shù)_collectDep的主要目的就是收集依賴,當get事件觸發(fā)的時候,會將_activeWatcher添加到路徑pathBinding中。關(guān)于_activeWatcheraddDep函數(shù),馬上我們會在Watcher模塊中介紹到。
  

Watcher模塊

  我們前面已經(jīng)講過,Binding中的訂閱者都是Watcher實例,Binding并不關(guān)心數(shù)據(jù)更改后的操作,對于Binding而言只需要調(diào)用訂閱者的update方法,具體的處理邏輯都隱藏在Watcher的背后。對于Watcher,其邏輯可能是一個指令directive(用于連接DOM與響應(yīng)式數(shù)據(jù))或者是一個watch偵聽器的回調(diào)函數(shù),這樣就能符合單一職責原則,解除模塊之間的耦合度,讓程序更易維護。

  在介紹Watcher之前,我們先介紹一下Batcher模塊,顧名思義,主要就是批處理任務(wù),看過React源碼的同學應(yīng)該也在其中看到過相似的概念。在這些框架中,有可能是因為某個操作過于昂貴(比如DOM操作),我們?nèi)绻麛?shù)據(jù)一改變就觸發(fā)相應(yīng)的操作其實是不合適的,比如:

//修改前vm.a === 1
vm.a = 2; 
vm.a = 1;

  其實兩次操作下來,我們的完全可以不需要進行操作,因為前后數(shù)據(jù)并沒有發(fā)生改變,這時如果我們進行批量處理,將兩次操作統(tǒng)一起來,就能在一定程度提升效率。
  

var _ = require("./util")

function Batcher () {
  this._preFlush = null
  this.reset()
}

var p = Batcher.prototype

p.push = function (job) {
  if (!job.id || !this.has[job.id]) {
    this.queue.push(job)
    this.has[job.id] = job
    if (!this.waiting) {
      this.waiting = true
      _.nextTick(this.flush, this)
    }
  } else if (job.override) {
    var oldJob = this.has[job.id]
    oldJob.cancelled = true
    this.queue.push(job)
    this.has[job.id] = job
  }
}

p.flush = function () {
  // before flush hook
  if (this._preFlush) {
    this._preFlush()
  }
  // do not cache length because more jobs might be pushed
  // as we run existing jobs
  for (var i = 0; i < this.queue.length; i++) {
    var job = this.queue[i]
    if (!job.cancelled) {
      job.run()
    }
  }
  this.reset()
}

p.reset = function () {
  this.has = {}
  this.queue = []
  this.waiting = false
}

module.exports = Batcher

  Batcher內(nèi)部有四個屬性并對外提供三個方法:

屬性:

has: 用來記錄某個任務(wù)(job)是否已經(jīng)在隊列中

queue: 用來存儲當前的任務(wù)隊列

waiting: 用來表示當前的任務(wù)隊列處于等待執(zhí)行狀態(tài)

_preFlush: 用來在執(zhí)行任務(wù)隊列前調(diào)用的鉤子函數(shù)

方法:

reset:重置參數(shù)屬性

push: 將任務(wù)放入批處理隊列

flush: 執(zhí)行批處理隊列中的所有任務(wù)

  上面的代碼邏輯非常簡單,不用逐一介紹,值得注意的是,每一個任務(wù)job都含有id屬性,用來唯一標識任務(wù),如果當前任務(wù)隊列中已經(jīng)存在并且任務(wù)的override屬性為false就不會重復放入。override屬性就是用來表示是否需要覆蓋已經(jīng)存在的任務(wù)。任務(wù)的cancelled屬性用來表示該任務(wù)是否需要被取消執(zhí)行。所有的任務(wù)job中的run方法就是任務(wù)所需要執(zhí)行的內(nèi)容。關(guān)于Vue.nextTick之后的文章我們會介紹,現(xiàn)在你可以就可以簡單理解成setTimeOut。

  接下來我們來看一下Watcher模塊的實現(xiàn):
  

var _ = require("./util")
var Observer = require("./observe/observer")
var expParser = require("./parse/expression")
var Batcher = require("./batcher")

var batcher = new Batcher()
var uid = 0

function Watcher (vm, expression, cb, ctx, filters, needSet) {
  this.vm = vm
  this.expression = expression
  this.cb = cb // change callback
  this.ctx = ctx || vm // change callback context
  this.id = ++uid // uid for batching
  this.value = undefined
  this.active = true
  this.deps = Object.create(null)
  this.newDeps = Object.create(null)
  var res = _.resolveFilters(vm, filters, this)
  this.readFilters = res && res.read
  this.writeFilters = res && res.write
  // parse expression for getter/setter
  res = expParser.parse(expression, needSet)
  this.getter = res.get
  this.setter = res.set
  this.initDeps(res.paths)
}

  Watcher模塊主要做的就是解析表達式,從中收集依賴并且在數(shù)據(jù)改變的時候調(diào)用注冊的回調(diào)函數(shù)。

vm: 就是對應(yīng)的響應(yīng)式數(shù)據(jù)所在的Vue實例

expression: 待解析的表達式

cb: 注冊的回調(diào)函數(shù),在數(shù)據(jù)改變時會調(diào)用

ctx: 回調(diào)函數(shù)執(zhí)行的上下文

id: Watcher的標識,用在Batcher對應(yīng)的job.id,每一個Watcher其實就是一個job

value: 表達式expression對應(yīng)的計算值

active: 該watcher是否是激活的

deps: 用來存儲當前Watcher依賴的數(shù)據(jù)路徑

  在整個Watcher構(gòu)造函數(shù)中我們需要注意的是兩個部分:
  

  var res = _.resolveFilters(vm, filters, this)
  this.readFilters = res && res.read
  this.writeFilters = res && res.write

  res = expParser.parse(expression, needSet)
  this.getter = res.get
  this.setter = res.set
  this.initDeps(res.paths)

  第一部分對應(yīng)的就是過濾器的處理,比如存在:

var vm = new Vue({
    data: {
        a: 1,
        b: -2
    },
    filters: {
        abs: function(v){
            return Math.abs(v);
        }
    }
})

  那么Watcher在解析表達式a+b|abs,得到對應(yīng)的結(jié)果值就是1。_.resolveFilters函數(shù)將filters解析成readFilterswriteFilters,其實本人也是從Vue2.0才開始入手的,之前并沒有聽過還存在什么writeFilter,于是翻看了Vue1.0的文檔,找了已經(jīng)被廢棄的Vue1.0的雙向過濾器。比如:

Vue.filter("currency", {
  read: function (value) {
    return "$" + value.toFixed(2)
  },
  write: function (value) {
    var number = +value.replace(/[^d.]/g, "")
    return isNaN(number) ? 0 : number
  }
})

var vm = new Vue({
  el: "#app",
  data: {
    price: 0
  }
})

  currency過濾器就是雙邊過濾器,當在輸入框中輸入例如: $12的時候,我們發(fā)現(xiàn)vm.price已經(jīng)被賦值為12。這就是write過濾器生效的結(jié)果。

  我們來看一下工具類utilsfilter模塊所提供的兩個方法resolveFiltersapplyFilters:

exports.resolveFilters = function (vm, filters, target) {
  if (!filters) {
    return
  }
  var res = target || {}
  var registry = vm.$options.filters
  filters.forEach(function (f) {
    var def = registry[f.name]
    var args = f.args
    var reader, writer
    if (!def) {
      _.warn("Failed to resolve filter: " + f.name)
    } else if (typeof def === "function") {
      reader = def
    } else {
      reader = def.read
      writer = def.write
    }
    if (reader) {
      if (!res.read) {
        res.read = []
      }
      res.read.push(function (value) {
        return args
          ? reader.apply(vm, [value].concat(args))
          : reader.call(vm, value)
      })
    }
    // only watchers needs write filters
    if (target && writer) {
      if (!res.write) {
        res.write = []
      }
      res.write.push(function (value) {
        return args
          ? writer.apply(vm, [value, res.value].concat(args))
          : writer.call(vm, value, res.value)
      })
    }
  })
  return res
}

  resolveFilters在被Watcher調(diào)用的時候,vm參數(shù)對應(yīng)的就是Vue的實例,而target則是Watcher實例本身,傳入的filters就比較特殊了,比如我們上面的例子:a+b|abs,對應(yīng)的filters就是

[{  
    name: "abs"
    args: null
}]

  我們看到filters是一個數(shù)組,其實每個元素的name對應(yīng)的就是應(yīng)用的過濾器函數(shù)名,而args則是傳入的預(yù)定的其他參數(shù)。代碼的邏輯非常的簡單,遍歷filters數(shù)組,將其中的每一個使用到的過濾器從vm.$options.filters取出,將對應(yīng)的readwrite包裝成新的函數(shù),并分別放入res.readres.write,并將res返回。然后配合下面的模塊提供的applyFilters函數(shù),我們就可以一個值經(jīng)過給定的一系列過濾器處理,得到最終的數(shù)值了。

exports.applyFilters = function (value, filters) {
  if (!filters) {
    return value
  }
  for (var i = 0, l = filters.length; i < l; i++) {
    value = filters[i](value)
  }
  return value
}

  第二部分代碼:

  res = expParser.parse(expression, needSet)
  this.getter = res.get
  this.setter = res.set
  this.initDeps(res.paths)

  涉及到的就是表達式的處理,我們之前講過,每個Watcher其實都是從一個表達式中收集依賴,并且在相應(yīng)的數(shù)據(jù)發(fā)生改變的時候調(diào)用對應(yīng)的回調(diào)函數(shù),expParser模塊不是我們本次文章的重點內(nèi)容,我們不需要知道它是怎么實現(xiàn)的,我們只要只要它是做什么的,可以看下面的代碼:
  

describe("Expression Parser", function () {
 it("parse getter", function () {
    var res = expParser.parse("a - b * 2 + 45");
    expect(res.get({
      a: 100,
      b: 23
    })).toEqual(100 - 23 * 2 + 45)
    expect(res.paths[0]).toEqual("a");
    expect(res.paths[b]).toEqual("b");
    expect(res.paths.length).toEqual(2);
  })
  
   it("parse setter", function () {
    var res = expParser.parse("a.b.d");
    var scope = {};
    scope.a = {b:{c:0}}
    res.set(scope, 123)
    expect(scope.a.b.c).toBe(123)
    expect(res.paths[0]).toEqual("a");
    expect(res.paths.length).toEqual(1);
  })
});

  其實從上面兩個測試用例中我們已經(jīng)能看出expParser.parse的功能了,expParser.parse能轉(zhuǎn)化一個表達式,返回值res中的paths表示表達式依賴數(shù)據(jù)的根路徑,get函數(shù)用于從一個值域scope中取得表達式對應(yīng)的計算值。而set函數(shù)用于給值域scope中設(shè)置表達式的值。

  我們接著看this.initDeps(res.paths)
 

var p = Watcher.prototype

p.initDeps = function (paths) {
  var i = paths.length
  while (i--) {
    this.addDep(paths[i])
  }
  this.value = this.get()
}

p.addDep = function (path) {
  var vm = this.vm
  var newDeps = this.newDeps
  var oldDeps = this.deps
  if (!newDeps[path]) {
    newDeps[path] = true
    if (!oldDeps[path]) {
      var binding =
        vm._getBindingAt(path) ||
        vm._createBindingAt(path)
      binding._addSub(this)
    }
  }
}

  initDeps函數(shù)的首先就是將表達式依賴根路徑中的每一個值調(diào)用函數(shù)addDep,將Watcher實例添加進入對應(yīng)的Binding中,addDep內(nèi)部實現(xiàn)也是非常的簡潔,調(diào)用_getBindingAt函數(shù)(已經(jīng)存在對應(yīng)的Binding)或者_createBindingAt(創(chuàng)建新的Binding)獲取到對應(yīng)Binding并將自身添加進入。newDeps用來記錄此次addDep過程中之前不存在的依賴項。之后initDeps函數(shù)調(diào)用了this.get()獲取當前表達式對應(yīng)的值。
  

p.get = function () {
  this.beforeGet()
  var value = this.getter.call(this.vm, this.vm.$scope)
  value = _.applyFilters(value, this.readFilters)
  this.afterGet()
  return value
}

p.beforeGet = function () {
  Observer.emitGet = true
  this.vm._activeWatcher = this
  this.newDeps = Object.create(null)
}

p.afterGet = function () {
  this.vm._activeWatcher = null
  Observer.emitGet = false
  _.extend(this.newDeps, this.deps)
  this.deps = this.newDeps
}

  get函數(shù)內(nèi)部實質(zhì)就是調(diào)用表達式對應(yīng)的get函數(shù)獲取表達式當前對應(yīng)的結(jié)果,然后通過applyFilters得到當前表達式對應(yīng)過濾器處理后的值。值得注意的是,在調(diào)用之前執(zhí)行了鉤子函數(shù)beforeGet,其目的就是開啟ObserveremitGet使得我們可以接受響應(yīng)式數(shù)據(jù)的get事件,然后將當前Vue實例的_activeWatcher賦值成當前的Watcher并置空newDeps準備存儲本次新增的依賴數(shù)據(jù)項。我們在Binding提到過:
  

this._observer.on("get", this._collectDep)
    
exports._collectDep = function (path) {
  var watcher = this._activeWatcher
  if (watcher) {
    watcher.addDep(path)
  }
}

  beforeGet所作的就是為了收集依賴所做的準備。afterGet所做的就是清除為依賴收集所做準備,邏輯和beforeGet正好相反。

  我們知道Watcher會在相應(yīng)的響應(yīng)式數(shù)據(jù)改變的時候被對應(yīng)Binding所調(diào)用,因此Watcher一定包含方法update:
  

p.update = function () {
  batcher.push(this)
}

p.run = function () {
  if (this.active) {
    var value = this.get()
    if (
      (typeof value === "object" && value !== null) ||
      value !== this.value
    ) {
      var oldValue = this.value
      this.value = value
      this.cb.call(this.ctx, value, oldValue)
    }
  }
}

  update并沒有理解調(diào)用對應(yīng)回調(diào)函數(shù),而且將Watcher放入Batcher隊列,Batcher會在恰當?shù)臅r間調(diào)用Watcherrun函數(shù)。run內(nèi)部會調(diào)用this.get()得到表達式當前的計算值,并且觸發(fā)回調(diào)函數(shù)。

  Watcher還有一個函數(shù)用于從所有的依賴的Binding中移除自身:

p.teardown = function () {
  if (this.active) {
    this.active = false
    var vm = this.vm
    for (var path in this.deps) {
      vm._getBindingAt(path)._removeSub(this)
    }
  }
}

  teardown內(nèi)部邏輯非常簡單,不再贅述。
  

總結(jié)

  講到這里大家可能被我粗糙的文筆搞的混亂了,我們舉個例子來看看,理順一下思路,假設(shè)存在下面的例子:

    new Vue({
        el: "#app",
        data: {
            a: { b: { c: 100 } },
            d: { e: { f: 200 } }
        }
    })
{{a.b.c + d.e.f}}

  對應(yīng)于上面的數(shù)據(jù),相應(yīng)的構(gòu)造好的Binding Tree如下的:
  
  

  我們在調(diào)用this.get去收集表達式a.b.c+d.c.e的對應(yīng)值時,我們會被Observer模塊的get事件觸發(fā)六次,分別對應(yīng)的值為:

a

a.b

a.b.c

d

d.e

d.e.f

  因此此時的Watcher中的dep存儲的就是對應(yīng)的依賴路徑:

  而此時的Watcher實例在Binding Tree的注冊情況如下:
  
 

  到此為止,我們已經(jīng)了解響應(yīng)式數(shù)據(jù)是如何與Watcher對應(yīng)的和響應(yīng)式數(shù)據(jù)改變時觸發(fā)相應(yīng)的操作。邏輯雖說不算特別難,但是還是有一定的復雜度的,建議可以對應(yīng)看看源碼,調(diào)試一下疑惑的地方,相信會有更多的收獲。

  如果文章有不正確的地方歡迎指正。最后還是希望大家能給我的Github博客點個Star。愿共同學習,一同進步。
  
  

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

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/108103.html

相關(guān)文章

  • 用proxy實現(xiàn)一個更優(yōu)雅的vue

    摘要:以上引用內(nèi)容來自阮一峰的教程的章節(jié)原文地址請戳這里。最后本文最終實現(xiàn)代碼已經(jīng)放在上,想要直接看效果的同學,可以上去直接,運行。 前言 如果你有讀過Vue的源碼,或者有了解過Vue的響應(yīng)原理,那么你一定知道Object.defineProperty(),那么你也應(yīng)該知道,Vue 2.x里,是通過 遞歸 + 遍歷 data對象來實現(xiàn)對數(shù)據(jù)的監(jiān)控的,你可能還會知道,我們使用的時候,直接通過數(shù)...

    objc94 評論0 收藏0
  • 少俠請留步,來一起實現(xiàn)個MVVM!

    摘要:一起來實現(xiàn)一個框架最近手癢,當然也是為了近階段的跳槽做準備,利用周五時光,仿照用法,實現(xiàn)一下的雙向綁定數(shù)據(jù)代理大胡子模板指令,等。 一起來實現(xiàn)一個mvvm框架 最近手癢,當然也是為了近階段的跳槽做準備,利用周五時光,仿照vue用法,實現(xiàn)一下mvvm的雙向綁定、數(shù)據(jù)代理、大胡子{{}}模板、指令v-on,v-bind等。當然由于時間緊迫,里面的編碼細節(jié)沒有做優(yōu)化,還請各位看官多多包涵!看...

    lily_wang 評論0 收藏0
  • MVVM 中的動態(tài)數(shù)據(jù)綁定

    摘要:要實現(xiàn)最小化刷新,我們要將模板中的每個綁定都收集起來。思考題在最后的實現(xiàn)下,我們把模板改為下面這樣雖然很少會有人這樣寫,就會出現(xiàn)重復的實例,該如何解決這個問題,參考早期源碼學習系列之四如何實現(xiàn)動態(tài)數(shù)據(jù)綁定 上一篇文章我們了解了怎樣實現(xiàn)一個簡單模板引擎。但這個模板引擎只適合靜態(tài)模板,因為它是將模板整體編譯成字符串進行全量替換。如果每次數(shù)據(jù)改變都進行一次替換,會有兩個最主要的問題: 性能...

    Meils 評論0 收藏0
  • 前端項目發(fā)布后的問題總結(jié)

    摘要:引言最近做的項目已經(jīng)接近尾聲剛剛發(fā)到線上回顧和總結(jié)一下這段時間遇到的問題和個人的一些想法。通過在指令中比較前后值以及設(shè)置避免不必要更新導致的彈窗渲染。 引言 最近做的項目已經(jīng)接近尾聲,剛剛發(fā)到線上,回顧和總結(jié)一下這段時間遇到的問題和個人的一些想法。 select下拉修改和復原 //部分下拉選項 {{o...

    endless_road 評論0 收藏0
  • Vue 雙向數(shù)據(jù)綁定原理分析

    摘要:關(guān)于雙向數(shù)據(jù)綁定當我們在前端開發(fā)中采用的模式時,,指的是模型,也就是數(shù)據(jù),,指的是視圖,也就是頁面展現(xiàn)的部分。參考沉思錄一數(shù)據(jù)綁定雙向數(shù)據(jù)綁定實現(xiàn)數(shù)據(jù)與視圖的綁定與同步,最終體現(xiàn)在對數(shù)據(jù)的讀寫處理過程中,也就是定義的數(shù)據(jù)函數(shù)中。 關(guān)于雙向數(shù)據(jù)綁定 當我們在前端開發(fā)中采用MV*的模式時,M - model,指的是模型,也就是數(shù)據(jù),V - view,指的是視圖,也就是頁面展現(xiàn)的部分。通常,...

    nanfeiyan 評論0 收藏0

發(fā)表評論

0條評論

tyheist

|高級講師

TA的文章

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