เป็นไปได้หรือไม่ที่จะได้รับชื่อคุณสมบัติที่สืบทอดมาซึ่งไม่สามารถระบุได้ของวัตถุ?


104

ใน JavaScript เรามีสองสามวิธีในการรับคุณสมบัติของวัตถุขึ้นอยู่กับสิ่งที่เราต้องการได้รับ

1) Object.keys()ซึ่งส่งคืนคุณสมบัติที่นับได้ของอ็อบเจ็กต์ทั้งหมดเป็นของตัวเองวิธี ECMA5

2) for...inลูปซึ่งส่งคืนคุณสมบัติที่นับได้ทั้งหมดของอ็อบเจกต์ไม่ว่าจะเป็นคุณสมบัติของตัวเองหรือสืบทอดมาจากโซ่ต้นแบบ

3) Object.getOwnPropertyNames(obj)ซึ่งส่งคืนคุณสมบัติของตัวเองทั้งหมดของวัตถุแจกแจงได้หรือไม่

นอกจากนี้เรายังมีวิธีการเช่นhasOwnProperty(prop)ให้เราตรวจสอบว่าทรัพย์สินนั้นสืบทอดมาหรือเป็นของวัตถุนั้นจริงหรือไม่และpropertyIsEnumerable(prop)ตามที่ชื่อแนะนำช่วยให้เราตรวจสอบว่ามีการแจกแจงคุณสมบัติหรือไม่

ด้วยตัวเลือกทั้งหมดนี้ไม่มีทางที่จะได้รับคุณสมบัติที่ไม่สามารถระบุได้และไม่ใช่ของตัวเองซึ่งเป็นสิ่งที่ฉันต้องการทำ มีวิธีใดบ้างที่จะทำเช่นนี้? กล่าวอีกนัยหนึ่งฉันจะได้รับรายการคุณสมบัติที่ไม่สามารถนับได้ที่สืบทอดมาได้หรือไม่?

ขอขอบคุณ.


4
คำถามของคุณตอบคำถามที่ฉันกำลังจะถาม: วิธีตรวจสอบคุณสมบัติที่ไม่สามารถนับได้ (เพียงเพื่อสำรวจว่ามีอะไรอยู่ในวัตถุที่กำหนดไว้ล่วงหน้า) ในที่สุดฉันก็พบ getOwnPropertyNames! :-)
marcus

1
@marcus :-) นั่นคือทั้งหมดที่เกี่ยวกับ!
dkugappi

คำตอบ:


116

เนื่องจาก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
ต้องขอบคุณสิ่งหนึ่งที่ฉันไม่เข้าใจคือบรรทัด: while(curr = Object.getPrototypeOf(cure))เนื่องจากคำสั่งเงื่อนไขใช้ตัวดำเนินการมอบหมายแทนตัวดำเนินการเปรียบเทียบสิ่งนี้จะไม่กลับมาเป็นจริงเสมอไปหรือ หรือบรรทัดนี้ตรวจสอบเป็นหลักว่า "curr" มีต้นแบบหรือไม่?
dkugappi

2
@AlexNabokov มันจะส่งคืนเท็จหากผลลัพธ์เป็นเท็จซึ่งจะเกิดขึ้นเมื่อObject.getPrototypeOf(cure)ส่งคืนnullที่ด้านบนของห่วงโซ่ต้นแบบ ฉันเดาว่านี่ถือว่าไม่มีโซ่ต้นแบบวงกลม!
Domenic

2
@ Alex Function.prototypeไม่สามารถเป็น "ราก" Object.prototypeต้นแบบเพราะมันเป็นจุดเชื่อมโยงต้นแบบ ฟังก์ชันObject.getPrototypeOf( obj )จะส่งคืนวัตถุที่อยู่บนสุดในห่วงโซ่ต้นแบบของobj. ช่วยให้คุณติดตามห่วงโซ่ต้นแบบได้objจนกว่าคุณจะไปถึงจุดสิ้นสุด ( nullค่า) ฉันไม่แน่ใจว่าปัญหาของคุณคืออะไร ...
Šime Vidas

2
@ Alex undefinedไม่มีก็ไม่ได้ Object.getPrototypeOf(John)ส่งกลับBoy.prototypeวัตถุ (เท่าที่ควร) - ดูที่นี่: jsfiddle.net/aeGLA/1 โปรดทราบว่าสร้างBoyเป็นไม่ได้Johnในห่วงโซ่ต้นแบบของ ห่วงโซ่ต้นแบบJohnมีดังนี้: Boy.prototype -> Object.prototype -> null.
Šime Vidas

3
" ฉันคิดว่า Object.getPrototypeOf (obj) จะส่งคืนต้นแบบของคอนสตรัคเตอร์ของ obj " - ใช่ ในกรณีของJohn, คอนสตรัคของเขาBoyและprototypeทรัพย์สินของมีBoy Boy.prototypeดังนั้นผลตอบแทนObject.getPrototypeOf(John) Boy.prototype
Šime Vidas

12

โซลูชันที่สะอาดกว่าโดยใช้การเรียกซ้ำ:

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ทรัพย์สินคืออะไร?
Pauan

เมื่อไหร่ object.getInherited จะกลายเป็นจริง? โปรดตรวจสอบคำถามด้านล่างเนื่องจากฉันติดอยู่กับมรดก: stackoverflow.com/questions/31718345/…
Ravindra babu

IMHO - สิ่งเหล่านี้เป็นของ Reflect ไม่ใช่ Object หรือ - อีกทางหนึ่ง - ฉันคาดหวังจากภาษา Object.keys (src, [settings]) ซึ่งการตั้งค่าที่เป็นทางเลือกสามารถระบุได้ว่าจะรวม non-ninumerables หรือไม่หากจะรวมที่สืบทอดมาหากจะรวมการสืบทอดที่ไม่สามารถนับได้หากจะรวมของตัวเอง ถ้าจะรวมสัญลักษณ์และอาจจะขุดลึกถึงระดับมรดกสูงสุด
Radagast the Brown

เอ่อ ... เหมือนกันสำหรับ Object.entries ไม่แน่ใจเกี่ยวกับ Object.values ​​แม้ว่า ...ดี. ทำไมจะไม่ล่ะ.
Radagast the Brown

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);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.