【vue源码】深度理解v-for

最近在阅读element源码的,可是element内部有不少this._l方法,element源码里面也找不到,查了一下,原来是vue的内部渲染列表的方法vue

源码位置,代码不长,能够一读git

三个工具函数

isDef

isDef是isDefined的缩写,反过来就是isUndefined,反正就是看它是否是undefinedgithub

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的那个值)ui

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 !!this

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没有编码

因此为了可以使用迭代器,咱们能够本身定义一个迭代器,示例代码:spa

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
}
复制代码