先从‘诡异’的问题入手

  • 例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的规范定义

  1. 调用ToObject(O)将结果赋值给变量obj
  2. 调用EnumerableOwnPropertyNames(obj, "key")将结果赋值给变量nameList
  3. 调用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:

  1. Return ! OrdinaryOwnPropertyKeys(O).

通过上面的介绍,我们可以发现keys的排序取决于 OrdinaryOwnPropertyKeys(O)

翻译过来就是:

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

注意:属性列表properties为List类型(List类型ECMAScript规范类型

第三步:将List类型的属性列表properties转换为Array得到最终的结果。

将List类型的属性列表转换成Array类型非常简单:

  1. 先声明一个变量array,值是一个空数组
  2. 循环属性列表,将每个元素添加到array
  3. 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的‘诡异’特性,你值得收藏!的更多相关文章

  1. JavaScript中in操作符(for..in)、Object.keys()和Object.getOwnPropertyNames()的区别

    ECMAScript将对象的属性分为两种:数据属性和访问器属性.每一种属性内部都有一些特性,这里我们只关注对象属性的[[Enumerable]]特征,它表示是否通过 for-in 循环返回属性,也可以 ...

  2. 【Java并发编程】并发编程大合集-值得收藏

    http://blog.csdn.net/ns_code/article/details/17539599这个博主的关于java并发编程系列很不错,值得收藏. 为了方便各位网友学习以及方便自己复习之用 ...

  3. [转] JavaScript中in操作符(for..in)、Object.keys()和Object.getOwnPropertyNames()的区别

    ECMAScript将对象的属性分为两种:数据属性和访问器属性.每一种属性内部都有一些特性,这里我们只关注对象属性的[[Enumerable]]特征,它表示是否通过 for-in 循环返回属性,也可以 ...

  4. 小tips:JS之for in、Object.keys()和Object.getOwnPropertyNames()的区别

    for..in循环 使用for..in循环时,返回的是所有能够通过对象访问的.可枚举的属性,既包括存在于实例中的属性,也包括存在于原型中的实例.这里需要注意的是使用for-in返回的属性因各个浏览器厂 ...

  5. mac设计师系列 Adobe “全家桶” 15款设计软件 值得收藏!

    文章素材来源:风云社区.简书 文章收录于:风云社区 www.scoee.com,提供1700多款mac软件下载 Adobe Creative Cloud 全线产品均可开放下载(简称Adobe CC 全 ...

  6. Object.keys(),Object.values() 用法

    ES8新特性 Object.keys() 用法 返回键名组成的数组, let arr={ name:'js', sex:'body' } let keys=Object.keys(arr); cons ...

  7. 对象是否拥有某个属性,in和for in以及object.hasOwnProperty('×××')的异同,以及Object.defineProperty(),Object.keys(),Object.getOwnPropertyNames()的用法

    1.在某个对象是否拥有某个属性,判断的方法有很多,常用的方法就是object.hasOwnProperty('×××'),这个方法是不包括对象原型链上的方法的,举个例子: var obj = { na ...

  8. javascript中 for in 、for 、forEach 、for of 、Object.keys().

    一 .for ..in 循环 使用for..in循环时,返回的是所有能够通过对象访问的.可枚举的属性,既包括存在于实例中的属性,也包括存在于原型中的实例.这里需要注意的是使用for-in返回的属性因各 ...

  9. [转帖]分享Oracle的四道经典面试题,值得收藏

    分享Oracle的四道经典面试题,值得收藏 原创 波波说运维 2019-07-20 00:02:00 https://www.toutiao.com/i6713901660919300621/ 概述 ...

  10. Object.keys(),Object.values(),Object.entries()

    (1)Object.keys() //       返回数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名. eg:var obj = {a:1,b:'gy'} Ob ...

随机推荐

  1. Net设计模式实例之适配器模式(Adapter Pattern)

    一.适配器模式简介(Brief Introduction) 适配器模式,将一个类装换成客户期望的另外一个接口.Adapter模式使的原本由于接口不兼容而不能工作的那些类可以一起工作. 二.解决的问题( ...

  2. 【js跨域】js实现跨域访问的几种方式

    这里说的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同域的框架中(iframe)的数据.只要协议.域名.端口有任何一个不同,都被 ...

  3. nginx 一二事(2) - 创建虚拟静态服务器

    一.什么是nginx 是一个C语言开发的HTTP反向代理服务器,性能非常高 一个俄罗斯的哥们开发的,官方提供的测试性能能够达到5W的并发,我的天呐~,实际测试差不多是2W,而淘宝的牛人可以优化到200 ...

  4. Java/Andriod- 使用Eclipse搭建环境

    从网上找来的,做了一点小修改,我自己试了一下,1.3步骤我没去做,最后也不影响. 在开始Android开发之旅启动之前,首先要搭建环境,然后创建一个简单的HelloWorld.本文的主题如下: 1.环 ...

  5. python中的列表list

    序列是Python中最基本的数据结构.序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推. Python有6个序列的内置类型,但最常见的是列表和元组. 序列 ...

  6. linux任务前后台执行

    Linux任务前后台的切换 Shell支持作用控制,有以下命令实现前后台切换: 1. command& 让进程在后台运行 2. jobs 查看后台运行的进程 3. fg %n 让后台运行的进程 ...

  7. Hibernate中HQL函数汇总及获取当前时间进行比较举例

    在很多时候,我们负责的项目中,在数据访问层(DAO层)通常我们会使用sql语句或者hql语句,而在我们使用hql语句拼接时有时会报错,通常的原因是:我们使用了标准的sql语句,开启的确是hiberna ...

  8. Python爬虫入门教程 33-100 《海王》评论数据抓取 scrapy

    1. 海王评论数据爬取前分析 海王上映了,然后口碑炸了,对咱来说,多了一个可爬可分析的电影,美哉~ 摘录一个评论 零点场刚看完,温导的电影一直很不错,无论是速7,电锯惊魂还是招魂都很棒.打斗和音效方面 ...

  9. 第十节:利用async和await简化异步编程模式的几种写法

    一. async和await简介 PS:简介 1. async和await这两个关键字是为了简化异步编程模型而诞生的,使的异步编程跟简洁,它本身并不创建新线程,但在该方法内部开启多线程,则另算. 2. ...

  10. python 解析命令行

    python中的命令行解析最简单最原始的方法是使用sys.argv来实现,更高级的可以使用argparse这个模块.argparse从python 2.7开始被加入到标准库中,所以如果你的python ...