关于Object.keys和Object.values的坑

776 阅读2分钟
  • 事件背景

    • 最近在写常量对象枚举时,发现循环Object.keys得出了顺序,并不是创建时的顺序
    • 于是查了查,果然有坑,
      • MDN表述:Object.keys 返回一个所有元素为字符串的数组,其元素来自于从给定的object上面可直接枚举的属性。这些属性的顺序与手动遍历该对象属性时的一致【真要命,具体啥顺序,没说】
        • MDN polifill【可以看出Object.keys是和for in 是一致的,这个就和红宝书上说的对上了】
        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;
            }
          })()
        };
        
      • 红宝书表述:for-in循环、Object.keys()、Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()以及Object.assign()在属性枚举顺序方面有很大区别。for-in循环和Object.keys()的枚举顺序是不确定的,取决于JavaScript引擎,可能因浏览器而异。Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()和Object.assign()的枚举顺序是确定性的。先以升序枚举数值键,然后以插入顺序枚举字符串和符号键。在对象字面量中定义的键以它们逗号分隔的顺序插入。
      • 现在的情况是:Object.keys()也会先序列化升序
  • Object.keys(xxx)的返回值并不一定是对象内的创建顺序

    • 示例Ex:
        const a = {
            1:[1],
            3:[3],
            2:[2]
        }
    
        console.log(Object.keys(a))//[1,2,3]
    
        // 出乎意料的,输出的结果并不是创建顺序[1,3,2]而是序列化之后的[1,2,3]
    
  • Object.values(xxx)的返回值同理也是,并不一定是对象内的创建顺序

    • Object.values(xxx)的顺序完全是Object.keys序列化键之后对应的值得序列化
    • 示例Ex:
        const a = {
            1:[1],
            3:[3],
            2:[2]
        }
    
        console.log(Object.values(a))//[[1],[2],[3]]
    
        // 出乎意料的,输出的结果并不是创建顺序[[1],[3],[2]]而是序列化之后的[[1],[2],[3]]
    
  • 总结:尽量不要让数字或者时间戳数字等作为对象的键,尤其是在列对象枚举时,这简直是个致命的伤,既会影响Object.keys的值也会影响Object.values的值

  • 彩蛋补充:Object.keys和Object.values只能处理可枚举的对象,对于Symbol类枚举键,是不可获取的

    • 示例Ex:
        const a = Symbol()
        const p = {x:1, y:2, [a]:"666"}
        Object.keys(p); //["x","y"]
    
  • 参考文献