Setting an ES6 class getter to enumerable

半世苍凉 提交于 2019-11-28 08:09:40

ES6 style getters are defined on the prototype, not on each individual person. To set the greeting property to enumerable you need to change:

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

To:

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

Object.keys only returns that object's own enumerable properties, so properties on the prototype are not returned. You will find the greeting property in Object.keys( Object.getPrototypeOf( person ) ), or in a for...in loop. Updated Plunker

If instead, you want each individual instance of Person to have its own greeting you can define it in the constructor:

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

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

Updated Plunker

You may do trick like this:

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

Benefit is that getters on plain object are enumerable and configurable by default, you don't have to care about these modifiers every time.

But... it looks kinda weird) Not sure whether this should be really used.

What is a class?

Non-static methods and accessors of a class lie on the prototype of the class so that every instance of it inherits them. You can access them through the instances, but they are not the own properties of the instances. Static methods and accessors lie on the class (which is a function) itself.

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

How to set properties enumerable

You can make non-static accessors, which are properties on a prototype, enumerable using 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});
}

But this way it’ll be mostly of no use, as most of the useful functions, such as Object.keys, Object.values, Object.entries, JSON.stringify, et cetera, do only look for own properties of objects.


Bringing properties on a prototype down to instances

You can also bring (copy) properties on a prototype down to instances. This way they no longer inherit the properties from the prototype, but have them as their own properties.

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

Bringing every non-static getter down to instances, enumerablizing them.

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);
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!