Vue源碼閱讀(三):數組的響應式處理

數組的響應式處理有何特殊之處

回答這個問題,實際上回答的是:Vue 的數據響應式原理有什麼限制?理解了 Vue 的數據響應式原理的限制,就能夠很好回答這個問題。數組

在實際使用 Vue 的過程當中,對於響應式 Data 的聲明,必須將須要響應的各個屬性都逐一在聲明階段寫清楚。若是對於一個響應式的 Object,動態增長了某一個屬性,那麼 Vue 是沒法爲該新增屬性作響應式處理的。舉個栗子:bash

export default {
    data(){
        return {
            foo : 1,
            bar : {tau : "test"}
        }
}

//給data動態增長屬性newFoo, 頁面沒法響應式
this.data.newFoo = "123"
//給bar動態增長屬性tau2, 頁面沒法響應
this.data.bar.tau2 = "tell me"

複製代碼

理解了這個限制,咱們再來看看數組的狀況。咱們會常常對數組進行的操做是怎樣的呢?app

var array1 = ['t1','t2','t3']

//動態修改元素
array1[0] = 'tt1'

//動態增長元素
array1[4] = 't5'

//push方法增長數組元素
array1.push("1234")

//動態改變元素的長度
array1.length = 100
複製代碼

這些常規操做,若是隻是經過常規的攔截 key、value 的方式來進行數據響應式,那麼明顯沒法徹底覆蓋。而且成本過高,設想一下:若是用戶直接寫一個array1[1000] = 'haha',Vue 是否要爲其 1000 個子元素(極有多是都爲 null )都響應化處理呢?post

另外,值得注意的是,之因此對象能夠在 data 聲明的時候寫死,是由於頁面操做中的對象屬性基本上能夠在編寫的時候肯定。但頁面中數組的變化就不同,其變化基本上沒法預測。而 JS 語言中的數組對其子元素的屬性不限制,因而更加凌亂。ui

那麼,Vue 是採用了什麼方式來進行數組的數據響應化實現呢?this

  1. 對於數組子元素,肯定了能夠觸發數組子元素數據響應化的七個方法,其餘數組操做沒法觸發數組子元素的數據響應化。(具體方法,見源碼解析)
  2. 針對數組子元素內部,循環賦予新的Observer,進行新一輪的判斷。

咱們來接着上篇文章的內容,看源碼。spa

源碼解析

查看/src/observer/array.js,能夠知道Vue對數組的七個方法進行了攔截處理:prototype

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)
    //額外通知變動,固然,只有這7個方法纔會有這個待遇
    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
  })
})
複製代碼

咱們來看,在定義 Observer 的時候,如何處理數組:code

import { arrayMethods } from './array'
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

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

  //值得留意的是:Observer對象在一個Vue實例中是存在多個的,取決於data數據中嵌套了幾個Object對象或數組對象
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    //若是是數組
    if (Array.isArray(value)) {
        //若是可以使用原型特性,直接將變異方法賦予響應化數組的原型鏈上
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        //若是沒法使用原型,那麼經過defineProperty的方式將變異方法賦予響應化數組
        copyAugment(value, arrayMethods, arrayKeys)
      }
      //接着,對數組子元素,進行新一輪的observe數據響應化的過程
      this.observeArray(value)
    } else {
      //若是是對象
      this.walk(value)
    }
    
    observeArray (items: Array<any>) {
        for (let i = 0, l = items.length; i < l; i++) {
          observe(items[i])
        }
    }
  }
  

function protoAugment (target, src: Object) {
  target.__proto__ = src
}

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

複製代碼

這個過程並不難看懂。至此,咱們能夠回答這個問題:server

data: {
    obj: {foo: 'foo'}
    bar: ['tua', 'tea']
}

//下面的這個操做,是否會觸發數據響應化過程呢?
this.bar[0] = 'testNew';
複製代碼
相關文章
相關標籤/搜索