Vue源碼學習(三)——數據雙向綁定

在Vue中咱們常常修改數據,而後視圖就直接修改了,那麼這些到底是怎麼實現的呢?
其實Vue使用了E5的語法Object.defineProperty來實現的數據驅動。
那麼Object.defineProperty到底是怎麼實現的呢?
咱們先來看一下一個簡單的demohtml

<template>
<div class="hello">
  {{test}}
</div>
</template>
<script>
export default {
  data () {
      test: '123'
  },
  created () {
      console.log(this.test === this._data.test) // true
  }
}
</script>

在vue這段小代碼中,this.test === this._data.test實際上是等價的。這是爲何呢
其實就是經過Object.defineProperty作的一個小代理實現的。
原理以下:vue

var obj = {
    _data: {
        x: 123,
        y: 467
    }
}
function proxy (target, sourceKey, key) {
    Object.defineProperty(target, key, {
        enumerable: true,
        configurable: true,
        get: function() {
            return this[sourceKey][key]
        },
        set: function(val) {
            this[sourceKey][key] = val
        }
    })
}
proxy(obj, '_data', 'x')
proxy(obj, '_data', 'y')

console.log(obj.x)  // 123
console.log(obj.y)  //  467
console.log(obj._data.x) // 123
console.log(obj._data.y) // 467

以上一個demo就是對obj對象的一個代理,經過訪問obj.x直接代理到obj._data.x。 Object.defineProperty具體用法能夠自行搜索。
那麼其實數據雙向綁定也是根據Object.defineProperty裏面的get和set來實現的,經過set的時候去作一些視圖更新的操做。react

接下來咱們就來看一下Vue源碼吧。
雙向數據綁定,將分爲如下3個部分:jquery

1. Observer。這個模塊是用於監聽對象上的全部屬性,即便用Object.defineProperty來實現get和set
2. Watcher。 這個模塊是觀察者,當監聽的數據值被修改時,執行對象的回調函數。
3. Dep。 鏈接Observe和Watcher的橋樑,每一個Observer對應一個Dep,內部維護一個數組,保存與該Observer相關的Watcher

Observer

首先來看下oberser。
路徑: src/core/observer/index.jsexpress

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

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    // 添加__ob__來標示value有對應的Observer
    def(value, '__ob__', this)
    // 對數組的處理
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value)
    } else {
    // 處理對象
      this.walk(value)
    }
  }

 // 給每一個屬性添加getter/setters
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  // 觀察數組的每一項
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

因此從源碼能夠看出數據類型主要分爲2種,一種是數組,一種是對象。對應的處理方法分別樹observe和defineReactive
那咱們再來看看這2個函數作了些什麼呢?
這2個函數也是在這個文件內部數組

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()
   
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  let childOb = !shallow && observe(val)
    // 這邊做出get和set的動態響應的處理
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    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
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

// 這個方法具體是爲對象的key添加get,set方法;若是用戶傳入本身自己傳入get和set方法也會保留其方法。它會爲每個值都建立一個Dep,在get函數中 dep.depend作了2件事,一是向Dep.target的內部添加dep,二是將dep。target添加到dep內部的subs數組中,也就是創建關係。
在set函數中,若是新值和舊值相同則不處理,若是不一樣,則通知更新。
接下來看observeapp

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對象。
可是這樣牢牢對數組中的每一項的對象進行了觀察,若是數組自己的長度修改那麼又如何觸發呢。Vue專門對數組作了特出的處理。
回過頭來看Observer的類中有這麼一段,在處理數組的時候,處理了這些代碼dom

const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value)

咱們來細看這些函數,見以下函數

function protoAugment (target, src: Object, keys: any) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}

function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})

咱們能夠看到protoAugment很簡單,就是執行了一段value._proto_ = arrayMethods
copyAugment中循環把arrayMethods上的arrayKeys方法添加到value上。
arrayMethods又是重寫了數組的操做方法['push','pop','shift','unshift','splice','sort','reverse']。
經過調用數組這些方法的時候,通知dep.notify。 至此Observer部分已經結束oop

Dep

Dep相對就簡單點。
源碼路徑:src/core/observer/dep.js

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

  constructor () {
    this.id = uid++
    this.subs = []
  }
   // 添加觀察者
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }
    // 刪除觀察者
  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }
    // 調用watcher
  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()
    }
  }
}

內部有個惟一的id標識,還有一個保存watcher的數組subs。

Watcher

let uid = 0

export default class Watcher {

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: Object
  ) {
    this.vm = vm
    vm._watchers.push(this)
    ...
    this.cb = cb
    this.id = ++uid
    ...
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = function () {}
      }
    }
    this.value = this.get()
  }

  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    ...
    value = this.getter.call(vm, vm)
    ...
    popTarget()
    this.cleanupDeps()
    return value
  }

   ...

  update () {
    ...
    queueWatcher(this)
  }

  run () {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }
  ...
}

Watcher就是用於把變化放入觀察,並通知其變化更新。
queueWatcher就是把變化者放入數組queue,而後經過nextTick去更換新數組queue中的變化。
在生命週期掛載元素時,就會經過建立Watcher,而後來更新創新模塊。

vm._watcher = new Watcher(vm, updateComponent, noop)

這邊數據雙向綁定差很少就結束了。最後再附上一張簡要的流程圖來進一步清晰本身的思路。

clipboard.png

下一章節經過數據綁定原理結合jquery來實現數據驅動更新的demo。之因此採用jquery操做dom是由於如今Vue源碼還沒到解析html模板那一步。
因此一步步來。等以後學完模板解析後。再去製做一個MVVM的簡易demo。

若是對您有幫助,請點個贊,謝謝!

相關文章
相關標籤/搜索