Object.keys 诡异特性示例详解

 更新时间:2022年10月13日 08:35:40   作者:南玖  
这篇文章主要介绍了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 诡异特性示例详解的详细内容,更多关于Object.keys 诡异的资料请关注脚本之家其它相关文章!

相关文章

  • 微信小程序实现倒计时60s获取验证码

    微信小程序实现倒计时60s获取验证码

    这篇文章主要为大家详细介绍了微信小程序实现倒计时60s获取验证码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-08-08
  • COM中获取JavaScript数组大小的代码

    COM中获取JavaScript数组大小的代码

    JavaScript调用COM传递数组时,在COM接口端,接收到的是VARIANT类型对象,如array,该对象的vt类型VT_DISPATCH,因此其值表示的是一个IDispatch类型的指针。
    2009-11-11
  • 利用jsonp跨域调用百度js实现搜索框智能提示

    利用jsonp跨域调用百度js实现搜索框智能提示

    这篇文章主要为大家详细介绍了使用jsonp跨域调用百度js实现搜索框智能提示,感兴趣的小伙伴们可以参考一下
    2016-08-08
  • js类型转换与引用类型详解(Boolean_Number_String)

    js类型转换与引用类型详解(Boolean_Number_String)

    本篇文章主要是对js中的类型转换与引用类型(Boolean_Number_String)进行了详细的介绍,需要的朋友可以过来参考下,希望对大家有所帮助
    2014-03-03
  • JavaScript监听键盘事件代码实现

    JavaScript监听键盘事件代码实现

    这篇文章主要介绍了JavaScript监听键盘事件代码实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • js 图片随机不定向浮动的实现代码

    js 图片随机不定向浮动的实现代码

    这篇文章介绍了js图片随机不定向浮动的实现代码,有需要的朋友可以参考一下
    2013-07-07
  • JS中let的基本用法举例

    JS中let的基本用法举例

    ES6新增了let命令,用来声明变量,它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效,下面这篇文章主要给大家介绍了关于JS中let的基本用法,需要的朋友可以参考下
    2023-01-01
  • 基于JS实现翻书效果的页面切换样式

    基于JS实现翻书效果的页面切换样式

    在项目开发中经常遇到翻书的页面切换效果,基于js代码怎么实现的呢?今天小编给大家分享基于JS实现翻书效果的页面切换样式,需要的朋友参考下吧
    2017-02-02
  • javascript 读取XML数据,在页面中展现、编辑、保存的实现

    javascript 读取XML数据,在页面中展现、编辑、保存的实现

    最近需要做这样一个需求,数据保存在XML里,在页面上通过表格显示其内容,可以修改内容,再保存到XML。下面把做这个东西的过程记录下来,做个笔记,也给需要的人一些帮助。
    2009-10-10
  • Javascript封装DOMContentLoaded事件实例

    Javascript封装DOMContentLoaded事件实例

    这篇文章主要介绍了Javascript封装DOMContentLoaded事件实例,DOMContentLoaded是FF,Opera 9的特有的Event, 当所有DOM解析完以后会触发这个事件,需要的朋友可以参考下
    2014-06-06

最新评论