web-dev-qa-db-ja.com

ES6クラスのゲッターを列挙可能に設定する

ゲッタープロパティを持つES6クラス(babeljsでトランスコンパイル)があります。これらのプロパティはデフォルトでは列挙できないことを理解しています。しかし、Object.definePropertyを使用してプロパティを列挙可能にできない理由がわかりません

// Declare class
class Person {
  constructor(myName) {
    this.name = myName;
  }

  get greeting() {
    return `Hello, I'm ${this.name}`;
  }
}

// Make enumerable (doesn't work)
Object.defineProperty(Person, 'greeting', {enumerable: true});

// Create an instance and get enumerable properties
var person = new Person('Billy');
var enumerableProperties = Object.keys(person);
// => ['name']

Plunkerの例

28
lightswitch05

ES6スタイルのゲッターは、個々のpersonではなく、プロトタイプで定義されます。 greetingプロパティを列挙可能に設定するには、変更する必要があります。

_// Make enumerable (doesn't work)
Object.defineProperty(Person, 'greeting', {enumerable: true});
_

に:

_// Make enumerable
Object.defineProperty(Person.prototype, 'greeting', {enumerable: true});
_

Object.keys は、そのオブジェクトのown列挙可能なプロパティのみを返すため、プロパティプロトタイプでは返されません。 greetingプロパティはObject.keys( Object.getPrototypeOf( person ) )または for ... in ループにあります。 更新されたプランカー

代わりに、Personの各インスタンスに独自のgreetingを持たせたい場合は、コンストラクターで定義できます。

_class Person {
  constructor(myName) {
    this.name = myName;

    Object.defineProperty( this, 'greeting', {
      enumerable: true,
      get: function ( ) { return `Hello, I'm ${this.name}`; }
    } );
  }
}
_

更新されたプランカー

37
Paulpro

クラスとは?

クラスの非静的メソッドとアクセサーは、クラスのプロトタイプにあり、クラスのすべてのインスタンスがそれらを継承します。インスタンスを介してそれらにアクセスできますが、それらはインスタンスの独自のプロパティではありません。静的メソッドとアクセサーは、クラス(関数)自体にあります。

class Test {
        #private_field = "A private field.";
        public_field = "A public field.";
        static get static_getter() {
                return "A static getter.";
        }
        static static_method() {
                return "A static method.";
        }
        get getter() {
                return "A non-static getter.";
        }
        method() {
                return "A non-static method.";
        }
}

console.log(`Class ("${typeof Test}" type)`, Object.getOwnPropertyDescriptors(Test));
console.log("Its prototype", Object.getOwnPropertyDescriptors(Test.prototype));
console.log("Its instance", Object.getOwnPropertyDescriptors(new Test));
Class ("function" type) {
    "length": {
        "value": 0,
        "writable": false,
        "enumerable": false,
        "configurable": true
    },
    "prototype": {
        "value": {……},
        "writable": false,
        "enumerable": false,
        "configurable": false
    },
    "static_getter": {
        "get": ƒ static_getter() {……},
        "set": undefined,
        "enumerable": false,
        "configurable": true
    },
    "static_method": {
        "value": ƒ static_method() {……},
        "writable": true,
        "enumerable": false,
        "configurable": true
    },
    "name": {
        "value": "Test",
        "writable": false,
        "enumerable": false,
        "configurable": true
    }
}
Its prototype {
    "constructor": {
        "value": class Test {……},
        "writable": true,
        "enumerable": false,
        "configurable": true
    },
    "getter": {
        "get": ƒ getter() {……},
        "set": undefined,
        "enumerable": false,
        "configurable": true
    },
    "method": {
        "get": ƒ method() {……},
        "writable": true,
        "enumerable": false,
        "configurable": true
    }
}
Its instance {
    "public_field": {
        "value": "A public field",
        "writable": true,
        "enumerable": true,
        "configurable": true
    }
}

列挙可能なプロパティを設定する方法

プロトタイプのプロパティである非静的アクセサーを、Object.definePropertyを使用して列挙可能にすることができます。

class Person {
    constructor(name) {
        this.name = name;
    }
    get greeting() {
        return `Hello from ${this.name}.`;
    }
}
for(const property of ["greeting"]) {
    Object.defineProperty(Person.prototype, property, {enumerable: true});
}

しかし、この方法では、Object.keysObject.valuesObject.entriesJSON.stringifyなどの便利な関数のほとんどが、オブジェクトのownプロパティを探します。


プロトタイプのプロパティをインスタンス化する

プロトタイプのプロパティをインスタンスに下げる(コピーする)こともできます。このようにして、プロトタイプからプロパティを継承するのではなく、独自のプロパティとして保持します。

class Person {
        constructor(name) {
                this.name = name;
                for(const property of ["greeting"]) {
                        const descriptor = Object.getOwnPropertyDescriptor(Person.prototype, property);
                        const modified_descriptor = Object.assign(descriptor, {enumerable: true});
                        Object.defineProperty(this, property, modified_descriptor);
                }
        }
        get greeting() {
                return `Hello from ${this.name}.`;
        }
}

const alice = new Person("Alice");
console.log(alice.greeting);
console.log(JSON.stringify(alice));
console.log(Object.entries(alice));

すべての非静的ゲッターをインスタンスに分類し、それらを列挙します。

const prototype = Object.getPrototypeOf(this);
const prototype_property_descriptors = Object.getOwnPropertyDescriptors(prototype);
for(const [property, descriptor] of Object.entries(prototype_property_descriptors)) {
    const is_nonstatic_getter = (typeof descriptor.get === "function");
    if(is_nonstatic_getter) {
        descriptor.enumerable = true;
        Object.defineProperty(this, property, descriptor);
    }
}

あなたはこのようなトリックをするかもしれません:

class Person {
  static createFields({ name }) {
    return {
      name,
      get greeting() {
        return `Hello, I'm ${this.name}`;
      }
    }
  }

  constructor(...args) {
    const inst = this.constructor.createFields(...args)
    const desc = Object.getOwnPropertyDescriptors(inst)
    Object.defineProperties(this, desc)
    return this
  }
}

利点は、プレーンオブジェクトのゲッターはデフォルトで列挙可能で構成可能であるため、毎回これらの修飾子を気にする必要がないことです。

しかし...それはちょっと奇妙に見えます)これが本当に使われるべきかどうかわかりません。

0
jeron-diovis