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

dku*_*ppi 90 javascript oop properties object

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

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

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

3)Object.getOwnPropertyNames(obj)返回对象的所有属性,可枚举与否.

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

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

谢谢.

air*_*tyh 106

既然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
}
Run Code Online (Sandbox Code Playgroud)

我在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__"]
Run Code Online (Sandbox Code Playgroud)

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

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;
}
Run Code Online (Sandbox Code Playgroud)

简单地得到一切......(enum/nonenum,self/inherited .. 请确认..

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
}
Run Code Online (Sandbox Code Playgroud)

  • "*我以为Object.getPrototypeOf(obj)将返回obj的构造函数的原型*" - 是的.在"John"的情况下,他的构造函数是`Boy`,而`Boy`的`prototype`属性是`Boy.prototype`.所以`Object.getPrototypeOf(John)`返回`Boy.prototype`. (3认同)
  • @AlexNabokov 如果结果为假,它将返回 false,当 `Object.getPrototypeOf(cure)` 在原型链的顶部返回 `null` 时会发生这种情况。我想这假设没有圆形原型链! (2认同)
  • @Alex`Function.prototype`永远不能成为"根"原型,因为它的原型链接指向`Object.prototype`.函数`Object.getPrototypeOf(obj)`返回`obj`原型链中最顶层的对象.它使您能够遵循`obj`的原型链,直到达到它的结尾(`null`值).我不确定你的问题是什么...... (2认同)
  • @Alex不,这不是'未定义'.`Object.getPrototypeOf(John)`返回`Boy.prototype`对象(应该这样) - 见这里:http://jsfiddle.net/aeGLA/1/.请注意,构造函数`Boy`在`John`的原型链中不是**.`John`的原型链如下:`Boy.prototype - > Object.prototype - > null`. (2认同)

Jos*_*cki 17

使用递归的更干净的解决方案:

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

编辑

更通用的功能:

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);
}
Run Code Online (Sandbox Code Playgroud)

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


Dav*_*idT 15

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

在看到Mozilla 的 JS 文档后特别指出:“没有单一机制可以迭代对象的所有属性;各种机制各自包含不同的属性子集。”...我确实有这个问题,尽管是较新的,因为我也想要符号键,并且我认为所有上面的答案早于这些)。

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

此页面上没有一个答案似乎涵盖了所有内容,但在所有答案之间,我认为这是可以完成的 - 包括还排除烦人的顶级键的选项。

在探索Mozilla 的 JS 文档中的代码时, airportyh的答案启发了我,再加上同一页面上它下面的表格,我发现了新的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;
}
Run Code Online (Sandbox Code Playgroud)

我真的很喜欢这种简单性,尽管我想知道我是否错过了什么。如果有人发现任何错误,请告诉我。

选项 2. 灵活:返回所有密钥,并可选择排除

补充:

  1. 基于一堆单行函数的过滤函数(这样更容易调试,这不是高尔夫代码),它根据传入的参数确定是否应排除任何给定的键,
  2. 是否遍历原型链的条件(根据airportyh 的回答),并且,
  3. 在达到最高级别之前是否停止的条件(根据Maciej Krawczyk 的回答)。

包括或排除:

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

(顺便说一句,我不是 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;
}
Run Code Online (Sandbox Code Playgroud)

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


nop*_*ole 5

ES6 中的直接迭代:

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

运行示例:

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