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

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

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

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

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

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

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

谢谢。

【问题讨论】:

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

标签: javascript oop object properties


【解决方案1】:

由于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
【解决方案2】:

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

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等应用相同的模板。

【讨论】:

    【解决方案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】:

      利用 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);
      }
      

      【讨论】:

        【解决方案5】:

        2022 年 1 月更新 -- 几乎所有答案都结合在一起,加上符号 (ES6+)

        在看到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 = [];
            // if primitive (primitives still have keys) skip the first iteration
            if (!(obj instanceof Object)) {
                obj = Object.getPrototypeOf(obj)
            }
            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;
        }
        

        正如 Jeff Hykin 在 cmets 中所指出的,这些解决方案使用 Reflect 和 ES6 中新的箭头函数。因此至少需要 ES6。

        【讨论】:

        • 严重低估了答案(截至撰写本文时还有另外 1 个赞成票)。不过可能应该添加一个 Ecmascript 版本警告(例如它什么时候停止工作:ES5/ES6/ES7)
        • @JeffHykin,谢谢! (用于反馈和编辑)。诚然,我不确定您所说的“它何时停止工作”是什么意思。我认为您的意思是“它工作的最早版本的 ES 是什么(而它在之前的任何版本中都失败了)”......?我认为它使用的最新功能是 Reflect.ownKeys(),但我似乎找不到任何关于它何时被引入的信息。我欢迎任何帮助。
        • 欢迎您!是的,我的评论应该说“它什么时候开始工作”。 Reflect 来自 ES6,我相信这是 getAllKeys 唯一依赖于版本的特性。对于getAllKeysConditionally,箭头函数也是 ES6,默认值是 ES5。所以总的来说这应该适用于 ES6 和更新版本?
        • 感谢您的欢迎和信息! ? 在标题中添加了 ES6+,并在末尾添加了注释。
        【解决方案6】:

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

        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 什么时候成真?请检查以下问题,因为我坚持继承:*.com/questions/31718345/…
        • 恕我直言-这些属于反射,而不是对象。或者 - 或者 - 我希望从语言 Object.keys(src, [settings]) 中可选设置可以指定是否包括非可数,如果包括继承,如果包括不可枚举继承,如果包括自己, 如果要包含符号,可能还有要挖掘的最大继承深度。
        • 呃...对于 Object.entries 也是如此。虽然不确定 Object.values 。 ...好。为什么不呢。
        【解决方案7】:

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

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

        【讨论】:

          【解决方案8】:

          这是我在研究该主题时提出的解决方案。要获取 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);
          }
          

          【讨论】:

            【解决方案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】:

              我个人喜好的实现:)

              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);
              }
              

              【讨论】:

                【解决方案11】:

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

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

                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" ]
                

                【讨论】:

                  最近更新 更多