【vue原始碼】深度理解v-for
最近在閱讀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 } 複製程式碼