Vue響應式----陣列變異方法

王興欣發表於2017-11-10

前言

很多初使用Vue的同學會發現,在改變陣列的值的時候,值確實是改變了,但是檢視卻無動於衷,果然是因為陣列太高冷了嗎?
檢視官方文件才發現,不是女神太高冷,而是你沒用對方法。

看來想讓女神自己動,關鍵得用對方法。雖然在官方文件中已經給出了方法,但是在下實在好奇的緊,想要解鎖更多姿勢的話,那就必須先要深入女神的心,於是乎才有了去探索Vue響應式原理的想法。(如果你願意一層一層地剝開我的心。你會發現,你會訝異…… 沉迷於鬼哭狼嚎 無法自拔QAQ)。

前排提示,Vue的響應式原理主要是使用了ES5的Object.defineProperty,毫不知情的同學可以檢視相關資料

為啥陣列不響應?

仔細一想,Vue的響應是基於Object.definePropery的,這個方法主要是對物件屬性的描述進行修改。陣列其實也是物件,通過定義陣列的屬性應該也能產生響應的效果呀。先驗證一下自己的想法,擼起袖子就開幹。

const arr = [1,2,3];

let val = arr[0];

Object.defineProperty(arr,'0',{
    enumerable: true,
    configurable: true,
    get(){
        doSomething();
        return val;
    },
    set(a){
        val = a;
        doSomething();
    }
});

function doSomething() {

}複製程式碼

然後在控制檯中分別輸入arrarr[0] = 2arr,可以看到如下圖的結果。


咦,一切居然都如預想猜想的一樣。
接下來,看到這段程式碼,有的同學可能會有所疑問,為啥在get()方法裡不直接返回this[0]呢?而是要藉助val來返回值呢?
仔細一想,臥槽!!!差點特麼的死迴圈了,你想呀,get()本身就是獲取當前屬性的值,在get()裡呼叫this[0]不是等同於再次呼叫了get()方法嗎? 好可怕好可怕,簡直嚇死勞資了。

雖然你想象中的女神可能會這種姿勢,但是你眼前的這個女神確實不是這種姿勢的,像我這種屌絲屬性暴露無疑的人怎麼可能猜透女神的心思?為什麼不這樣響應資料呢?或許是因為陣列和物件還是有所差別,定義陣列的屬性可能會產生一些麻煩與Bug。又或許是因為在互動的過程中可能會產生大量的資料,導致整體的效能下降。也有可能是作者權衡利弊之後用其他方法也可以達到資料響應的效果。反正我是猜不透啦。

為啥呼叫陣列原生方法就可以響應了?

為什麼使用了這些陣列的方法就就能讓資料響應了呢?
先看看陣列部分的原始碼吧。

簡單的來講,def的作用就是重新定義物件屬性的value值。

//array.js
import { def } from '../util/index'

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
//arrayMethods是對陣列的原型物件的拷貝,
//在之後會將該物件裡的特定方法進行變異後替換正常的陣列原型物件
/**
 * Intercept mutating methods and emit events
 */
[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
.forEach(function (method) {
  // cache original method
  //將上面的方法儲存到original中
  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
  })
})複製程式碼

貼出def部分的程式碼

/**
 * Define a property.
 */
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}複製程式碼

array.js是對陣列的一些方法進行變異,我們以push方法來舉個例子。
首先 就是要用original = arrayProto['push']來儲存原生的push方法。

然後就是要定義變異的方法了,對於def函式,如果不深究的話,def(arrayMethods,method,function(){}),這個函式可以粗略的表示為arrayMethods[method] = function mutator(){};
假設在之後呼叫push方法,實際上呼叫的是mutator方法,在mutator方法中,第一件事就是呼叫儲存了原生push方法的original,先求出實際的值。
一堆文字看起來實在很抽象,那麼寫一段低配版的程式碼來表達原始碼的含義。

const push = Array.prototype.push;

Array.prototype.push = function mutator (...arg){
    const result = push.apply(this,arg);
    doSomething();
    return result
}

function doSomething(){
    console.log('do something');
}

const arr = [];
arr.push(1);
arr.push(2);
arr.push(3);複製程式碼

在控制檯中檢視結果為:

那麼原始碼中的

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()複製程式碼

這段程式碼就是對應的doSomething()

在該程式碼中,清清楚楚的寫了2個單詞的註釋notify change,不認識這2個單詞的同學就百度一下嘛,這裡就由我代勞了,這倆單詞的意思是釋出改變
每次呼叫了該方法,都會求出值,然後做一些其他的事情,比如釋出改變與觀察新增的元素,響應的其他過程在本篇就不討論了。

[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]複製程式碼

目前一共有這麼些方法,只要用對方法就能改變女神的姿勢喲!

小結

對於標題,我一改再改,一開始叫淺析Vue響應原理,但是後來一看 這個標題實在太大,那就從最簡單的入手吧,先從陣列入手,而且本篇也不會花費太多時間去閱讀。如果本篇有什麼地方寫得有誤,誤導了他人,請一定指出,萬分感激。
最後在光棍節前祝大家光棍節快樂。
我? 我特麼當然不是單身狗啦!

哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈
嗚嗚嗚嗚嗚嗚嗚嗚嗚嗚嗚嗚嗚嗚嗚嗚嗚嗚

相關文章