vue2.x響應(yīng)式原理-數(shù)組篇

來(lái)不及解釋了,快上車(chē)......

之前的一篇文章 vue2.x響應(yīng)式原理主要是對(duì)象的響應(yīng)式盏求,今天補(bǔ)充一下數(shù)組響應(yīng)式的原理筛峭,因?yàn)関ue對(duì)數(shù)組做了特別的處理箭启。

vue 為什么沒(méi)像處理對(duì)象一樣用 Object.defineProperty 處理數(shù)組蜜氨?是 Object.defineProperty 無(wú)法監(jiān)測(cè)數(shù)組嗎炎滞?又或者是出于其它方面的什么考慮呢蚜迅?那它是怎么實(shí)現(xiàn)對(duì)數(shù)組的監(jiān)聽(tīng)的舵匾?帶著這些問(wèn)題我們?nèi)ヒ惶骄烤?..

Object.defineProperty 支持?jǐn)?shù)組嗎

首先我們來(lái)做一個(gè)測(cè)試,看 Object.defineProperty 是否支持?jǐn)?shù)組谁不。

function defineReactive(obj, key, val) {
    Object.defineProperty(obj, key, {
        enumerable: true, // 屬性可枚舉
        configurable: true,
        get() {
            console.log('??-----讀取', val)
            return val;
        },
        set(newVal) {
            if (val === newVal) return
            val = newVal
            console.log('??-----改變---', val, obj)
        }
    })
}

還是這個(gè) defineReactive 函數(shù)坐梯,我們通過(guò)它來(lái)遍歷數(shù)組,用數(shù)組的索引作為 key刹帕,來(lái)給每一項(xiàng)打上getter/setter吵血。

let array = [1,2,3,4,5]

array.forEach((c,index) => {
    defineReactive(array, index, c)
})

log1.png

我們?cè)诳刂婆_(tái)打印一下,可以看到打印的結(jié)果偷溺,這說(shuō)明數(shù)組項(xiàng)被打上了 getter/setter蹋辅,還回到這個(gè)問(wèn)題,Object.defineProperty 可以做到對(duì)數(shù)組的監(jiān)聽(tīng)挫掏,它是支持?jǐn)?shù)組的侦另。

vue為什么沒(méi)有提供對(duì)數(shù)組屬性的監(jiān)聽(tīng)呢

第二個(gè)為題就來(lái)了,既然 Object.defineProperty 有這個(gè)能力砍濒,那么 vue 為什么沒(méi)用它來(lái)實(shí)現(xiàn)對(duì)數(shù)組屬性的監(jiān)聽(tīng)呢

有人提到 length 屬性淋肾,length 屬性的改變可能會(huì)導(dǎo)致一些空元素,的確爸邢,Object.defineProperty 不能處理這些為空的數(shù)組樊卓。但是我們換個(gè)角度想一下,通過(guò)改變 length 屬性去增加數(shù)組長(zhǎng)度杠河,不就是相當(dāng)于增加屬性嗎碌尔,這個(gè)在對(duì)象里面 vue 也是無(wú)法監(jiān)測(cè)到的浇辜。

在上圖第二個(gè) log 里面,當(dāng)給數(shù)組某一項(xiàng)賦值的時(shí)候唾戚,觸發(fā)了 setter柳洋,setter 的時(shí)候,打印 obj 數(shù)組又依次讀取了數(shù)組的值叹坦,這會(huì)影響性能熊镣。另外很多時(shí)候數(shù)組長(zhǎng)度我們并不確定,無(wú)法提前打上 getter/setter募书,而且如果數(shù)組長(zhǎng)度很大也會(huì)造成性能問(wèn)題绪囱,用尤大的原話說(shuō)就是性能代價(jià)和獲得的用戶(hù)體驗(yàn)收益不成正比。

貼一個(gè)尤大在github上的回答莹捡。

you.png

另外賀師俊老濕的回答也非常精辟??
:如果你知道數(shù)組的長(zhǎng)度鬼吵,理論上是可以預(yù)先給所有的索引設(shè)置 getter/setter 的。但是一來(lái)很多場(chǎng)景下你不知道數(shù)組的長(zhǎng)度篮赢,二來(lái)齿椅,如果是很大的數(shù)組,預(yù)先加 getter/setter 性能負(fù)擔(dān)較大启泣。

總而言之就是理論上 vue 是可以這樣做涣脚,但是出于性能考慮沒(méi)這樣做,而是用了一種數(shù)組變異辦法來(lái)觸發(fā)視圖更新种远。

vue如何實(shí)現(xiàn)對(duì)數(shù)組的監(jiān)聽(tīng)

解決了上面的兩個(gè)問(wèn)題涩澡,我們接下來(lái)就來(lái)看看 vue 的數(shù)組變異具體是怎么實(shí)現(xiàn)的。

const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

if (Array.isArray(value)) {
  const augment = hasProto
    ? protoAugment
    : copyAugment
  augment(value, arrayMethods, arrayKeys)
  this.observeArray(value)
} else {
  this.walk(value)
}

