Object.keys的‘诡异’特性,你值得收藏!
先从‘诡异’的问题入手
- 例1: 纯Number类型的属性
const obj = {
1: 1,
6: 6,
3: 3,
2: 2
}
console.log('keys', Object.keys(obj))
// ['1', '2', '3', '6']
返回的key为什么自动按照升序排序了?
- 例2: 纯String类型的属性
const obj2 = {
a: 'a',
c: 'c',
f: 'f',
b: 'b',
}
console.log(Object.keys(obj2))
// ['a', 'c', 'f', 'b']
这里为什么又不自动排序了?
看到这里是不是觉得很懵?话不多说,我们先查文档,看看mdn上对Object.keys
的描述:
Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致 。
emm,然而它并没有说到底是按哪种顺序返回的。
探索
既然文档上找不到,那我们就一步一步来慢慢研究
Object.keys的polyfill的实现
if (!Object.keys) {
Object.keys = (function () {
var hasOwnProperty = Object.prototype.hasOwnProperty,
hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
dontEnums = [
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'constructor'
],
dontEnumsLength = dontEnums.length;
return function (obj) {
if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) throw new TypeError('Object.keys called on non-object');
var result = [];
for (var prop in obj) {
if (hasOwnProperty.call(obj, prop)) result.push(prop);
}
if (hasDontEnumBug) {
for (var i=0; i < dontEnumsLength; i++) {
if (hasOwnProperty.call(obj, dontEnums[i])) result.push(dontEnums[i]);
}
}
return result;
}
})()
};
从Object.keys
的polyfill的实现,我们可以发现它内部其实是用for...in
来实现的。那我们就可以去查找for...in
遍历时的顺序规则。然而它也并没有介绍遍历的顺序是怎样的,那么我们就只能去查找ECMAScript
的规范了。
Object.keys的规范定义
- 调用
ToObject(O)
将结果赋值给变量obj
- 调用
EnumerableOwnPropertyNames(obj, "key")
将结果赋值给变量nameList
- 调用
CreateArrayFromList(nameList)
得到最终的结果
第一步:将参数转换成Object(ToObject(O)
)
因为Object.keys内部会调用ToObject(O)
方法,所以它不只是可以接受对象参数,还可以接受其它类型的参数,下面这张表就是ToObject
根据不同类型的值转成Object的映射:
参数类型 | 结果 |
---|---|
Undefined | 抛出TypeError |
Null | 抛出TypeError |
Number | 返回一个新的 Number 对象 |
String | 返回一个新的 String 对象 |
Boolean | 返回一个新的 Boolean 对象 |
Symbol | 返回一个新的 Symbol 对象 |
Object | 直接将Object返回 |
我们通常给Object.keys传的参数都会是一个对象,但我们也可以来看看其它类型值的返回值会是怎样的?
- Number
console.log(Object.keys(123)) // []
返回的是空数组,这是因为new Number(123)并没有可提取的属性
- String
console.log(Object.keys('123')) // [ '0', '1', '2' ]
字符串之所以返回的不是空数组,是因为new String('123')有可以提取的属性
第二步:通过转换后的对象获得属性列表properties
。(顺序取决于这里)
对象属性列表是通过 EnumerableOwnPropertyNames
获取的,其中比较重要的是调用对象的内部方法OwnPropertyKeys
获得对象的ownKeys
(这些内容可以在ECMAScript规范里面找到,就不展开介绍了,我们重点看排序)
The [[OwnPropertyKeys]] internal method of an ordinary object O takes no arguments. It performs the following steps when called:
- Return ! OrdinaryOwnPropertyKeys(O).
通过上面的介绍,我们可以发现keys的排序取决于 OrdinaryOwnPropertyKeys(O)
翻译过来就是:
- 创建一个空的列表用于存放 keys
- 将所有合法的数组索引按升序的顺序存入
- 将所有字符串类型索引按属性创建时间以升序的顺序存入
- 将所有
Symbol
类型索引按属性创建时间以升序的顺序存入 - 返回 keys
注意:属性列表properties
为List类型(List类型是ECMAScript规范类型)
第三步:将List类型的属性列表properties
转换为Array得到最终的结果。
将List类型的属性列表转换成Array类型非常简单:
- 先声明一个变量
array
,值是一个空数组 - 循环属性列表,将每个元素添加到
array
中 - 将
array
返回
总结
Object.keys
返回的对象属性顺序
将所有合法的数组索引按升序排序
将所有字符串类型索引按属性创建时间以升序排序
将所有
Symbol
类型索引按属性创建时间以升序排序
合法数组索引指的是正整数,负数或者浮点数一律当做字符串处理。严格来说对象属性没有数字类型的,无论是数字还是字符串,都会被当做字符串来处理。
看题
const obj = {}
obj[-1] = -1
obj[1] = 1
obj[1.1] = 1.1
obj['2'] = '2'
obj['c'] = 'c'
obj['b'] = 'b'
obj['a'] = 'a'
obj[2] = 2
obj[Symbol(1)] = Symbol(1)
obj[Symbol('a')] = Symbol('a')
obj[Symbol('b')] = Symbol('b')
obj['d'] = 'd'
console.log(Object.keys(obj))
经过上面对Object.key
特性的介绍,想必大家都不会再搞错Object.keys
的输出顺序了吧。
答案:
[ '1', '2', '-1', '1.1', 'c', 'b', 'a', 'd' ]
看到答案很多同学是不是有很多疑问?
如何理解对象属性是正整数还是字符串?
首先我们上面说过合法数组索引指的是正整数,负数或者浮点数一律当做字符串处理。严格来说对象属性没有数字类型的,无论是数字还是字符串,都会被当做字符串来处理。
所以上面只有1,'2',2
是合法数组索引,但我们知道其实它们都会被转成字符串,所以后面的2
会将前面的'2'
覆盖,然后它们按升序排序。然后负数与浮点数一律当做字符串处理按属性创建时间以升序排序。这样就可以得到上面的答案了。
为什么没有Symbol类型?
因为在 EnumerableOwnPropertyNames
的规范中规定了返回值只应包含字符串属性(上面说了数字其实也是字符串)。
我们也可以在MDN上查看关于 Object.getOwnPropertyNames()
的描述。
Object.getOwnPropertyNames()
方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组。
所以 Symbol 属性是不会被返回的,如果要返回 Symbol 属性可以用 Object.getOwnPropertySymbols()。
我是南玖,我们下期见!!!
Object.keys的‘诡异’特性,你值得收藏!的更多相关文章
- JavaScript中in操作符(for..in)、Object.keys()和Object.getOwnPropertyNames()的区别
ECMAScript将对象的属性分为两种:数据属性和访问器属性.每一种属性内部都有一些特性,这里我们只关注对象属性的[[Enumerable]]特征,它表示是否通过 for-in 循环返回属性,也可以 ...
- 【Java并发编程】并发编程大合集-值得收藏
http://blog.csdn.net/ns_code/article/details/17539599这个博主的关于java并发编程系列很不错,值得收藏. 为了方便各位网友学习以及方便自己复习之用 ...
- [转] JavaScript中in操作符(for..in)、Object.keys()和Object.getOwnPropertyNames()的区别
ECMAScript将对象的属性分为两种:数据属性和访问器属性.每一种属性内部都有一些特性,这里我们只关注对象属性的[[Enumerable]]特征,它表示是否通过 for-in 循环返回属性,也可以 ...
- 小tips:JS之for in、Object.keys()和Object.getOwnPropertyNames()的区别
for..in循环 使用for..in循环时,返回的是所有能够通过对象访问的.可枚举的属性,既包括存在于实例中的属性,也包括存在于原型中的实例.这里需要注意的是使用for-in返回的属性因各个浏览器厂 ...
- mac设计师系列 Adobe “全家桶” 15款设计软件 值得收藏!
文章素材来源:风云社区.简书 文章收录于:风云社区 www.scoee.com,提供1700多款mac软件下载 Adobe Creative Cloud 全线产品均可开放下载(简称Adobe CC 全 ...
- Object.keys(),Object.values() 用法
ES8新特性 Object.keys() 用法 返回键名组成的数组, let arr={ name:'js', sex:'body' } let keys=Object.keys(arr); cons ...
- 对象是否拥有某个属性,in和for in以及object.hasOwnProperty('×××')的异同,以及Object.defineProperty(),Object.keys(),Object.getOwnPropertyNames()的用法
1.在某个对象是否拥有某个属性,判断的方法有很多,常用的方法就是object.hasOwnProperty('×××'),这个方法是不包括对象原型链上的方法的,举个例子: var obj = { na ...
- javascript中 for in 、for 、forEach 、for of 、Object.keys().
一 .for ..in 循环 使用for..in循环时,返回的是所有能够通过对象访问的.可枚举的属性,既包括存在于实例中的属性,也包括存在于原型中的实例.这里需要注意的是使用for-in返回的属性因各 ...
- [转帖]分享Oracle的四道经典面试题,值得收藏
分享Oracle的四道经典面试题,值得收藏 原创 波波说运维 2019-07-20 00:02:00 https://www.toutiao.com/i6713901660919300621/ 概述 ...
随机推荐
- Day05 表格
表格 <table width="300" border="1" cellspacing="0"> <caption> ...
- 如何将 DevSecOps 引入企业?
前 言 现如今,大部分企业已经在内部实现了 DevOps 实践.DevOps 为团队提供了交付可靠软件和快速更新的方法论.这种方法让团队更专注于质量而不是将时间浪费在运维上.然而,结果是,安全实践往往 ...
- PTA(BasicLevel)-1018 锤子剪刀布
一.问题定义 大家应该都会玩"锤子剪刀布"的游戏:两人同时给出手势,胜负规则如下: 剪刀 > 布, 锤子 > 剪刀, 布 > 锤子 现给出两人的交 ...
- Java变量和Scanner类
1.变量的分类1)按数据类型分类 详细说明: 1. 整型:byte(1字节=8bit) \ short(2字节) \ int(4字节) \ long(8字节) ① byte范围:-128 ~ ...
- NC16884 [NOI2001]食物链
题目链接 题目 题目描述 动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形.A吃B,B吃C,C吃A. 现有N个动物,以1-N编号.每个动物都是A,B,C中的一种,但是我们并不知道它到 ...
- 【docker专栏6】详解docker容器状态转换管理命令
docker容器有三种状态运行.停止.暂停,镜像可以创建.运行容器,镜像和容器也可以转换成tar压缩包进行存储.本文为大家介绍容器的状态转换命令及镜像创建运行容器.tar包导入导出相关的命令及使用场景 ...
- HackerRank第一趴--Basic Select
CITY表: Field Type ID number NAME VARCHAR2(17) COUNTRYCODE VARCHAR2(3) DISTRICT VARCHAR2(20) POPULATI ...
- Docker安装及基础命令
一.docker安装 1.安装docker #关闭防火墙和selinux systemctl stop firewalld.service setenforce 0 #安装依赖包 yum ins ...
- NoSQL,关系型数据库,行列数据库对比、类比
数据库对比.类比 关系型数据库.NoSQL 关系型数据库 表与表之间有关系.表存储一些格式化的数据结构,每个元组字段的组成都一样,这样便于表之间的联结操作.不过也限制了其性能瓶颈. 更支持SQL,支持 ...
- 20220723-Mac上使用IntelliJ IDEA
目录 IDEA快捷键 IDEA模板 常用模板快捷键 个人随笔 软件:IntelliJ IDEA 电脑:Mac IDEA快捷键 打开/关闭 项目视图 快捷键:⌘ + 1 运行项目 快捷键:⌃ + ⇧ + ...