Можно ли получить не перечисляемые имена наследуемых свойств объекта?
В JavaScript у нас есть несколько способов получить свойства объекта, в зависимости от того, что мы хотим получить.
1) Object.keys()
, который возвращает все собственные перечисляемые свойства объекта, метод ECMA5.
2) for...in
цикл, который возвращает все перечисляемые свойства объекта, независимо от того, являются ли они собственными свойствами или унаследованы от цепочки прототипов.
3) Object.getOwnPropertyNames(obj)
который возвращает все собственные свойства объекта, перечисляемые или нет.
У нас также есть такие методы как hasOwnProperty(prop)
позволяет нам проверить, является ли свойство наследуемым или действительно принадлежит этому объекту, и propertyIsEnumerable(prop)
который, как следует из названия, позволяет нам проверить, является ли свойство перечисляемым.
Со всеми этими опциями нет способа получить не перечисляемое, не-собственное свойство объекта, что я и хочу сделать. Есть какой-либо способ сделать это? Другими словами, могу ли я как-то получить список унаследованных не перечисляемых свойств?
Спасибо.
11 ответов
Поскольку 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;
}
И просто получить все..(enum/nonenum, self/ наследственный..Пожалуйста, подтвердите..
function getAllPropertyNames( obj ) {
var props = [];
do {
props= props.concat(Object.getOwnPropertyNames( obj ));
} while ( obj = Object.getPrototypeOf( obj ) );
return props;
}
Более чистое решение с использованием рекурсии:
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
, так далее.
Обновление за январь 2022 г. - почти все ответы вместе взятые, плюс символы
Увидев , что документация Mozilla JS конкретно говорит : «ни один механизм не повторяет все свойства объекта; каждый из различных механизмов включает в себя разные подмножества свойств».... У меня был именно этот вопрос, хотя и более новый, потому что я также хочу символьные ключи, и я думаю, что все ответы выше предшествуют этим).
Я пришел сюда в надежде, что кто-то еще знает, как создать такой единый механизм.
Кажется, что ни один ответ на этой странице не охватывает ВСЕ, но между всеми ними я думаю, что это можно сделать, включая возможность также исключить раздражающие .
Изучая код в документе Mozilla JS , на который , а также таблицу под ним на той же странице, я обнаружил новый
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. Гибкий: возврат КАЖДОГО ключа с необязательными исключениями.
Добавляет:
- функция фильтра, основанная на наборе однострочных функций (таким образом проще отлаживать, и это не гольф кода), которые определяют, следует ли исключить какой-либо заданный ключ, на основе переданных параметров,
- условие проходить цепочку прототипов или нет (за вдохновил ответ аэропортаответ аэропорта ), и,
- условие остановки или нет до достижения верхнего уровня (согласно ключи верхнего уровняответу Мацея Кравчика ).
Включить или исключить:
- перечисляемые ключи
- неисчислимые ключи
- клавиши символов
- строковые ключи
- собственные ключи
- унаследованные ключи
- ключи верхнего уровня.
(Кстати, я не эксперт по 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;
}
Прямая итерация в 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));
Использование множества наборов приводит к несколько более чистому решению, 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);
}
Если вы пытаетесь зарегистрировать не перечисляемые свойства родительского объекта ex. по умолчанию методы, определенные внутри класса в es6, устанавливаются для прототипа, но не перечисляются.
Object.getOwnPropertyNames(Object.getPrototypeOf(obj));
Чтобы получить все унаследованные свойства или методы для некоторого экземпляра, вы можете использовать что-то вроде этого
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(","));
Обычно вы не хотите включать свойства прототипа объекта, такие как
__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" ]
Реализация в моих личных предпочтениях:)
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);
}
Вот решение, которое я придумал во время изучения предмета. Чтобы получить все не перечисляемые несобственные свойства 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);
}
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);