溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Vue性能優(yōu)化之深挖數(shù)組的示例分析

發(fā)布時(shí)間:2021-09-03 09:44:32 來源:億速云 閱讀:108 作者:小新 欄目:web開發(fā)

這篇文章主要介紹Vue性能優(yōu)化之深挖數(shù)組的示例分析,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!

背景

最近在用 Vue 重構(gòu)一個(gè)歷史項(xiàng)目,一個(gè)考試系統(tǒng),題目量很大,所以核心組件的性能成為了關(guān)注點(diǎn)。先來兩張圖看下最核心的組件 Paper 的樣式。

Vue性能優(yōu)化之深挖數(shù)組的示例分析 

Vue性能優(yōu)化之深挖數(shù)組的示例分析

從圖中來看,分為答題區(qū)與選擇面板區(qū)。

稍微對(duì)交互邏輯進(jìn)行下拆解:

  • 答題模式與學(xué)習(xí)模式可以相互切換,控制正確答案顯隱。

  • 單選與判斷題直接點(diǎn)擊就記錄答案正確性,多選是選擇答案之后點(diǎn)擊確定才能記錄正確性。

  • 選擇面板則是記錄做過的題目的情況,分為六種狀態(tài)(未做過的,未做過且當(dāng)前選擇的,做錯(cuò)的,做錯(cuò)的且當(dāng)前選擇的,做對(duì)的,做對(duì)的且當(dāng)前選擇的),用不同的樣式去區(qū)別。

  • 點(diǎn)擊選擇面板,答題區(qū)能切到對(duì)應(yīng)的題號(hào)。

基于以上考慮,我覺得我必須有三個(gè)響應(yīng)式的數(shù)據(jù):

  • currentIndex: 當(dāng)前選中題目的序號(hào)。

  • questions:所有題目的信息,是個(gè)數(shù)組,里面維護(hù)了每道題的問題、選項(xiàng)、正確與否等信息。

  • cardData:題目分組的信息,也是個(gè)數(shù)組,按章節(jié)名稱對(duì)不同的題目進(jìn)行了分類。

數(shù)組每一項(xiàng)數(shù)據(jù)結(jié)構(gòu)如下:

currentIndex = 0 // 用來標(biāo)記當(dāng)前選中題目的索引

questions = [{
  secId: 1, // 所屬章節(jié)的 id
  tid: 1, // 題目 id
  content: '題目?jī)?nèi)容' // 題目描述
  type: 1, // 題型,1 ~ 3 (單選,多選,判斷)
  options: ['選項(xiàng)1', '選項(xiàng)2', '選項(xiàng)3', '選項(xiàng)4',] // 每個(gè)選項(xiàng)的描述
  choose: [1, 2, 4], // 多選——記錄用戶未提交前的選項(xiàng)
  done: true, // 標(biāo)記當(dāng)前題目是否已做
  answerIsTrue: undefined // 標(biāo)記當(dāng)前題目的正確與否
}]

cardData = [{
  startIndex: 0, // 用來記錄循環(huán)該分組數(shù)據(jù)的起始索引,這個(gè)值等于前面數(shù)據(jù)的長(zhǎng)度累加。
  secName: '章節(jié)名稱',
  secId: '章節(jié)id',
  tids: [1, 2, 3, 11] // 該章節(jié)下面的所有題目的 id
}]

由于題目可以左右滑動(dòng)切換,所以我每次從 questions 取了三個(gè)數(shù)據(jù)去渲染,用的是 cube-ui 的 Slide 組件,只要自己根據(jù) this.currentIndex 結(jié)合 computed 特性去動(dòng)態(tài)的切割三個(gè)數(shù)據(jù)就行。

這一切都顯得很美好,尤其是即將結(jié)束了一個(gè)歷史項(xiàng)目的核心組件的編寫之前,心情特別的舒暢。

然而轉(zhuǎn)折點(diǎn)出現(xiàn)在了渲染選擇面板樣式這一步

代碼邏輯很簡(jiǎn)單,但是發(fā)生了讓我懵逼的事情。

