vue中的數據綁定道理

vue的相應式數據綁定

原文地點:
https://github.com/HolyZheng/…

癥結詞:Object.defineProperty、觀察者形式

《vue中的數據綁定道理》

vue中的相應式數據綁定是經由過程數據挾制和觀察者形式來完成的。當前進修源碼為vue2.0
源碼癥結目次

src
|---core
|    |---instance
|          |---init.js
|          |---state.js
|    |---observer
|          |---dep.js
|          |---watcher.js

當我們實例化一個vue運用的時刻,會伴隨着種種的初始化事情,相干的初始化事情代碼在init.js文件中

// src/core/instance/init.js

Vue.prototype._init = function (options?: Object) {
  ...
  initLifecycle(vm)
  initEvents(vm)
  callHook(vm, 'beforeCreate')
  initState(vm)
  callHook(vm, 'created')
  initRender(vm)
}

在這裡能夠看到對state的初始化事情initState()

// src/core/instance/state.js

export function initState (vm: Component) {
  vm._watchers = []
  initProps(vm)
  initData(vm)
  initComputed(vm)
  initMethods(vm)
  initWatch(vm)
}

能夠看到這裡有對種種sate的初始化事情,我們看initData()

// src/core/instance/state.js

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? data.call(vm)
    : data || {}
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object.',
      vm
    )
  }
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  let i = keys.length
  while (i--) {
    if (props && hasOwn(props, keys[i])) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${keys[i]}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else {
      proxy(vm, keys[i])
    }
  }
  // observe data
  observe(data)
  data.__ob__ && data.__ob__.vmCount++
}

這裏做了一點推斷,推斷data要領是不是返回的是一個對象,以及props中是不是有與data中重名的屬性,末了會挪用observe對data舉行監聽,看一下observe

// src/core/observer/index.js

export function observe (value: any): Observer | void {
  if (!isObject(value)) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    observerState.shouldConvert &&
    !config._isServer &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  return ob
}

可已看到這裏也是做了一點推斷,假如有__ob__屬性的話就用它,或許假如data是數組或對象或可擴大對象的話,就為它新建一個Observer,看一下Observer

// src/core/observer/index.js

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
    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 through each property and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]])
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

推斷data是不是是數組,假如是數組就對數組元素再去挪用observe要領做一樣的處置懲罰,假如不是,就挪用walk去挾制該數據,對數據的挾制重要再defineReactive要領中,正如函數名,讓數據變得相應式。看一下defineReactive要領

// src/core/observer/index.js

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: Function
) {
  const dep = new Dep()
// data中的每一個成員都有一個對應的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

  let childOb = observe(val)
  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)) {
          for (let e, i = 0, l = value.length; i < l; i++) {
            e = value[i]
            e && e.__ob__ && e.__ob__.dep.depend()
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      if (newVal === value) {
        return
      }
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = observe(newVal)
      dep.notify() // 宣布關照
    }
  })
}

遍歷狀況,修正狀況的getter和setter,當頁面上對應狀況被初次襯着的時刻,會為頁面上每一個運用到data的處所新建一個watcher,並將當前watcher保存到全局變量Dep.target中,在對應data的getter中就會挪用Dep.depend要領,將當前的watcher增加到當前的Dep中,一個Dep對應一個或多個watcher,着取決於,此狀況被運用的數目。當data被修正時,對應的setter就會被觸發,會挪用對應的Dep中的notify要領,關照一切觀察者,舉行更新。

這裏湧現了兩個定的類:Dep和Watcher,个中Dep治理觀察者,Wathcer代表觀察者

先看一下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)
  }

  depend () {
    if (Dep.target) {
// 挪用當前target,也就是正在處置懲罰的watcher的addDep要領,並把此Dep傳進去
      Dep.target.addDep(this)
    }
  }

  notify () {
    // stablize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

看一下watcher.js

// src/core/observer/watcher.js

export default class Watcher {
...
  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)) {
       // 將當前watcher增加到當前的Dep中
        dep.addSub(this)
      }
    }
  }
...
}

總結

vue的相應式數據綁定重要依靠Object.defineProperty和觀察者形式。

  1. 在我們新建一個vue實例的時刻,做一系列的初始化事情,這部份的邏輯集合在src文件夾下的core文件夾下的instanceobserver文件夾內
  2. 相應式數據綁定是在狀況的初始化階段完成的,在initState要領中的initData中舉行data的數據綁定。
  3. 在initData中挪用observe要領,為該data新建一個Observer類,然後終究挪用為data中的每一個成員挪用walk要領,在walk中經由過程defineReactive要領挾制當前數據
  4. 在defineReactive中經由過程Object.defineProperty去修正數據的getter和setter
  5. 在頁面襯着的時刻,頁面上每一個用到data的處所都邑天生一個watcher,並將它保存到全局變量Dep.target中,watcher轉變每一個觀察者,Dep用來治理觀察者。
  6. 然後在data的getter中將挪用Dep的depend要領,將Dep.target中的watcher增加到此data對應的Dep中,完成依靠網絡
  7. 在data被修正的時刻,對應data的setter要領就會被出動,會挪用Dep.notify()要領宣布關照,挪用每一個watcher的uptade要領舉行更新。
    原文作者:holyZhengs
    原文地址: https://segmentfault.com/a/1190000015437701
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