【问题标题】:Is it possible to get the non-enumerable inherited property names of an object?是否可以获得对象的不可枚举的继承属性名称?
【发布时间】:2011-12-22 20:59:28
【问题描述】:

在 JavaScript 中,我们有几种获取对象属性的方法,具体取决于我们想要获取的内容。

1) Object.keys(),它返回一个对象的所有自己的、可枚举的属性,一个 ECMA5 方法。

2) 一个for...in循环,它返回一个对象的所有可枚举属性,无论它们是自己的属性,还是从原型链继承的。

3) Object.getOwnPropertyNames(obj) 返回对象的所有自身属性,无论是否可枚举。

我们还有 hasOwnProperty(prop) 等方法让我们检查属性是否被继承或实际上属于该对象,propertyIsEnumerable(prop) 顾名思义,让我们检查属性是否可枚举。

使用所有这些选项,没有办法获得对象的不可枚举、非自己的属性,这正是我想要做的。有没有办法做到这一点?换句话说,我能以某种方式获取继承的不可枚举属性的列表吗?

谢谢。

【问题讨论】:

  • 您的问题回答了我要问的问题:如何检查不可枚举的属性(只是为了探索预定义对象中可用的内容)。最后我找到了getOwnPropertyNames! :-)
  • @marcus :-) 这就是 SO 的全部意义所在!

标签: javascript oop object properties


【解决方案1】:

2022 年 1 月更新 -- 几乎所有答案都加在一起,加上符号

在看到Mozilla's JS documentation specifically say 之后:“没有一种机制可以迭代对象的所有属性;各种机制都包含不同的属性子集。”...我确实有这个问题,尽管它比较新,因为我也想要符号键,我认为以上所有答案都早于那些)。

我来到这里希望其他人知道如何创建这样一个单一的机制。

此页面上似乎没有一个答案可以涵盖所有内容,但在所有答案之间,我认为可以做到——包括排除烦人的top level keys 的选项。

在探索the code in Mozilla's JS doc'n 受到airportyh's answer 启发的过程中,加上the table below it on that same page,我发现了新的Reflect.ownKeys。这可以捕获所有内容(包括符号)...除了继承的属性,但是 airportyh 走原型链的答案可以解决这个问题。

所以...结合所有这些发现并尽可能简化,我想出了以下两个函数,(我相信)可以捕捉一切。发帖以防万一。

选项 1. 简单:返回每个键,没有例外

返回每个键,无论是否可枚举、字符串、符号、自己的、继承的和顶级的。

function getAllKeys(obj) {
    let keys = [];
    while (obj) {
        keys = keys.concat(Reflect.ownKeys(obj));
        obj = Object.getPrototypeOf(obj);
    }
    return keys;
}

我真的很喜欢这种简单,但我想知道我是否遗漏了什么。如果有人发现其中有任何错误,请告诉我。

选项 2. 灵活:返回每个键,可选排除项

