14

I was trying ES6 syntax and find I cannot define prototype property or instance property within class defination, why forbids it?

I was using MyClass.prototype.prop=1 before, try ES7 by babel compiler as below, still cannot define prototype property.

class MyClass{
  prop=1;
  static sProp=1;
}

I don't think define instance property is any dangerous, there's 2 cases in my own browser game need prototype property:

  1. Subclass instances need to inherit same property value from base class:

    var Building=function(){...}
    Building.prototype.sight=350;
    TerranBuilding.CommandCenter=...(CommandCenter extends Building)
    TerranBuilding.Barracks=...(Barracks extends Building)
    

So CommandCenter and Barracks will both have same building sight as 350.

new CommandCenter().sight===new Barracks().sight//All buildings have same sight
  1. Buffer effect override original property and remove buffer

    Marine.prototype.speed=20
    var unit=new Marine()
    unit.speed===20//get unit.__proto__.speed 20
    unit.speed=5//Buffer:slow down speed, unit.speed will override unit.__proto__.speed
    delete unit.speed//Remove buffer
    unit.speed===20//true, speed restore
    

So I think it should add a way to set prototype property instead of forbid it completely, or can you give some other solutions to deal with above 2 cases?

Armen Michaeli
  • 8,625
  • 8
  • 58
  • 95
gloomyson
  • 171
  • 2
  • 5
  • Can you use getters instead? `class MyClass{ get prop() { return 1;} }` – Ruan Mendes Jul 11 '16 at 16:38
  • @JuanMendes There're many properties defined for each unit type, for example size, hp, mp and damage, to write get/set for all these properties are exhausting; and for my case2, I need to override the default property value sometimes because of buffer effect, and can remove that buffer by delete unit.prop to forbid overriding default unit.__proto__.prop, your get/set cannot support this in simple way. – gloomyson Jul 11 '16 at 16:51
  • It's generally considered an anti-pattern to have atomic values on the prototype. The prototype is primarily for methods. Keep atomic values on instances. –  Jul 11 '16 at 18:26
  • 1
    This is not ES7, this is a proposal. – Felix Kling Jul 11 '16 at 19:26
  • @torazaburo I don't know why it would be an anti pattern. Prototype is for anything that is shared by object instances. As I see it, the danger is having mutable objects on the prototype because you can mutate it and that change will be reflected on all instances. If you have a primitive on the prototype, and you do `this.prim = 5`, that will set a value on the object itself, and will not affect the one on the prototype. Please post a link explaining why "it's generally considered an anti-pattern" – Ruan Mendes Jul 11 '16 at 19:32
  • @torazaburo Don't you think all subclass intances share the same default property value is a common user case? – gloomyson Jul 12 '16 at 01:59

6 Answers6

8

Updated Answer (April, 2022)


Just two months after my previous answer, in August of 2021, the static block proposal was moved to stage 4 by the TC-39 committee. See the whole informal list of finished proposals here.

For those looking to get a use case summary of static blocks in Javascript, read the initial publication from the V8 blog from March 2021, after their implementation.

Also, see the MDN documentation for static initialization blocks.

Though most all updated browsers now support this, read below if you really like to support Internet Explorer.


Original Answer


Below is the typical pattern I follow in javascript. Native, no babel, etc..

It mirrors the static-block style that java uses. There is a Stage 3 Proposal open for this right now, and I expect therefor, that it will be standardized in the near future (as is consistent with the stage 3 proposal expectations of the TC-39 committee).

What the proposal will look like

class MyClass {
    static {
        // Any code here is executed directly after the initialization
        // of MyClass. You can add prototype stuff here. The function
        // is called bound to `MyClass`.
    }
}

This can be done today using a static iife

These will function exactly the same way.

class MyClass {
    // Using private properties is not required, it is just an option. Make
    // sure to use an arrow function so that `this` refers to `MyClass`,
    // Note that `MyClass` will still be in the functions closure.
    static #_ = (() => {
        // 'Almost' how functions are typically added. ES6 style
        // is always recommended over this.
        this.prototype.myFunc = function myFunc() {
            console.log(":D");
        };

        // ES6 would actually do this (approximately) so that the function is
        // non-enumerable in the prototype.
        Reflect.defineProperty(this.prototype, "myFunc", {
            // enumerable: false,  // defaults 'false'
            writable: true,
            configurable: true,

            // I'm intentionally not using the shorthand for the function
            // so that it is named 'myFunc'.
            value: function myFunc() {
                console.log(":D");
            }
        });