<div class="card-content">
 <div class="block" v-for="item in cardData" :key="item.secName">
  <div class="sub-title">{{item.secName}}</div>
  <div class="group">
   <span
    @click="cardClick(index + item.startIndex)"
    class="item"
    :class="getItemClass(index + item.startIndex)"
    v-for="(subItem, index) in item.secTids"
    :key="subItem">{{index + item.startIndex + 1}}</span>
  </div>
 </div>
</div>

其實(shí)就是利用 cardData 去生成 DOM 元素,這是個(gè)分組數(shù)據(jù)(先是以章節(jié)為維度,章節(jié)下面還有對(duì)應(yīng)的題目),上面的代碼其實(shí)是一個(gè)循環(huán)里面嵌套了另一個(gè)循環(huán)。

但是,只要我切換題目或者點(diǎn)擊面板,抑或是觸發(fā)任意響應(yīng)式數(shù)據(jù)的改變,都會(huì)讓頁面卡死!!

探索

當(dāng)下的第一反應(yīng),肯定是 js 在某一步的執(zhí)行時(shí)間過長(zhǎng),所以利用 Chrome 自帶的 Performance 工具 追蹤了一下,發(fā)現(xiàn)問題出在 getItemClass 這個(gè)函數(shù)調(diào)用,占據(jù)了 99% 的時(shí)間,而且時(shí)間都超過 1s 了。瞅了眼自己的代碼:

getItemClass (index) {
 const ret = {}
 // 如果是做對(duì)的題目,但并不是當(dāng)前選中
 ret['item_true'] = this.questions[index]......
 // 如果是做對(duì)的題目,并且是當(dāng)前選中
 ret['item_true_active'] = this.questions[index]......
 // 如果是做錯(cuò)的題目,但并不是當(dāng)前選中
 ret['item_false'] = this.questions[index]......
 // 如果是做錯(cuò)的題目,并且是當(dāng)前選中
 ret['item_false_active'] = this.questions[index]......
 // 如果是未做的題目,但不是當(dāng)前選中
 ret['item_undo'] = this.questions[index]......
 // 如果是未做的題目,并且是當(dāng)前選中
 ret['item_undo_active'] = this.questions[index]......
 return ret
},

這個(gè)函數(shù)主要是用來計(jì)算選擇面板每一個(gè)小圓圈該有的樣式。每一步都是對(duì) questions 進(jìn)行了 getter 操作。初看,好像沒什么問題,但是由于之前看過 Vue 的源碼,細(xì)想之下,覺得不對(duì)。

首先,webpack 會(huì)將 .vue 文件的 template 轉(zhuǎn)換成 render 函數(shù),也就是實(shí)例化組件的時(shí)候,其實(shí)是對(duì)響應(yīng)式屬性求值的過程,這樣響應(yīng)式屬性就能將 renderWatcher 加入依賴當(dāng)中,所以當(dāng)響應(yīng)式屬性改變的時(shí)候,能觸發(fā)組件重新渲染。

我們先來了解下 renderWatcher 是什么概念,首先在 Vue 的源碼里面是有三種 watcher 的。我們只看 renderWatcher 的定義。

// 位于 vue/src/core/instance/lifecycle.js
new Watcher(vm, updateComponent, noop, {
  before () {
   if (vm._isMounted) {
    callHook(vm, 'beforeUpdate')
   }
  }
}, true /* isRenderWatcher */)

updateComponent = () => {
 vm._update(vm._render(), hydrating)
}

// 位于 vue/src/core/instance/render.js
Vue.prototype._render = function (): VNode {
  ......
  
  const { render, _parentVnode } = vm.$options
  try {
   vnode = render.call(vm._renderProxy, vm.$createElement)
  } catch (e) {
   ......
  }
  return vnode
}

稍微分析下流程:實(shí)例化 Vue 實(shí)例的時(shí)候會(huì)走到 options 取到由 template 編譯生成的 render 函數(shù),進(jìn)而執(zhí)行 renderWatcher 收集依賴。_render 返回的是組件的 vnode,傳入 _update 函數(shù)從而執(zhí)行組件的 patch,最終生成視圖。

