浅析Object.keys()导致的问题及其执行机制

一、导致的问题介绍

动态生成分享卡片的时候,卡片底部的小程序码丢失了,然而其他小伙伴在自己手机上运行正常。事实上除了这条动态以外,其它都是正常的。

先交代一下项目背景,这是一个微信小程序项目,其中生成分享卡片功能用到的是一个叫 wxml2canvas 的库,然而该库目前看上去已经「年久失修」,上面所说的 BUG 就是因为这个库,本文分享一下排查该 BUG 的过程、以及如何从 ECMAScript 规范中找到关于 Object.keys() 返回顺序的规范定义,最后介绍一下在 V8 引擎中是如何处理对象属性的。希望大家在阅读本文后,不会再因为搞不懂 Object.keys() 输出的顺序而犯错导致产生莫名其妙的 BUG。

1、这个 BUG 是如何产生的?

  wxml2canvas 在绘制的时候,会根据一个叫做 sorted 的对象对它的 keys 进行遍历,该对象的 key 为节点的 top 值,value 为节点元素;

  问题就是出在这里,该库作者误以为 Object.keys() 总是会按照实际创建属性的顺序返回,然而当 key 为正整数的时候,返回顺序就不符合原本的预期了,会出现了绘制顺序错乱,从而导致这个 BUG 的产生。

2、如何解决这个 BUG

  由于对象的 key 是一个数字,那么 key 有可能会是整数,也有可能是浮点数。但是预期行为是希望 Object.keys() 按照属性实际创建的顺序返回,那只要将所有 key 都强制转换为浮点数就好了。

3、Object.keys() 是按照什么顺序返回值的?

(1)Object.keys() 返回顺序与遍历对象属性时的顺序一样,调用的 [[OwnPropertyKeys]]() 内部方法。

(2)根据 ECMAScript 规范,在输出 keys 时会先将所有 key 为数组索引类型(正整数)从小到大的顺序排序,然后将所有字符串类型(包括负数、浮点数)的 key 按照实际创建的顺序来排序

4、V8 内部是如何处理对象属性的?

  这个之前我有总结博客,看即可。

5、如何解决该 BUG

  经过对比,发现原来大部分情况下,top 值都会是浮点数,而本次出 BUG 的卡片小程序码只是非常凑巧地为整数,导致绘制顺序不对。我才发现 wxml2canvas 原本的逻辑是想根据 sorted 创建的顺序来绘制,但是没有考虑 key 为整数的情况。所以,最后通过这样修改解决问题:强制添加小数点,将整数转为浮点数。

  很显然,是因为 wxml2canvas 作者对 Object.keys() 返回顺序的机制不了解,才导致出现这样的 BUG。不知道是否也有同学犯过同样的错误,为避免再次出现这样的情况,非常有必要深入、全面地介绍一下 Object.keys() 的执行机制。

二、Object.keys() 的执行机制

  关于 keys 如何排序就在 OrdinaryOwnPropertyKeys 的定义中:

The abstract operation OrdinaryOwnPropertyKeys takes argument O (an Object). It performs the following steps when called:

1. Let keys be a new empty List.

2. For each own property key P of O such that P is an array index, in ascending numeric index order, do a. Add P as the last element of keys.

3. For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do a. Add P as the last element of keys.

4. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do a. Add P as the last element of keys.

5. Return keys.

  到这里,我们已经知道我们想要的答案,这里总结一下:

  1. 创建一个空的列表用于存放 keys
  2. 将所有合法的数组索引按升序的顺序存入
  3. 将所有字符串类型索引按属性创建时间以升序的顺序存入
  4. 将所有 Symbol 类型索引按属性创建时间以升序的顺序存入
  5. 返回 keys

  这里顺便也纠正一个普遍的误区:有些回答说将所有属性为数字类型的 key 从小到大排序,其实不然,还必须要符合 「合法的数组索引」 ,也即只有正整数才行,负数或者浮点数,一律当做字符串处理。

  PS:严格来说对象属性没有数字类型的,无论是数字还是字符串,都会被当做字符串来处理。

复制代码
// 我们结合上面的规范,来思考一下下面这段代码会输出什么:
const testObj = {}

testObj[-1] = ''
testObj[1] = ''
testObj[1.1] = ''
testObj['2'] = ''
testObj['c'] = ''
testObj['b'] = ''
testObj['a'] = ''
testObj[Symbol(1)] = ''
testObj[Symbol('a')] = ''
testObj[Symbol('b')] = ''
testObj['d'] = ''

console.log(Object.keys(testObj))
复制代码

  看完 ECMAScript 的规范定义,相信你不会再搞错 Object.keys() 的输出顺序了。

原文参考:https://juejin.cn/post/7041049741458669576

posted @   古兰精  阅读(1031)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示