        // Note that all children of MyClass will refer to this exact
        // object if put in the prototype, i.e. not a copy of it.
        // Also, this property will be non-enumerable on the children
        // (but enumerable on the prototype itself unless you
        // use `defineProperty` as above).
        this.prototype.sharedProperty = { name: "Gerald" };
    })();
}
Hunter Kohler
  • 1,885
  • 1
  • 18
  • 23
  • 1
    The `static` block proposal is now stage 4, so it's already implemented in modern browsers/engines and will be part of ES2022. [MDN docs here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Class_static_initialization_blocks). – Inkling Apr 05 '22 at 06:19
  • 1
    @Inkling Nice. I'll update the answer. – Hunter Kohler Apr 05 '22 at 07:04
7

Neither of those will be on the class prototype.

The class Foo { bar = 1; } syntax will assign a value to the class instance, to be accessed with this.bar.

The class Foo { static bar = 1; } syntax will assign a value to the class constructor, to be accessed with Foo.bar.

There isn't much reason to use the prototype in this case. It will only complicate who actually owns the property and assigning a number in a few different classes will have very little overhead.

I would suggest the class instance property and just use this.sight everywhere you need it.

ssube
  • 47,010
  • 7
  • 103
  • 140
  • 4
    A valid use case is that the OP does want the property to be inherited, your suggestion doesn't help that. That is, they can override it at the object level, and get it back from the prototype by doing `delete obj.shadowingProperty` – Ruan Mendes Jul 11 '16 at 16:35
5

The simplest way to add a property to the prototype inside the class body is by using the prototype assignment as a "value" for a dummy static property:

class MyClass {
    static _dummy = MyClass.prototype.prop1 = <expression1>
    static _dummy = MyClass.prototype.prop2 = <expression2>
    // or
    static _dummy = this.prototype.prop2 = <expression2>
}

(it works without parentheses because = is right-associative, and it's fine to re-use the same dummy property for each prototype assignment)

If you want to do more interesting (multi-line) computation for the values, an initializer can be an immediately-executed function expression, in which case you've basically created a static constructor and you can put all the initializations for the prototype and class object in that.

Hugh Allen
  • 6,509
  • 1
  • 34
  • 44
2

I think the other answer didn't get the point of this question. The whole point of having inheritance is that you can decide when and where to override something. and if anyone think that's an overhead, why does ya use an OOP language at all?

I don't know why it's "forbidden" after all, but I could share some ideas. I'm pretty sure there is no way to define a Prototype Property with 'class' keyword. Any definition will be install on "hasOwnProperty". A huge setback is that in a constructor, there is no way to have any parents' constructors to interact with an overridden property.

To the point of reasoning, it's actually expelled by an other feature: you can use expressions to assign properties to this.

class A extends B { sight = this.getSight() * 3 }

When an expression excuses, it is either run with instance - created with constructor, or run at class declaration - when the prototype is created.

Accessors and methods don't have this problem. They are defined at prototype definition time and called at instance run time.

Property defined with expression with "=" is the return value of the expression. It is excused right after definition - should be the instance creation time, otherwise this could not be available.

So it's nothing about patterns. It's about having expressions or having inheritance. I definitely prefer inheritance, expressions are so pointless when you can write them right into the constructor.

class A extends B { constructor() { this.sight = this.getSight() * 3 } 

Using decorators are a nice work around. You can always do something with the prototype in javascript:

@B({sight:2}) class A {};

decorator B is:

function(option) {return function(clazz) {clazz.prototype.sight = option.sight; return clazz}}
AiShiguang
  • 1,031
  • 1
  • 9
  • 13
2

Using Class static initialization blocks1:

class MyClass {
  static {
    this.prototype.prop = 1;
  }
}

console.log(MyClass.prototype.prop); // 1
const instance = new MyClass();
console.log(instance.__proto__.prop); // 1

this in the static block differs as1:

The this inside a static block refers to the constructor object of the class.

The code inside the static block is executed only once when the class initialization gets evaluated.

Note: Safari doesn't support this feature as of June 2022. You can check the latest info on, for example, mdn web docs1.


[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Class_static_initialization_blocks

catwith
  • 875
  • 10
  • 13
-3
class MyClass {
   constructor() {
    MyClass.prototype.prop2 = "Marry";
  }
}

const mc = new MyClass()
mc.__proto__ // { prop2: "Marry" }
Maksym Dudyk
  • 1,082
  • 14
  • 16