其次,從我寫的 template 來分析,為了渲染選擇面板的 DOM,是有兩層 for 循環(huán)的,內(nèi)部每次循環(huán)都會(huì)執(zhí)行 getItemClass 函數(shù),而函數(shù)的內(nèi)部又是對(duì) questions 這個(gè)響應(yīng)式數(shù)組進(jìn)行了 getter 求值,從目前來看,時(shí)間復(fù)雜度是 O(n&sup2;),如上圖所示,我們大概有 2000 多道題目,我們假設(shè)有 10 個(gè)章節(jié),每個(gè)章節(jié)有 200 道題目,getItemClass 內(nèi)部是對(duì) questions 進(jìn)行了 6 次求值,這樣一算,粗略也是 12000 左右,按 js 的執(zhí)行速度,是不可能這么慢的。

那么問題是不是出現(xiàn)在對(duì) questions 進(jìn)行 getter 的過程中,出現(xiàn)了 O(n&sup3;) 的復(fù)雜度呢?

于是,我打開了 Vue 的源碼,由于之前深入研究過源碼,所以輕車熟路地找到了 vue/src/core/instance/state.js 里面將 data 轉(zhuǎn)換成 getter/setter 的部分。

function initData (vm: Component) {
 ......
 // observe data
 observe(data, true /* asRootData */)
}

定義一個(gè)組件的 data 的響應(yīng)式,都是從 observe 函數(shù)開始,它的定義是位于 vue/src/core/observer/index.js 。

export function observe (value: any, asRootData: ?boolean): Observer | void {
 if (!isObject(value) || value instanceof VNode) {
  return
 }
 let ob: Observer | void
 if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
  ob = value.__ob__
 } else if (
  shouldObserve &&
  !isServerRendering() &&
  (Array.isArray(value) || isPlainObject(value)) &&
  Object.isExtensible(value) &&
  !value._isVue
 ) {
  ob = new Observer(value)
 }
 if (asRootData && ob) {
  ob.vmCount++
 }
 return ob
}

observe 函數(shù)接受對(duì)象或者數(shù)組,內(nèi)部會(huì)實(shí)例化 Observer 類。

export class Observer {
 value: any;
 dep: Dep;
 vmCount: number;
 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 {
    copyAugment(value, arrayMethods, arrayKeys)
   }
   this.observeArray(value)
  } else {
   this.walk(value)
  }
 }
 walk (obj: Object) {
  const keys = Object.keys(obj)
  for (let i = 0; i < keys.length; i++) {
   defineReactive(obj, keys[i])
  }
 }
 observeArray (items: Array<any>) {
  for (let i = 0, l = items.length; i < l; i++) {
   observe(items[i])
  }
 }
}

Observer 的構(gòu)造函數(shù)很簡(jiǎn)單,就是聲明了 dep、value 屬性,并且將 value 的 _ ob _ 屬性指向當(dāng)前實(shí)例。舉個(gè)栗子:

// 剛開始的 options 
export default {
  data : {
    msg: '消息',
    arr: [1],
    item: {
      text: '文本'
    }
  }
}
// 實(shí)例化 vm 的時(shí)候,變成了以下
data: {
  msg: '消息',
  arr: [1, __ob__: {
      value: ...,
      dep: new Dep(),
      vmCount: ...
    }],
  item: {
    text: '文本',
    __ob__: {
      value: ...,
      dep: new Dep(),
      vmCount: ...
    }
  },
  __ob__: {
    value: ...,
    dep: new Dep(),
    vmCount: ...
  }
}

也就是每個(gè)對(duì)象或者數(shù)組被 observe 之后,多了一個(gè) _ ob _ 屬性,它是 Observer 的實(shí)例。那么這么做的意義何在呢,稍后分析。

繼續(xù)分析 Observer 構(gòu)造函數(shù)的下面部分:

// 如果是數(shù)組,先篡改數(shù)組的一些方法(push,splice,shift等等),使其能夠支持響應(yīng)式
if (Array.isArray(value)) {
 if (hasProto) {
  protoAugment(value, arrayMethods)
 } else {
  copyAugment(value, arrayMethods, arrayKeys)
 }
 // 數(shù)組里面的元素還是數(shù)組或者對(duì)象,遞歸地調(diào)用 observe 函數(shù),使其成為響應(yīng)式數(shù)據(jù)
 this.observeArray(value)
} else {
 // 遍歷對(duì)象,使其每個(gè)鍵值也能成為響應(yīng)式數(shù)據(jù)  
 this.walk(value)
}
walk (obj: Object) {
  const keys = Object.keys(obj)
  for (let i = 0; i < keys.length; i++) {
   // 將對(duì)象的鍵值轉(zhuǎn)換成 getter / setter,
   // getter 收集依賴
   // setter 通知 watcher 更新
   defineReactive(obj, keys[i])
  }
}
observeArray (items: Array<any>) {
  for (let i = 0, l = items.length; i < l; i++) {
   observe(items[i])
  }
}

