Чи можна отримати незліченну кількість успадкованих назв властивостей об’єкта?


99

У JavaScript ми маємо кілька способів отримати властивості об’єкта, залежно від того, що ми хочемо отримати.

1) Object.keys(), який повертає всі власні, перелічені властивості об'єкта, метод ECMA5.

2) for...inцикл, який повертає всі перелічені властивості об'єкта, незалежно від того, є вони власними властивостями або успадковані від ланцюжка прототипів.

3) Object.getOwnPropertyNames(obj)який повертає всі власні властивості об'єкта, численні чи ні.

У нас також є такі методи, які hasOwnProperty(prop)дозволяють нам перевірити, чи властивість передається у спадок або насправді належить цьому об’єкту, і propertyIsEnumerable(prop)які, як випливає з назви, дозволяють перевірити, чи властивість перелічувана.

З усіма цими параметрами неможливо отримати незліченну, не власну властивість об’єкта, що я і хочу зробити. Чи є спосіб зробити це? Іншими словами, чи можу я якось отримати список успадкованих незліченних властивостей?

Дякую.


4
Ваше запитання відповіло на запитання, яке я збирався задати: Як перевірити незліченні властивості (лише для вивчення того, що доступне в заздалегідь визначених об’єктах). Нарешті я знайшов getOwnPropertyNames! :-)
Маркус,

1
@marcus :-) Ось у чому суть!
dkugappi

Відповіді:


115

Оскільки 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;
}

1
Завдяки byby, одне, що я не розумію, - це рядок: while(curr = Object.getPrototypeOf(cure))оскільки умовний оператор використовує оператор присвоєння замість оператора порівняння, чи не завжди це поверне істину? Або цей рядок по суті перевіряє, чи "curr" має прототип?
dkugappi

2
@AlexNabokov він поверне false, якщо результат хибний, що відбудеться, коли Object.getPrototypeOf(cure)повернеться nullвгорі ланцюжка прототипів. Думаю, це не передбачає створення кругових ланцюжків прототипів!
Доменік

2
@Alex Function.prototypeніколи не може бути "кореневим" прототипом, оскільки це посилання на прототип Object.prototype. Функція Object.getPrototypeOf( obj )повертає найвищий об'єкт у ланцюжку прототипів obj. Це дозволяє вам слідувати за ланцюжком прототипів, objпоки не дійдете до його кінця ( nullзначення). Я не впевнений, у чому ваша проблема з цим ...
Шіме Відас

2
@Alex Ні, це не так undefined. Object.getPrototypeOf(John)повертає Boy.prototypeоб'єкт (як слід) - див. тут: jsfiddle.net/aeGLA/1 . Зверніть увагу , що конструктор Boyє НЕ в ланцюзі прототипів John. Прототипом ланцюжок Johnвиглядає наступним чином : Boy.prototype -> Object.prototype -> null.
Шіме Відас,

3
" Я думав, що Object.getPrototypeOf (obj) поверне прототип конструктора obj " - Так. У випадку John, його конструктор є Boy, а prototypeвластивість Boyє Boy.prototype. Так Object.getPrototypeOf(John)повертається Boy.prototype.
Шіме Відас,

9

Більш чисте рішення з використанням рекурсії:

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тощо.


4

Користуючись перевагами наборів, ми отримуємо дещо більш чисте рішення, 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);
}

2

Пряма ітерація в ES6:

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

Приклад запуску:


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власності?
Пауан,

Коли object.getInherited стане істинним? Будь ласка, перевірте питання нижче, оскільки я застряг у спадщині: stackoverflow.com/questions/31718345/…
Равіндра Бабу

IMHO - вони належать Reflect, а не Object. Або - в якості альтернативи - я би очікував від мови Object.keys (src, [настройки]), де необов’язкові параметри можуть вказати, чи включати нечислимі, якщо включати успадковані, якщо включати неперелічені успадковані, якщо включати власні , якщо включати символи, і, можливо, до якої максимальної глибини успадкування копати.
Радагаст Браун

е ... те саме для Object.entries. Не впевнений щодо Object.values. ...Ну. чому ні.
Радагаст Браун

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

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

якщо ви намагаєтеся зареєструвати незліченні властивості батьківського об'єкта, наприклад. за замовчуванням методи, визначені всередині класу в es6, встановлюються на прототипі, але встановлюються як незліченні.

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

0

Реалізація в моїх особистих уподобаннях :)

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);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.