🔥Vue暗藏秘招揭示:v-for指令颠覆对象属性渲染顺序的传统观念!

avatar
Eat,Sleep,Code @汽车之家 | autohome

前言

今日发现在Vue项目中有同学使用v-for 指令来渲染对象属性,并且该产品需求中对属性的顺序有特定要求。我们知道,在 JavaScript 中,普通对象的属性排序是无序的,不像数组那样按照索引顺序排列。另外,不同的 JavaScript 引擎可能会以不同的方式对对象的属性进行排序。但在这里v-for真的能够保障对象属性的渲染顺序是正确的吗?难不成是一个隐藏的bug?对于我这个强迫症来说实在忍不了,带着问题,我们马不停蹄开始本文的剖析。

Vue中对象属性遍历渲染机制

  • Vue2中对象属性遍历渲染机制

当然,vue2和vue3的响应式原理是不同,导致v-for的底层渲染原理也可能不同,那么我们首先我们来翻看Vue2中v-for相关的源码:

//vue2中简化后的v-for源码
export function renderList(val: any,render: (val: any, keyOrIndex: string | number, index?: number) => VNode
): Array<VNode> | null {
  let ret: Array<VNode> | null = null,i,l,keys,key
  if (...) {...} else if (isObject(val)) {
    if (hasSymbol && val[Symbol.iterator]) {...} else {
      keys = Object.keys(val)
      ret = new Array(keys.length)
      for (i = 0, l = keys.length; i < l; i++) {
       ...
      }
    }
  }
 ...
  return ret
}

透过源码我们发现,当使用 v-for 遍历普通对象属性时,Vue 使用 Object.keys() 来获取对象的属性。那么object.keys被调用的背后发生了什么?object.keys是如何保障属性顺序的? 接着我顺手翻开了ECMAScript 规范,在ECMAScript 规范(ES2024)中在对Object.keys的底层原理阐述中我们找到了”按属性创建时间升序“这样的描述,如图: image.png 综上我们可以确定,在Vue2中通过使用 Object.keys() 来保证了普通对象属性在使用v-for指令时可以按照顺序渲染。

  • Vue3中对象属性遍历渲染机制

在 Vue 3 中,Vue 引入了响应式系统的重大改进,并通过 Proxy 对象进行对象的响应式追踪。在 Vue 3 中,v-for 遍历对象属性时,Vue 会使用 Reflect.ownKeys() 来获取对象的属性,这个方法会返回对象的所有键,包括字符串键和 Symbol 键,而Reflect.ownKeys() 底层执行原理和Ojbect.keys()相同,都是按照它们在对象中的插入顺序进行渲染。因此 Vue 3 在渲染对象属性时也能够保证它们的顺序。

在React中如何保障对象属性渲染顺序

在 React 的 JSX 中,使用 for...in 循环遍历普通对象属性时,并不能保证属性的顺序。 如果你需要确保对象属性的顺序在 JSX 中保持一致,推荐使用数组或 Map 等有序的数据结构来存储对象属性,然后在 JSX 中遍历和渲染它们。

以下是一个示例,展示如何使用数组来保持对象属性的顺序:

const myObject = {
  key1: 'value1',
  key2: 'value2',
  key3: 'value3'
};

const objectArray = Object.entries(myObject);

return (
  <div>
    {objectArray.map(([key, value]) => (
      <div key={key}>
        {key}: {value}
      </div>
    ))}
  </div>
);

在最新的 ECMAScript 规范(ES2024)中,Object.entries() 方法返回的数组按照对象属性的插入顺序进行排序。这意味着,如果你按顺序向对象添加属性,然后使用 Object.entries() 方法,返回的数组元素将按照插入的顺序排列。

可靠的Map

Map 提供了一种可靠的方法来存储和迭代键值对,并且保持其插入顺序不变。值得注意的是,如果你删除并重新插入某个键值对,它将会移动到最后。但是在不修改键值对的情况下,Map 会保持插入顺序不变。以下是一个示例,展示了 Map 的有序性:

const myMap = new Map();
myMap.set('key1', 'value1');
myMap.set('key2', 'value2');
myMap.set('key3', 'value3');

for (const [key, value] of myMap) {
  console.log(key, value);
}

// 输出结果:
// key1 value1
// key2 value2
// key3 value3

结论

总结而言,在Vue中使用v-for指令遍历对象属性时,是可以保障对象属性的渲染顺序的。在Vue 2 中根据 Object.keys() 返回的顺序进行渲染;而Vue 3 则是通过本身的响应式机制确保了对象属性的渲染顺序,这是 Vue 3 响应式系统的一个改进点,使得在渲染对象属性时更可靠地保持顺序。在 React中使for...in 循环遍历对象属性并不能保证属性的顺序,可以用数组或 Map 等有序的数据结构来存储对象属性,以保障对象属性在渲染时的顺序。当然,上文一直强调的是普通对象,对于Array、Map、TypedArray等对象类型依赖其内置迭代器。另外别忘了我们也可以自定义迭代器来实现自定义迭代行为哦🐥!

需要注意的是,Vue 的渲染行为是由其内部实现决定的,并且未来的 Vue 版本可能会进行更改。因此,依赖于 Vue 对象属性的顺序保持在业务逻辑中可能不是一个可靠的做法。

最后🙋,本文的最终目的是想通过对v-for指令原理的浅要剖析来抛砖引玉,激发读者对 Vue 数据驱动的无限魅力的探索,以引发更深入的思考和探讨。

备注

本文基于最新的ECMAScript 规范(ES2024)进行的调研总结,因时间原因并未调研Object.keys等方法在老旧的js 环境(特别是在 ECMAScript 2015 之前的版本)中执行逻辑是否与目前一致,既有可能在老旧版本浏览器上Vue无法保障对象属性渲染顺序,关于这块的调研后期将补上。另外,对于不同的js方法,在未来的版本可能也会有所更改。因此,在开发中请仔细阅读文档并进行测试,以确保您的代码在不同环境中的表现一致性。

参考文献

developer.mozilla.org/en-US/docs/…

tc39.es/ecma262