我們?cè)俎垡幌滤悸罚紫仍?initState 里面調(diào)用 initData,initData 得到用戶配置的 data 對(duì)象后調(diào)用了 observe,observe 函數(shù)里面會(huì)實(shí)例化 Observer 類,在其構(gòu)造函數(shù)里面,首先將對(duì)象的 _ ob _ 屬性指向 Observer 實(shí)例(這一步是為了檢測(cè)到對(duì)象添加或者刪除屬性之后,能觸發(fā)響應(yīng)式的伏筆),之后遍歷當(dāng)前對(duì)象的鍵值,調(diào)用 defineReactive 去轉(zhuǎn)換成 getter / setter。

所以,來分析下 defineReactive。

// 如果是數(shù)組,先篡改數(shù)組的一些方法(push,splice,shift等等),使其能夠支持響應(yīng)式
if (Array.isArray(value)) {
 if (hasProto) {
  protoAugment(value, arrayMethods)
 } else {
  copyAugment(value, arrayMethods, arrayKeys)
 }
 // 數(shù)組里面的元素還是數(shù)組或者對(duì)象,遞歸地調(diào)用 observe 函數(shù),使其成為響應(yīng)式數(shù)據(jù)
 this.observeArray(value)
} else {
 // 遍歷對(duì)象,使其每個(gè)鍵值也能成為響應(yīng)式數(shù)據(jù)  
 this.walk(value)
}
walk (obj: Object) {
  const keys = Object.keys(obj)
  for (let i = 0; i < keys.length; i++) {
   // 將對(duì)象的鍵值轉(zhuǎn)換成 getter / setter,
   // getter 收集依賴
   // setter 通知 watcher 更新
   defineReactive(obj, keys[i])
  }
}
observeArray (items: Array<any>) {
  for (let i = 0, l = items.length; i < l; i++) {
   observe(items[i])
  }
}

首先,我們從 defineReactive 可以看出,每個(gè)響應(yīng)式屬性都有一個(gè) Dep 實(shí)例,這個(gè)是用來收集 watcher 的。由于 getter 與 setter 都是函數(shù),并且引用了 dep,所以形成了閉包,dep 一直存在于內(nèi)存當(dāng)中。因此,假如在渲染組件的時(shí)候,如果使用了響應(yīng)式屬性 a,就會(huì)走到上述的語句1,dep 實(shí)例就會(huì)收集組件這個(gè) renderWatcher,因?yàn)樵趯?duì) a 進(jìn)行 setter 賦值操作的時(shí)候,會(huì)調(diào)用 dep.notify() 去 通知 renderWatcher 去更新,進(jìn)而觸發(fā)響應(yīng)式數(shù)據(jù)收集新一輪的 watcher。

那么語句2與3,到底是什么作用呢

我們舉個(gè)栗子分析

<div>{{person}}<div>
export default {
 data () {
  return {
   person: {
    name: '張三',
    age: 18
   }    
  }
 }
}

this.person.gender = '男' // 組件視圖不會(huì)更新

因?yàn)?Vue 是無法探測(cè)到對(duì)象增添屬性,所以也沒有一個(gè)時(shí)機(jī)去觸發(fā) renderWatcher 的更新。

為此, Vue 提供了一個(gè) API, this.$set ,它是 Vue.set 的別名。

export function set (target: Array<any> | Object, key: any, val: any): any {
 if (Array.isArray(target) && isValidArrayIndex(key)) {
  target.length = Math.max(target.length, key)
  target.splice(key, 1, val)
  return val
 }
 if (key in target && !(key in Object.prototype)) {
  target[key] = val
  return val
 }
 const ob = (target: any).__ob__
 if (target._isVue || (ob && ob.vmCount)) {
  process.env.NODE_ENV !== 'production' && warn(
   'Avoid adding reactive properties to a Vue instance or its root $data ' +
   'at runtime - declare it upfront in the data option.'
  )
  return val
 }
 if (!ob) {
  target[key] = val
  return val
 }
 defineReactive(ob.value, key, val)
 ob.dep.notify()
 return val
}

