【vue原始碼】深度理解v-for

Sarva發表於2019-05-06

最近在閱讀element原始碼的,但是element內部有很多this._l方法,element原始碼裡面也找不到,查了一下,原來是vue的內部渲染列表的方法

原始碼位置,程式碼不長,可以一讀

三個工具函式

isDef

isDef是isDefined的縮寫,反過來就是isUndefined,反正就是看它是不是undefined

function isDef (v) {
  return v !== undefined && v !== null
}
複製程式碼

isObject

isObject,主要區分原始值和物件

function isObject (obj) {
  return obj !== null && typeof obj === 'object'
}
複製程式碼

hasSymbol

用來判斷當前宿主環境是否支援原生 Symbol 和 Reflect.ownKeys。首先判斷 Symbol 和 Reflect 是否存在,並使用 isNative 函式保證 Symbol 與 Reflect.ownKeys 全部是原生定義

var hasSymbol =
  typeof Symbol !== 'undefined' && isNative(Symbol) &&
  typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys);

/* 判斷是否是內建方法 */
function isNative (Ctor) {
  return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
}
複製程式碼

renderList

src/core/instance/render-helpers/index.js 的installRenderHelpers方法中,renderList方法複製給了target._l ,即this._l = renderList

程式碼邏輯很清晰,分四種情況(你可以把val看作被v-for的那個值)

val 為 Array,或者 String

ret = new Array(val.length)
for (i = 0, l = val.length; i < l; i++) {
  ret[i] = render(val[i], i)
}
複製程式碼

val 為 number

竟然還支援 number !!

ret = new Array(val)
for (i = 0; i < val; i++) {
    ret[i] = render(i + 1, i)
}
複製程式碼

val 為 Object

  • 支援Symbol,且含有迭代器的情況

Symbol.iterator 為每一個物件定義了預設的迭代器,內建型別中,Array,String,Map,Set,TypedArray而Object沒有

所以為了能夠使用迭代器,我們可以自己定義一個迭代器,示例程式碼:

const obj = {
  age: 1,
  name: 'liu',
  [Symbol.iterator]: function*() {
    let properties = Object.keys(this)
    for (let i of properties) {
      yield [i, this[i]]
    }
  }
}

const res = obj[Symbol.iterator]()
console.log('res', res.next())
複製程式碼

所以,如果你有自定義列表順序的需求的話,可以自定義一個迭代器,定義遍歷的值的順序

ret = []
const iterator: Iterator<any> = val[Symbol.iterator]()
let result = iterator.next()
while (!result.done) {
    ret.push(render(result.value, ret.length))
    result = iterator.next()
}
複製程式碼
  • 不支援Symbol的情況

這種情況比較簡單,通過Object.key生成物件的屬性陣列,然後遍歷一下

keys = Object.keys(val)
ret = new Array(keys.length)
for (i = 0, l = keys.length; i < l; i++) {
    key = keys[i]
    ret[i] = render(val[key], key, i)
}
複製程式碼

val 沒有被定義的情況

返回一個空陣列

if (!isDef(ret)) {
    ret = []
}
複製程式碼

PS: 雖然我覺得這種異常情況應該置於最前,屬於個人編碼習慣,問題不大

總結

  • v-for 可以對數字,字串進行遍歷
  • 可以自定義物件的迭代器,實現自定義列表順序
  • TypeArray 是有迭代器的,即v-for可以渲染類陣列
  • v-for 裡面做了異常處理,所以當你傳入了不屬於Array,String,Number,Object的值時,v-for渲染一個空陣列

原始碼

import { isObject, isDef, hasSymbol } from 'core/util/index'

/**
 * Runtime helper for rendering v-for lists.
 */
export function renderList (
  val: any,
  render: (
    val: any,
    keyOrIndex: string | number,
    index?: number
  ) => VNode
): ?Array<VNode> {
  let ret: ?Array<VNode>, i, l, keys, key
  if (Array.isArray(val) || typeof val === 'string') {
    ret = new Array(val.length)
    for (i = 0, l = val.length; i < l; i++) {
      ret[i] = render(val[i], i)
    }
  } else if (typeof val === 'number') {
    ret = new Array(val)
    for (i = 0; i < val; i++) {
      ret[i] = render(i + 1, i)
    }
  } else if (isObject(val)) {
    if (hasSymbol && val[Symbol.iterator]) {
      ret = []
      const iterator: Iterator<any> = val[Symbol.iterator]()
      let result = iterator.next()
      while (!result.done) {
        ret.push(render(result.value, ret.length))
        result = iterator.next()
      }
    } else {
      keys = Object.keys(val)
      ret = new Array(keys.length)
      for (i = 0, l = keys.length; i < l; i++) {
        key = keys[i]
        ret[i] = render(val[key], key, i)
      }
    }
  }
  if (!isDef(ret)) {
    ret = []
  }
  (ret: any)._isVList = true
  return ret
}
複製程式碼

相關文章