observeArray 會(huì)把數(shù)組里面的對(duì)象數(shù)據(jù)變成是可偵測(cè)的響應(yīng)式數(shù)據(jù)坠敷,observeArray 和 walk 函數(shù)主要還是我們上篇文章vue2.x響應(yīng)式原理的內(nèi)容。vue在這里對(duì)數(shù)組進(jìn)行了特別處理射富,就是依靠著 protoAugment膝迎、copyAugment 這兩個(gè)函數(shù)。

export const hasProto = '__proto__' in {}

首先通過(guò) hasProto 判斷瀏覽器是否支持 proto 屬性胰耗,來(lái)決定是執(zhí)行 protoAugment 還是 copyAugment限次。再看這兩個(gè)函數(shù)之前,我們先來(lái)看一下數(shù)組變異的核心文件 array.js,順便也能知道 arrayMethods, arrayKeys 這兩個(gè)參數(shù)是什么柴灯。

// array.js

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

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

/**
 * Intercept mutating methods and emit events
 */
;[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    // 調(diào)用數(shù)組真正的方法
    const result = original.apply(this, args)
    // __ob__代表數(shù)據(jù)是否被observe了
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    // inserted表示有數(shù)據(jù)插入 對(duì)新數(shù)據(jù)進(jìn)行observe
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})
/**
 * 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
  })
}

vue 通過(guò) Object.create讓arrayMethods 繼承了數(shù)組的原型卖漫,Object.create 本質(zhì)是原型式繼承【手撕JS繼承】,此時(shí)數(shù)組的原型掛到了arrayMethods 的原型鏈上赠群。

def函數(shù)主要來(lái)定義屬性羊始。遍歷這七種方法,通過(guò) def 函數(shù)查描,我們重寫(xiě)了 arrayMethods 原型鏈上的這七種方法突委,并在內(nèi)部調(diào)用了數(shù)組的原始方法柏卤,最后通過(guò) notify 更新視圖。inserted 代表新數(shù)據(jù)插入匀油,需要對(duì)新數(shù)據(jù)進(jìn)行 obsserve缘缚。

下面我們?cè)倩仡^來(lái)看 protoAugment 和 copyAugment 這兩個(gè)函數(shù)。

/**
 * Augment an target Object or Array by intercepting
 * the prototype chain using __proto__
 */
function protoAugment (target, src: Object, keys: any) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}

/**
 * Augment an target Object or Array by defining
 * hidden properties.
 */
/* istanbul ignore next */
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])
  }
}

protoAugment 讓數(shù)組的原型鏈指向了 arrayMethods敌蚜。
當(dāng)瀏覽器不支持 __proto__ 時(shí)候桥滨,遍歷 arrayKeys,通過(guò) def 函數(shù)弛车,我們手動(dòng)把 arrayMethods 上的方法掛載到目標(biāo)數(shù)據(jù)上齐媒,相當(dāng)于是個(gè) polyfill。唯一的區(qū)別是 protoAugment 是把 arrayMethods 掛到了目標(biāo)的原型鏈上帅韧,而 copyAugment 則是直接把 arrayMethods 的方法定義到了目標(biāo)的屬性上里初。

小結(jié)

vue 出于性能的考慮,沒(méi)有用 Object.defineProperty 去監(jiān)聽(tīng)數(shù)組忽舟,而是通過(guò)覆蓋數(shù)組的原型的方法双妨,對(duì)常用的七種方法進(jìn)行了變異,以此來(lái)實(shí)現(xiàn)對(duì)數(shù)組的監(jiān)聽(tīng)叮阅。

源碼地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末刁品,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子浩姥,更是在濱河造成了極大的恐慌挑随,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件勒叠,死亡現(xiàn)場(chǎng)離奇詭異兜挨,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)眯分,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)拌汇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人弊决,你說(shuō)我怎么就攤上這事噪舀。” “怎么了飘诗?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵与倡,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我昆稿,道長(zhǎng)纺座,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任貌嫡,我火速辦了婚禮比驻,結(jié)果婚禮上该溯,老公的妹妹穿的比我還像新娘。我一直安慰自己别惦,他們只是感情好狈茉,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著掸掸,像睡著了一般氯庆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扰付,一...
    開(kāi)封第一講書(shū)人閱讀 49,772評(píng)論 1 290
  • 那天堤撵,我揣著相機(jī)與錄音,去河邊找鬼羽莺。 笑死实昨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的盐固。 我是一名探鬼主播荒给,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼刁卜!你這毒婦竟也來(lái)了志电?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蛔趴,失蹤者是張志新(化名)和其女友劉穎挑辆,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體孝情,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鱼蝉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了箫荡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚀乔。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖菲茬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情派撕,我是刑警寧澤婉弹,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站终吼,受9級(jí)特大地震影響镀赌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜际跪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一商佛、第九天 我趴在偏房一處隱蔽的房頂上張望喉钢。 院中可真熱鬧,春花似錦良姆、人聲如沸肠虽。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)税课。三九已至,卻和暖如春痊剖,著一層夾襖步出監(jiān)牢的瞬間韩玩,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工陆馁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留找颓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓叮贩,卻偏偏與公主長(zhǎng)得像击狮,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子妇汗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容