set 函數(shù)接受三個(gè)參數(shù),第一個(gè)參數(shù)可以是 Object 或者 Array,其余的參數(shù)分別為 key, value。如果利用這個(gè) API 給 person 增加一個(gè)屬性呢?

this.$set(this.person, 'gender', '男') // 組件視圖重新渲染

為什么通過 set 函數(shù)又能觸發(fā)重新渲染呢?注意到這一句, ob.dep.notify() , ob 怎么來的呢,那就得回到之前的 observe 函數(shù)了,其實(shí) data 經(jīng)過 observe 處理之后變成下面這樣。

{
 person: {
  name: '張三',
  age: 18,
  __ob__: {
   value: ...,
   dep: new Dep()
  }
 },
 __ob__: {
  value: ...,
  dep: new Dep()
 }
}
// 只要是對(duì)象,都定義了 __ob__ 屬性,它是 Observer 類的實(shí)例

從 template 來看,視圖依賴了 person 這個(gè)屬性值,renderWatcher 被收集到了 person 屬性的 Dep 實(shí)例當(dāng)中,對(duì)應(yīng) defineReactive 函數(shù)定義的 語句1 ,同時(shí), 語句2 的作用就是將 renderWatcher 收集到 person._ ob _.dep 當(dāng)中去,因此在給 person 增加屬性的時(shí)候,調(diào)用 set 方法才能獲取到 person._ ob _.dep,進(jìn)而觸發(fā) renderWatcher 更新。

那么得出結(jié)論,語句2的作用是為了能夠探測(cè)到響應(yīng)式數(shù)據(jù)是對(duì)象的情況下增刪屬性而引發(fā)重新渲染的。

再舉個(gè)栗子解釋下 語句3 的作用。

<div>{{books}}<div>
export default {
 data () {
  return {
   books: [
    {
     id: 1,
     name: 'js'
    }
   ]    
  }
 }
}

因?yàn)榻M件對(duì) books 進(jìn)行求值,而它是一個(gè)數(shù)組,所以會(huì)走到語句3的邏輯。

if (Array.isArray(value)) { // 語句3
  dependArray(value)
}

function dependArray (value: Array<any>) {
 for (let e, i = 0, l = value.length; i < l; i++) {
  e = value[i]
  e && e.__ob__ && e.__ob__.dep.depend()
  if (Array.isArray(e)) {
   dependArray(e)
  }
 }
}

從邏輯上來看,就是循環(huán) books 的每一項(xiàng) item,如果 item 是一個(gè)數(shù)組或者對(duì)象,就會(huì)獲取到 item._ ob _.dep,并且將當(dāng)前 renderWatcher 收集到 dep 當(dāng)中去。

如果沒有這一句,會(huì)發(fā)生什么情況?考慮下如下的情況:

this.$set(this.books[0], 'comment', '棒極了') // 并不會(huì)觸發(fā)組件更新

如果理解成 renderWatch 并沒有對(duì) this.books[0] 進(jìn)行求值,所以改變它并不需要造成組件更新,那么這個(gè)理解是有誤的。正確的是因?yàn)閿?shù)組是元素的集合,內(nèi)部的任何修改是需要反映出來的,所以語句3就是為了在 renderWatcher 對(duì)數(shù)組求值的時(shí)候,將 renderWatcher 收集到數(shù)組內(nèi)部每一項(xiàng) item._ ob _.dep 當(dāng)中去,這樣只要內(nèi)部發(fā)生變化,就能通過 dep 獲取到 renderWatcher,通知它更新。

那么結(jié)合我的業(yè)務(wù)代碼,就分析出來問題出現(xiàn)在語句3當(dāng)中。

<div class="card-content">
 <div class="block" v-for="item in cardData" :key="item.secName">
  <div class="sub-title">{{item.secName}}</div>
  <div class="group">
   <span
    @click="cardClick(index + item.startIndex)"
    class="item"
    :class="getItemClass(index + item.startIndex)"
    v-for="(subItem, index) in item.secTids"
    :key="subItem">{{index + item.startIndex + 1}}</span>
  </div>
 </div>
