當 Vue 處理陣列與處理純物件的方式一樣

三毛丶發表於2019-03-04

處理陣列方法的弊端

Vue 在響應式的處理中,對陣列與物件採用了不同的方式,如下原始碼所示:

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

複製程式碼

當值是陣列時,Vue 通過攔截陣列變異方法的方式來實現響應式,此種方式有兩弊端:

  • 通過索引設定項,Vue 不能監測到。
  • 修改陣列長度時,Vue 也不能監測到。

使用與處理純物件相同的方式

既然在單獨處理陣列時,有以上弊端,那為什麼不使用和純物件一樣的方式?

修改部分原始碼如下:

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

複製程式碼

接著,我們主要對陣列測試兩點,利用索引設定項,以及修改陣列長度:

<div id="app">
  <div>{{ test }}</div>
  <div>{{ test.length }}</div>
  <button @click="someMethod">button</button>
</div>
<script>
new Vue({
  el: '#app',
  data: {
    test: [1, 2, 3, 4]
  },
  methods: {
    someMethod () {
      this.test[0] = 5
      this.test.length = 10
      console.log(this.test) // [5, 2, 3, 4, empty * 6]
    }
  }
})
</script>
複製程式碼

當點選 button 時,能看到結果:

當 Vue 處理陣列與處理純物件的方式一樣

Wait, 為什麼陣列裡出現了 null ?

null?empty?

當給陣列設定 length 時,如果大於陣列本身長度,新元素則會以 empty 填充,如下所示:

const arr = [1, 2, 3]
arr.length = 5
console.log(arr) // [1, 2, 3, empty * 2]
複製程式碼

empty 不同於 undefined,在遍歷時,會被忽略:

const arr = [1, 2, 3]
arr[5] = undefined

console.log(arr) // [1, 2, 3, empty * 2, undefined]

arr.forEach(item => console.log(item))

// 1 2 3 undefined
複製程式碼

那麼問題來了,上圖中為什麼出現 null?(this.test 列印出來正常,在 html 中渲染出 null)

為了探究此問題,我嘗試在 html 中輸出一個陣列變數:

const arr = [1, 2, 3]
document.write(arr)
複製程式碼

可是事與願違:

當 Vue 處理陣列與處理純物件的方式一樣

我好像得到了字串。

換個物件試試:

const obj = { a: 1 }
document.write(obj)
複製程式碼

結果:

當 Vue 處理陣列與處理純物件的方式一樣

輸出的結果,好像被 toString() 了?

const obj = { a: 1 }
console.log(obj.toString()) // [object Object]
複製程式碼

也就是說,當你嘗試在頁面輸出一個變數時,JavaScript 會自動呼叫 toString() 方法。

既然這樣,為了讓頁面輸出一個變數,需要把變數序列化:

const arr = [1, 2, 3]
arr.length = 6
document.write(JSON.stringify(arr))
複製程式碼

得到結果:

[1, 2, 3, null, null, null]
複製程式碼

陣列成員裡的 empty 在經過 JSON.stringify 後,轉化成了 null

大陣列下的效能問題

從例子中可以看出,其實 Vue 是可以使用與處理純物件的方式來處理陣列的。官方解釋不這麼做的原因是出於對效能的考慮。

為了得到驗證,我嘗試使用以下兩種不同方式:

  • Vue 單獨處理陣列的方式;
  • 和處理純物件相同的方式。

通過兩者頁面 Load 時間,來對比效能差異。

測試程式碼:

<div id="app">
  <div>{{ test }}</div>
</div>
<script>
const arr = new Array(100000)
new Vue({
  el: '#app',
  data: {
    test: arr
  }
})
</script>
複製程式碼

當使用 Vue 單獨處理陣列的方式時:

當 Vue 處理陣列與處理純物件的方式一樣

當使用與處理純物件相同的方式時:

當 Vue 處理陣列與處理純物件的方式一樣

可見效能上,前者還是好很多。畢竟遍歷很長的陣列,確實是一件很耗效能的事。

我的部落格即將同步至騰訊雲+社群,邀請大家一同入駐:cloud.tencent.com/developer/s…

相關文章