浅析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.
到这里,我们已经知道我们想要的答案,这里总结一下:
- 创建一个空的列表用于存放 keys
- 将所有合法的数组索引按升序的顺序存入
- 将所有字符串类型索引按属性创建时间以升序的顺序存入
- 将所有
Symbol
类型索引按属性创建时间以升序的顺序存入 - 返回 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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律