</div>
getItemClass (index) {
 const ret = {}
 // 如果是做對(duì)的題目,但并不是當(dāng)前選中
 ret['item_true'] = this.questions[index]......
 // 如果是做對(duì)的題目,并且是當(dāng)前選中
 ret['item_true_active'] = this.questions[index]......
 // 如果是做錯(cuò)的題目,但并不是當(dāng)前選中
 ret['item_false'] = this.questions[index]......
 // 如果是做錯(cuò)的題目,并且是當(dāng)前選中
 ret['item_false_active'] = this.questions[index]......
 // 如果是未做的題目,但不是當(dāng)前選中
 ret['item_undo'] = this.questions[index]......
 // 如果是未做的題目,并且是當(dāng)前選中
 ret['item_undo_active'] = this.questions[index]......
 return ret
},

首先 cardData 是一個(gè)分組數(shù)據(jù),循環(huán)里面套循環(huán),假設(shè)有 10 個(gè)章節(jié), 每個(gè)章節(jié)有 200 道題目,那么其實(shí)會(huì)執(zhí)行 2000 次 getItemClass 函數(shù),getItemClass 內(nèi)部會(huì)有 6 次對(duì) questions 進(jìn)行求值,每次都會(huì)走到 dependArray,每次執(zhí)行 dependArray 都會(huì)循環(huán) 2000 次,所以粗略估計(jì) 2000 * 6 * 2000 = 2400 萬次,如果假設(shè)一次執(zhí)行的語句是 4 條,那么也會(huì)執(zhí)行接近一億次的語句,性能自然是原地爆炸!

既然從源頭分析出了原因,那么就要找出方法從源頭上去解決。

拆分組件

很多人理解拆分組件是為了復(fù)用,當(dāng)然作用不止是這些,拆分組件更多的是為了可維護(hù)性,可以更語義化,在同事看到你的組件名的時(shí)候,大概能猜出里面的功能。而我這里拆分組件,是為了隔離無關(guān)的響應(yīng)式數(shù)據(jù)造成的組件渲染。從上圖可以看出,只要任何一個(gè)響應(yīng)式數(shù)據(jù)改變,Paper 都會(huì)重新渲染,比如我點(diǎn)擊收藏按鈕,Paper 組件會(huì)重新渲染,按道理只要收藏按鈕這個(gè) DOM 重新渲染即可。

在嵌套循環(huán)中,不要用函數(shù)

性能出現(xiàn)問題的原因是在于我用了 getItemClass 去計(jì)算每一個(gè)小圓圈的樣式,而且在函數(shù)里面還對(duì) questions 進(jìn)行了求值,這樣時(shí)間復(fù)雜度從 O(n&sup2;) 變成了 O(n&sup3;)(由于源碼的 dependArray也會(huì)循環(huán))。最后的解決方案,我是棄用了 getItemClass 這個(gè)函數(shù),直接更改了 cardData 的 tids 的數(shù)據(jù)結(jié)構(gòu),變成了 tInfo,也就是在構(gòu)造數(shù)據(jù)的時(shí)候,計(jì)算好樣式。

this.cardData = [{
  startIndex: 0,
  secName: '章節(jié)名稱',
  secId: '章節(jié)id',
  tInfo: [
  {
    id: 1,
    klass: 'item_false'
  }, 
  {
    id: 2,
    klass: 'item_false_active'
  }]
}]

如此一來,就不會(huì)出現(xiàn) O(n&sup3;) 時(shí)間復(fù)雜度的問題了。

善用緩存

我發(fā)現(xiàn) getItemClass 里面自己寫的很不好,其實(shí)應(yīng)該用個(gè)變量去緩存 quesions,這樣就不會(huì)造成對(duì) questions 多次求值,進(jìn)而多次走到源碼的 dependArray 當(dāng)中去。

const questions = this.questions

// good      // bad
// questions[0]  this.questions[0] 
// questions[1]  this.questions[1]
// questions[2]  this.questions[2]
......

// 前者只會(huì)對(duì) this.questions 一次求值,后者會(huì)三次求值

以上是“Vue性能優(yōu)化之深挖數(shù)組的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

vue
AI