閱讀Vue源碼(一)響應式原理

vue的響應式原理,是發佈-訂閱模式的應用。學習vue響應式原理前,先來了解下,到底什麼是發佈-訂閱模式。vue

從訂鞋子理解發布-訂閱模式

有一天去商城買鞋子,結果沒貨了。店員拿出一個本對你說,能夠先登記電話,到貨再通知你。react

這個例子中,店員是消息的發佈者,店員有個小本本,只要貨到了,就會通知小本本上的客戶。客戶是消息的訂閱者。能夠在將來某個時候接收信息發佈者(售貨員)的消息,而不用一直輪訓消息的變化。用代碼實現這個例子。git

const sales = {
    //售貨員的小本本
	noteBook :[],
	//在本上登記電話
	add(customer){
	     let isExist = this.noteBook.find((item) =>{
		    return item.phone == customer.phone
		 })
		 if(!isExist){
		 this.noteBook.push(customer)
		 }

	},
	//刪除小本本上的記錄
	delete(customer){
		  let getIndex = this.noteBook.findIndex((v) =>{
		    return v.phone == customer.phone
		  })
		  if(getIndex!==-1){
		   this.noteBook.splice(getIndex,1)
		  }
	},
     //通知顧客
	notify(){
	 this.noteBook.forEach((item) =>{
	   item.upDate();
	 })
	}
 }

 const customer1 = {
   phone:'12345678',
   upDate(){
     console.log(`customer1電話號碼${this.phone}`)
   },
  }

const customer2 = {
   phone:'87654321',
   upDate(){
     console.log(`customer2電話號碼${this.phone}`)
   },
  }

   //記錄客戶1信息
sales.add(customer1)
   //記錄客戶2信息
sales.add(customer2)
//到貨了,通知客戶
sales.notify()
複製代碼

vue源碼中的發佈-訂閱模式

1.響應式對象

1.1什麼是響應式對象

Object.defineProperty 方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性, 並返回這個對象。 get 是一個給屬性提供的 getter 方法,當咱們訪問了該屬性的時候會觸發 getter 方法;set 是一個給屬性提供的 setter 方法,當咱們對該屬性作修改的時候會觸發 setter 方法。github

一旦對象擁有了 getter 和 setter,咱們能夠簡單地把這個對象稱爲響應式對象編程

vue在何時,把哪些對象建立爲響應對象呢。設計模式

1.2響應式對象的建立過程

initState 在 Vue 的初始化階段,_init 方法執行的時候,會執行 initState(vm) 方法(src/core/instance/state.js)數組

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 */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

複製代碼

initDatabash

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    .......
  // observe data
  observe(data, true /* asRootData */)
}
複製代碼

observe閉包

observe 的功能就是用來監測數據的變化。若是沒有_ob_屬性,而且不是Observer的實例,就爲數據添加一個Observer類。observe定義在(src/core/observer/index.js)編程語言

export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}
複製代碼

Observer類
Observer類 主要作了三件事。1.爲當前值添加_ob_屬性。2.當前值爲數組調用observeArray方法,循環調用observe方法,給裏面的每一個值變爲響應對象。3.當前值是對象,調用walk方法

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data

  constructor (value: any) {
   ...
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }
複製代碼

walk函數裏,調用defineReactive方法,給響應式對象動態添加 getter 和 setter

walk (obj: Object) {
   const keys = Object.keys(obj)
   for (let i = 0; i < keys.length; i++) {
     defineReactive(obj, keys[i])
   }
 }
複製代碼

defineReactive.
對數據進行響應式化,爲數據添加getter/setter,爲每一個數據添加一個訂閱者列表的過程,。這個列表是getter閉包中的屬性,會記錄依賴這個數據的組件。 響應式化後的數據至關於消息的發佈者。

new Dep()建立一個數組,裏面保存全部對這個數據依賴的訂閱者

export function defineReactive (
....
) {
  const dep = new Dep()
 ....
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true
    //依賴收集
    get: function reactiveGetter () {
    ....
    },
    // 派發更新
    set: function reactiveSetter (newVal) {
     .....
    }
  })
}
複製代碼

2.依賴收集

2.1 什麼是依賴收集

每一個組件都對應一個Watcher訂閱者。當每一個組件的渲染函數被執行時,都會將本組件的Watcher放到本身所依賴的響應式數據的訂閱者列表裏,這就至關於完成了訂閱。

2.2 源碼中依賴收集的過程

2.2.1 getter是何時觸發的

當對數據對象的訪問會觸發他們的 getter 方法,那麼這些對象何時被訪問呢?

在Vue 的 mount 過程是經過 mountComponent 函數。(core/instance/lifecycle.js)。

updateComponent = () => {
  vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
  before () {
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate')
    }
  }
}, true /* isRenderWatcher */)
複製代碼

當實例化一個渲染Watcher時,.首先進入 watcher 的構造函數邏輯,而後會執行它的 this.get() 方法(src/core/observer/watcher.js).而後調用pushTarget

pushTarget(this)
複製代碼

pushTarget(src/core/observer/dep.js )把當的渲染watcher賦值給 Dep.target

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

在warcher構造函數裏,繼續執行this.getter,對應就是 updateComponent 函數,這實際上就是在執行:

vm._update(vm._render(), hydrating)
複製代碼

vm._render(),執行這個方法會對vm上的數據訪問,這時就觸發了數據對象的getter.

src/core/observer/index.js

get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
複製代碼

觸發getter,調用dep.depend,也就是調用 Dep.target.addDep,這時Dep.target已經被賦值爲當前的渲染watcher。

src/core/observer/watcher.js

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)
      }
    }
  }
複製代碼

執行dep.addSub,那麼就會執行 this.subs.push(sub),也就是說把當前的 watcher 訂閱到這個數據持有的 dep 的 subs 中,這個目的是爲後續數據變化時候能通知到哪些 subs 作準備。

3.派發更新

當響應式數據發生變化的時候,也就是觸發了setter時,setter會負責通知該數據的訂閱者列表裏的Watcher,Watcher會觸發組件從新渲染來更新視圖

參考:
JavaScript 設計模式精講

Vue.js技術揭祕

相關文章
相關標籤/搜索