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

132

在JavaScript中,根据我们需要获取的内容,我们有几种获取对象属性的方法。

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

2) 使用for...in循环,它返回对象的所有可枚举属性,无论是自身属性还是从原型链继承而来的。

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

我们还有一些其他的方法,例如hasOwnProperty(prop)让我们检查一个属性是否是继承而来的或者是该对象拥有的,以及propertyIsEnumerable(prop),如其名所示,让我们检查一个属性是否可以枚举。

虽然有这么多的选项,但是我们没有办法获取一个对象非可枚举和非自身的属性,这正是我想要做的。有没有什么方法可以做到这一点呢?换句话说,我能否以某种方式获取继承的非可枚举属性的列表?

谢谢。


5
你的问题回答了我想问的问题:如何检查非枚举属性(只是为了探索预定义对象中的可用内容)。最后我发现了getOwnPropertyNames! :-) - marcus
1
@marcus :-) 这就是 Stack Overflow 的全部意义! - dkugappi
11个回答

148
由于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
}

console.log(getAllProperties([1,2,3]));

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

    obj = Object.getPrototypeOf(obj);
  } while (obj);

  return props;
}

1
谢谢你,Toby。有一件事我不明白,就是这行代码: while(curr = Object.getPrototypeOf(cure)),因为条件语句使用了赋值运算符而不是比较运算符,这难道不会始终返回 true 吗?还是说这行代码实际上是在检查“curr”是否有原型? - dkugappi
2
@AlexNabokov 如果结果为假,即当Object.getPrototypeOf(cure)在原型链的顶部返回null时,它将返回false。我猜这假设没有循环原型链! - Domenic
2
@Alex Function.prototype永远不可能是“根”原型,因为它的原型链接指向Object.prototype。函数Object.getPrototypeOf(obj)返回obj原型链中最顶层的对象。它使您能够沿着obj的原型链跟踪,直到达到其末端(null值)。我不确定您对此有什么问题... - Šime Vidas
2
@Alex 不,它不是 undefinedObject.getPrototypeOf(John) 返回 Boy.prototype 对象(正如应该的那样)- 请参见此处:http://jsfiddle.net/aeGLA/1/。请注意,构造函数 Boy 不在 John 的原型链中。John 的原型链如下:Boy.prototype -> Object.prototype -> null - Šime Vidas
3
我会尽力做到最好。翻译如下:"我曾认为Object.getPrototypeOf(obj)会返回obj构造函数的原型" - 是的。以John为例,他的构造函数是Boy,而Boyprototype属性是Boy.prototype。因此,Object.getPrototypeOf(John)会返回Boy.prototype - Šime Vidas
显示剩余9条评论

20

使用递归的更清晰的解决方案:

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等方法可以应用此相同的模板。


14

2022年1月更新--几乎每个答案都合并了,加上符号(ES6+)

在看到 Mozilla的JS文档明确表示:"没有单一机制迭代对象的所有属性;各种机制包含不同的子集属性"之后...我有完全相同的问题,尽管较新,因为我也想要符号键,而且我认为以上所有答案都是旧的。

我来这里是希望有人知道如何创建这样一个单一机制。

这个页面上没有一个答案似乎涵盖了所有内容,但在它们之间,我认为可以做到--包括排除烦人的顶层键的选项。

在探究Mozilla JS文档中的代码airportyh的答案以及同一页面下的表格时,我发现了新的Reflect.ownKeys。它可以捕获所有内容(包括符号)...除了继承属性,但是通过遍历原型链来解决。

因此...将所有这些发现结合起来并尽可能简化后,我得出了以下两个函数,我相信它们可以捕获每一个键。发布出来以帮助其他人。

选项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)。
包括或排除:
  • 可枚举键
  • 不可枚举键
  • 符号键
  • 字符串键
  • 自己的键
  • 继承的键
  • 顶层键。
< p > < em >(附带说一句,我不是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指出,这些解决方案使用了ES6中的Reflect和箭头函数。因此,需要至少使用ES6。


1
被低估的答案(截至撰写本文只有1个赞)。不过可能需要加上一个 Ecmascript 版本警告(例如:它在哪个版本停止工作:ES5/ES6/ES7)。 - Jeff Hykin
@JeffHykin,谢谢!(感谢你的反馈和编辑)。诚然,我不确定你所说的“何时停止工作”的意思是什么。我想你的意思是“它能在哪个ES版本中运行(而在此之前的任何版本都会失败)”...?我认为它使用的最新功能是Reflect.ownKeys(),但我似乎找不到关于它何时引入的任何信息。我欢迎任何帮助。 - DavidT
1
欢迎!是的,我的评论应该写“何时开始运行”。Reflect 是来自 ES6,我很有信心这是 getAllKeys 的唯一版本相关特性。对于 getAllKeysConditionally,箭头函数也是 ES6,而默认值是 ES5。所以总体来说,这应该适用于 ES6 及更新版本。 - Jeff Hykin
1
感谢您的欢迎和信息!已将ES6+添加到标题中,并在末尾添加了一条注释。 - DavidT
A--选项1将包括重复的键。这就是为什么其他ES6答案使用Sets的原因。 B--值得一提的是,我的答案确实提到了它可以用于获取Symbols等,尽管只是作为一个脚注。例如,walkProtoChain(obj, Reflect.ownKeys)将检索所有键。 C--“这里没有人使用Array.prototype.filter()”我用过 ;) 话虽如此,大多数答案都不使用它,因为它是不必要的。问题是关于字符串属性的,其中几乎任何子集都可以直接查询而无需任何过滤。 - Josh Klodnicki
1
A--好的观点(重复键),使用集合作为解决方案,但是如果我想要所有键,甚至是重复的(即使我不确定什么是适当的表示方式),该怎么办?我可以想到一些解决方法,但现在已经超出了原始问题的范围。 B. 是的,很公平。 C. 再次公平。抱歉,我错过了您使用筛选器的用法。 - DavidT

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

示例运行:

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
利用集合的优势可以得到一个比较简洁的解决方案,我个人认为。
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);
}

2

如果你想记录父对象的非枚举属性,例如,默认情况下,在ES6中定义的类内部的方法会设置在原型上,但是被设置为非枚举的。

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

1
通常情况下,您不希望包含对象原型属性,例如__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" ]

1

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

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(","));

1
最好使用Object.getInherited而不是Object.prototype.getInherited。这样做也消除了对丑陋的!(name == 'getInherited')检查的需求。另外,在你的实现中,props数组可能包含重复的属性。最后,忽略constructor属性有什么目的? - Pauan
对象的getInherited属性何时变为true?请查看下面的问题,因为我在继承方面卡住了:http://stackoverflow.com/questions/31718345/javascript-unable-to-access-same-object-method-after-object-createbaseobject/31719231?noredirect=1#comment51377720_31719231 - Ravindra babu
在我看来,这些应该属于 Reflect 而不是 Object。或者,从语言上讲,我期望有 Object.keys(src, [settings]),其中可选的 settings 可以指定是否包括非数字、是否包括继承、是否包括非枚举继承、是否包括自身、是否包括符号,以及可能要挖掘到什么最大继承深度。 - Radagast the Brown
嗯...Object.entries也是一样的。不过我不确定Object.values怎么样。...好吧,为什么不试试呢。 - Radagast the Brown

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

0

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接