是否可以获得对象的不可枚举的继承属性名称?

Posted

技术标签:

【中文标题】是否可以获得对象的不可枚举的继承属性名称?【英文标题】: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 的全部意义所在! 【参考方案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. 灵活:返回每个键,可选排除项

补充:

    基于一组单行函数的过滤函数(这种方式更易于调试,这不是代码高尔夫?),根据传入的参数确定是否应排除任何给定键, 是否遍历原型链的条件(根据airportyh's answer),并且, 在达到顶层之前停止或不停止的条件(根据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 什么时候成真?请检查以下问题,因为我坚持继承:***.com/questions/31718345/… 恕我直言-这些属于反射,而不是对象。或者 - 或者 - 我希望从语言 Object.keys(src, [settings]) 中可选设置可以指定是否包括非可数,如果包括继承,如果包括不可枚举继承,如果包括自己, 如果要包含符号,可能还有要挖掘的最大继承深度。 呃...对于 Object.entries 也是如此。虽然不确定 Object.values 。 ...好吧。为什么不呢。

以上是关于是否可以获得对象的不可枚举的继承属性名称?的主要内容,如果未能解决你的问题,请参考以下文章

迭代不可枚举的属性

对象-枚举属性

01-spring配置详解

Object对象属性

C++遍历获得一个类的所有属性名,对该类的实例的所有属性的值 ...~~

总结Java中反射+枚举+Lambda表达式