补充:

  1. 基于一组单行函数的过滤函数(这种方式更易于调试,这不是代码高尔夫?),根据传入的参数确定是否应排除任何给定键,
  2. 是否遍历原型链的条件(根据airportyh's answer),并且,
  3. 在达到顶层之前停止或不停止的条件(根据Maciej Krawczyk's answer)。

包含或排除:

  • 可枚举的键
  • 不可枚举的键
  • 符号键
  • 字符串键
  • 自己的钥匙
  • 继承的键
  • 顶级键。

(顺便说一句,我不是 JS 专家,所以也许我遗漏了一些东西。我有点困惑为什么这里没有其他人使用 Array.prototype.filter(),因为这不正是我们正在做的吗?)

我相信以下内容涵盖了它。默认情况下,所有内容都包括在内,除了顶级键。调整口味。如果这里有任何错误,我再次欢迎反馈:

function getAllKeysConditionally(obj, includeSelf = true, includePrototypeChain = true, includeTop = false, includeEnumerables = true, includeNonenumerables = true, includeStrings = true, includeSymbols = true) {
    
    // Boolean (mini-)functions to determine any given key's eligibility:
    const isEnumerable = (obj, key) => Object.propertyIsEnumerable.call(obj, key);
    const isString = (key) => typeof key === 'string';
    const isSymbol = (key) => typeof key === 'symbol';
    const includeBasedOnEnumerability = (obj, key) => (includeEnumerables && isEnumerable(obj, key)) || (includeNonenumerables && !isEnumerable(obj, key));
    const includeBasedOnKeyType = (key) => (includeStrings && isString(key)) || (includeSymbols && isSymbol(key));
    const include = (obj, key) => includeBasedOnEnumerability(obj, key) && includeBasedOnKeyType(key);
    const notYetRetrieved = (keys, key) => !keys.includes(key);
    
    // filter function putting all the above together:
    const filterFn = key => notYetRetrieved(keys, key) && include(obj, key);
    
    // conditional chooses one of two functions to determine whether to exclude the top level or not:
    const stopFn = includeTop ? (obj => obj === null) : (obj => Object.getPrototypeOf(obj) === null);
    
    // and now the loop to collect and filter everything:
    let keys = [];
    while (!stopFn(obj, includeTop)) {
        if (includeSelf) {
            const ownKeys = Reflect.ownKeys(obj).filter(filterFn);
            keys = keys.concat(ownKeys);
        }
        if (!includePrototypeChain) { break; }
        else {
            includeSelf = true;
            obj = Object.getPrototypeOf(obj);
        }
    }
    return keys;
}

【讨论】:

    【解决方案2】:

    您通常不希望包含 Object 原型属性,例如 __defineGetter__hasOwnProperty__proto__ 等。

    此实现允许您包含或排除对象原型属性:

    function getAllPropertyNames(object, includeObjectPrototype = false) {
      const props = Object.getOwnPropertyNames(object);
    
      let proto = Object.getPrototypeOf(object);
      const objectProto = Object.getPrototypeOf({});
    
      while (proto && (includeObjectPrototype || proto !== objectProto)) {
        for (const prop of Object.getOwnPropertyNames(proto)) {
          if (props.indexOf(prop) === -1) {
            props.push(prop);
          }
        }
        proto = Object.getPrototypeOf(proto);
      }
    
      return props;
    }
    
    
    console.log(getAllPropertyNames(new Error('Test'), true));
    // ["fileName", "lineNumber", "columnNumber", "message", "toString", "name", "stack", "constructor", "toLocaleString", "valueOf", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__", "__proto__"]
    
    console.log(getAllPropertyNames(new Error('Test'), false));
    // [ "fileName", "lineNumber", "columnNumber", "message", "toString", "name", "stack", "constructor" ]
    

    【讨论】:

      【解决方案3】:

      在 ES6 中直接迭代:

      function getAllPropertyNames(obj) {
          let result = new Set();
          while (obj) {
              Object.getOwnPropertyNames(obj).forEach(p => result.add(p));
              obj = Object.getPrototypeOf(obj);
          }
          return [...result];
      }
      

      示例运行:

      function getAllPropertyNames(obj) {
        let result = new Set();
        while (obj) {
          Object.getOwnPropertyNames(obj).forEach(p => result.add(p));
          obj = Object.getPrototypeOf(obj);
        }
        return [...result];
      }
      
      let obj = {
        abc: 123,
        xyz: 1.234,
        foobar: "hello"
      };
      
      console.log(getAllPropertyNames(obj));

      【讨论】:

        【解决方案4】:

        我个人喜好的实现:)

        function getAllProperties(In, Out = {}) {
            const keys = Object.getOwnPropertyNames(In);
            keys.forEach(key => Object.defineProperty(In, key, {
                enumerable: true
            }));
            Out = { ...In, ...Out };
        
            const Prototype = Object.getPrototypeOf(In);
            return Prototype === Object.prototype ? Out : getAllProperties(Proto, Out);
        }
        

        【讨论】:

          【解决方案5】:

          由于getOwnPropertyNames 可以为您提供不可枚举的属性,您可以使用它并将其与原型链向上结合。

          function getAllProperties(obj){
              var allProps = []
                , curr = obj
              do{
                  var props = Object.getOwnPropertyNames(curr)
                  props.forEach(function(prop){
                      if (allProps.indexOf(prop) === -1)
                          allProps.push(prop)
                  })
              }while(curr = Object.getPrototypeOf(curr))
              return allProps
          }
          

          我在 Safari 5.1 上测试并得到了

          > getAllProperties([1,2,3])
          ["0", "1", "2", "length", "constructor", "push", "slice", "indexOf", "sort", "splice", "concat", "pop", "unshift", "shift", "join", "toString", "forEach", "reduceRight", "toLocaleString", "some", "map", "lastIndexOf", "reduce", "filter", "reverse", "every", "hasOwnProperty", "isPrototypeOf", "valueOf", "__defineGetter__", "__defineSetter__", "__lookupGetter__", "propertyIsEnumerable", "__lookupSetter__"]
          

          更新:稍微重构了代码(添加了空格和花括号,并改进了函数名称):

          function getAllPropertyNames( obj ) {
              var props = [];
          
              do {
                  Object.getOwnPropertyNames( obj ).forEach(function ( prop ) {
                      if ( props.indexOf( prop ) === -1 ) {
                          props.push( prop );
                      }
                  });
              } while ( obj = Object.getPrototypeOf( obj ) );
          
              return props;
          }
          

          【讨论】:

          • 谢谢 toby,我不明白的一件事是:while(curr = Object.getPrototypeOf(cure)),因为条件语句使用赋值运算符而不是比较运算符,这不总是返回 true 吗?还是这条线本质上是检查“curr”是否有原型?
          • @AlexNabokov 如果结果为 falsy,它将返回 false,这将在原型链顶部的 Object.getPrototypeOf(cure) 返回 null 时发生。我猜这假设没有圆形原型链!
          • @Alex Function.prototype 永远不能成为“根”原型,因为它的原型链接指向 Object.prototype。函数Object.getPrototypeOf( obj ) 返回obj 原型链中最顶层的对象。它使您能够遵循obj 的原型链,直到到达其末端(null 值)。我不确定你的问题是什么......
          • @Alex 不,不是undefinedObject.getPrototypeOf(John) 返回 Boy.prototype 对象(应该如此) - 请参见此处:jsfiddle.net/aeGLA/1。请注意,John 的原型链中的构造函数 BoynotJohn的原型链如下:Boy.prototype -> Object.prototype -> null
          • "我认为 Object.getPrototypeOf(obj) 会返回 obj 的构造函数原型" - 是的。对于John,他的构造函数是Boy,而Boyprototype属性是Boy.prototype。所以Object.getPrototypeOf(John) 返回Boy.prototype
          【解决方案6】:

          使用递归的更简洁的解决方案:

          function getAllPropertyNames (obj) {
              const proto     = Object.getPrototypeOf(obj);
              const inherited = (proto) ? getAllPropertyNames(proto) : [];
              return [...new Set(Object.getOwnPropertyNames(obj).concat(inherited))];
          }
          

          编辑

          更多通用函数:

          function walkProtoChain (obj, callback) {
              const proto     = Object.getPrototypeOf(obj);
              const inherited = (proto) ? walkProtoChain(proto, callback) : [];
              return [...new Set(callback(obj).concat(inherited))];
          }
          
          function getOwnNonEnumPropertyNames (obj) {
              return Object.getOwnPropertyNames(obj)
                  .filter(p => !obj.propertyIsEnumerable(p));
          }
          
          function getAllPropertyNames (obj) {
              return walkProtoChain(obj, Object.getOwnPropertyNames);
          }
          
          function getAllEnumPropertyNames (obj) {
              return walkProtoChain(obj, Object.keys);
          }
          
          function getAllNonEnumPropertyNames (obj) {
              return walkProtoChain(obj, getOwnNonEnumPropertyNames);
          }
          

          可以使用Object.getOwnPropertySymbols等应用相同的模板。

          【讨论】:

            【解决方案7】:

            利用 Sets 可以带来更简洁的解决方案,IMO。

            const own = Object.getOwnPropertyNames;
            const proto = Object.getPrototypeOf;
            
            function getAllPropertyNames(obj) {
                const props = new Set();
                do own(obj).forEach(p => props.add(p)); while (obj = proto(obj));
                return Array.from(props);
            }
            

            【讨论】:

              【解决方案8】:

              如果您尝试记录父对象 ex 的不可枚举属性。默认情况下,在 es6 中的类中定义的方法在原型上设置,但设置为不可枚举。

              Object.getOwnPropertyNames(Object.getPrototypeOf(obj));
              

              【讨论】:

                【解决方案9】:
                function getNonEnumerableNonOwnPropertyNames( obj ) {
                    var oCurObjPrototype = Object.getPrototypeOf(obj);
                    var arReturn = [];
                    var arCurObjPropertyNames = [];
                    var arCurNonEnumerable = [];
                    while (oCurObjPrototype) {
                        arCurObjPropertyNames = Object.getOwnPropertyNames(oCurObjPrototype);
                        arCurNonEnumerable = arCurObjPropertyNames.filter(function(item, i, arr){
                            return !oCurObjPrototype.propertyIsEnumerable(item);
                        })
                        Array.prototype.push.apply(arReturn,arCurNonEnumerable);
                        oCurObjPrototype = Object.getPrototypeOf(oCurObjPrototype);
                    }
                    return arReturn;
                }
                

                使用示例:

                function MakeA(){
                
                }
                
                var a = new MakeA();
                
                var arNonEnumerable = getNonEnumerableNonOwnPropertyNames(a);
                

                【讨论】:

                  【解决方案10】:

                  这是我在研究该主题时提出的解决方案。要获取 obj 对象的所有不可枚举的非自己属性,请执行 getProperties(obj, "nonown", "nonenum");

                  function getProperties(obj, type, enumerability) {
                  /**
                   * Return array of object properties
                   * @param {String} type - Property type. Can be "own", "nonown" or "both"
                   * @param {String} enumerability - Property enumerability. Can be "enum", 
                   * "nonenum" or "both"
                   * @returns {String|Array} Array of properties
                   */
                      var props = Object.create(null);  // Dictionary
                  
                      var firstIteration = true;
                  
                      do {
                          var allProps = Object.getOwnPropertyNames(obj);
                          var enumProps = Object.keys(obj);
                          var nonenumProps = allProps.filter(x => !(new Set(enumProps)).has(x));
                  
                          enumProps.forEach(function(prop) {
                              if (!(prop in props)) {
                                  props[prop] = { own: firstIteration, enum_: true };
                              }           
                          });
                  
                          nonenumProps.forEach(function(prop) {
                              if (!(prop in props)) {
                                  props[prop] = { own: firstIteration, enum_: false };
                              }           
                          });
                  
                          firstIteration = false;
                      } while (obj = Object.getPrototypeOf(obj));
                  
                      for (prop in props) {
                          if (type == "own" && props[prop]["own"] == false) {
                              delete props[prop];
                              continue;
                          }
                          if (type == "nonown" && props[prop]["own"] == true) {
                              delete props[prop];
                              continue;
                          }
                  
                          if (enumerability == "enum" && props[prop]["enum_"] == false) {
                              delete props[prop];
                              continue;
                          }
                          if (enumerability == "nonenum" && props[prop]["enum_"] == true) {
                              delete props[prop];
                          }
                      }
                  
                      return Object.keys(props);
                  }
                  

                  【讨论】:

                    【解决方案11】:

                    要获取某个实例的所有继承属性或方法,您可以使用类似这样的方法

                    var BaseType = function () {
                        this.baseAttribute = "base attribute";
                        this.baseMethod = function() {
                            return "base method";
                        };
                    };
                    
                    var SomeType = function() {
                        BaseType();
                        this.someAttribute = "some attribute";
                        this.someMethod = function (){
                            return "some method";
                        };
                    };
                    
                    SomeType.prototype = new BaseType();
                    SomeType.prototype.constructor = SomeType;
                    
                    var instance = new SomeType();
                    
                    Object.prototype.getInherited = function(){
                        var props = []
                        for (var name in this) {  
                            if (!this.hasOwnProperty(name) && !(name == 'constructor' || name == 'getInherited')) {  
                                props.push(name);
                            }  
                        }
                        return props;
                    };
                    
                    alert(instance.getInherited().join(","));
                    

                    【讨论】:

                    • 最好使用Object.getInherited 而不是Object.prototype.getInherited。这样做也消除了对丑陋的!(name == 'getInherited') 检查的需要。此外,在您的实现中,props 数组可以包含重复的属性。最后,忽略constructor 属性的目的是什么?
                    • object.getInherited 什么时候成真?请检查以下问题,因为我坚持继承:stackoverflow.com/questions/31718345/…
                    • 恕我直言-这些属于反射,而不是对象。或者 - 或者 - 我希望从语言 Object.keys(src, [settings]) 中可选设置可以指定是否包括非可数,如果包括继承,如果包括不可枚举继承,如果包括自己, 如果要包含符号,可能还有要挖掘的最大继承深度。
                    • 呃...对于 Object.entries 也是如此。虽然不确定 Object.values 。 ...好吧。为什么不呢。
                    猜你喜欢
                    • 2012-02-13
                    • 1970-01-01
                    • 2018-12-18
                    • 2017-12-18
                    • 2022-11-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多