-
Notifications
You must be signed in to change notification settings - Fork 12.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Declare static non-method members within class' prototype #3743
Comments
class SomeClass {
CONSTANT: number;
showPrototype() { alert(this.CONSTANT); return this; }
}
SomeClass.prototype.CONSTANT = 10;
new SomeClass().showPrototype(); // Displays 10 |
Thx for the attempt but you've removed |
Nope, instantiate SomeClass 1000 times and you only get 1 property. Describe a non-method prototype member just as you would describe a method prototype member, the way I've shown. |
@LordJZ 's suggestion is correct. Putting non-method members on the prototype is usually a bad idea; we likely won't add special syntax for this unless there's some compelling reason to do so. The approach of setting it manually immediately after the class declaration is a good solution for people who really know what they're doing. |
class SomeClass
SomeClass.TS_CONSTANT = 10
CS_CONSTANT: 20
showStatic: -> alert('TS_CONSTANT: ' + SomeClass.TS_CONSTANT); @
showPrototype: -> alert('CS_CONSTANT: ' + @CS_CONSTANT); @
new SomeClass().showStatic().showPrototype() It emits the following JS: var SomeClass;
SomeClass = (function() {
function SomeClass() {}
SomeClass.TS_CONSTANT = 10;
SomeClass.prototype.CS_CONSTANT = 20;
SomeClass.prototype.showStatic = function() {
alert('TS_CONSTANT: ' + SomeClass.TS_CONSTANT);
return this;
};
SomeClass.prototype.showPrototype = function() {
alert('CS_CONSTANT: ' + this.CS_CONSTANT);
return this;
};
return SomeClass;
})();
new SomeClass().showStatic().showPrototype(); |
I don't understand why you'd care, from a type system perspective, whether a property is initially defined in the prototype or not. It seems more like the kind of thing you'd put in the documentation. What kinds of things would be errors for a prototype-defined property that wouldn't be an error for a non-prototype property (or vice versa)? |
|
Although this issue has unfortunately already been closed I'd like to point out on the discussion at [1] for reference. In this discussion "DerCapac" proposed to introduce a special keyword like My main concerns why I'd vote for a keyword are readability and verbosity: IMHO the workaround suggested by @LordJZ or @ahejlsberg [1] to write In terms of verbosity I found that it is not enough to write MyClass.prototype.foo = "bar" but one has also to declare the property on the class itself to type it. Yet one must be aware not to accidentally initialize the property because it would create an own property which "hides" the prototype property initialized outside the class. This way what we write at the top and inside a class declaration may be driven by a statement which was written at the bottom and outside of it. Is this really favourable? Example:
Its apparent that something like
would be much more concise. @RyanCavanaugh I would also be interested to know why we should care about the issue from a type system perspective, but personally I would like to see more compelling arguments where this JS concept is fundamentally conflicting with a type system. I don't think it does because the TS compiler can already handle it properly when using the verbose syntax. Put differently, why are static properties so much more interesting from a type system perspective that they have gotten their own keyword? Instead we similarily could write I think the actual point is more about language design and why there is syntactic sugar for certain concepts whereas it is refused for others. Refusing a keyword because there are consequences which might be unfamiliar to people coming from statically typed languages is reasonable but IMHO also very debatable. I'd like to argue that the proposed workaround makes it harder for these people to explore the benefits and pitfalls of this particular JS concept whereas a keyword would provide a clear name to the concept and help writing less verbose, more readable code. |
I feel this issue was unfairly & abruptly "replied + closed" w/o much thought. @devpunk, excellent 2-year-old discussion dig btW: https://typescript.CodePlex.com/discussions/444777 I was motivated to open this issue here b/c I was making a ".d.ts" file for some JS library. Until then, I've finally found out a much better actual workaround. However, it demands that we have 2 extra interfaces. The interface outside the namespace has the same name as the class and extends the internal interface. However, we can't initialize them aFaIK b/c they're described inside an interface. =( class SomeClass {
__proto__: this;
showProtoConst() { alert(this.CONSTANT); return this; }
}
interface SomeClass extends SomeClass.prototype {}
namespace SomeClass {
export interface prototype {
CONSTANT: number;
}
}
class SubClass extends SomeClass {}
SomeClass.prototype.CONSTANT = 10;
const val = new SomeClass().showProtoConst();
console.info(val.CONSTANT);
SomeClass.prototype.CONSTANT = 20;
console.info(val.CONSTANT);
const sub = new SubClass().showProtoConst();
console.info(sub.CONSTANT);
SomeClass.prototype.CONSTANT = 30;
console.info(sub.CONSTANT);
SubClass.prototype.CONSTANT = 40;
console.info(sub.CONSTANT);
console.info(val.CONSTANT); // still 30 though. |
@GoToLoop I don't think they wanted to be unfair, but simply got a lot more issues to deal with. I share your opinion that the topic deserved more discussion. The underlying problem of setting prototype properties might not even be specific to TypeScript but may require a solution in ES6 classes as well. As far as TypeScript is concerned the answers I could find so far may be best summed up as we don't want to give fame to a JavaScript concept which we consider bad practice. I think its a valid argument but I tried to show why it might eventually turn out to contradict the good intentions they actually have in mind. I am not sure if I fully understand why you didn't declare a regular instance variable thats initialized outside the class {}-block. Without explicit initialization it would remain (by the way: funny avatar :) |
Sorry I don't get what you mean? The only instance variable from class SomeClass is Variables val & sub refer respectively to instances of class SomeClass & SubClass. |
|
Sorry, I used a bit awkward language here. I thought about using @LordJZ's proposal but forgot that you weren't satisfied with it because
Note "true nature as class variable" in the sense of Java is not possible to achieve in JavaScript. The way it is currently solved in TS is what comes most closely to class variables (at the cost of inheritance and
With a Java background you may not used to the fact that when assigning a value to a static attribute using Example: class MyClass() {
mySemiStaticVar:string;
read() {
console.log(this.mySemiStaticVar);
}
write(value:string) {
this.mySemiStaticVar = value;
}
}
MyClass.prototype.mySemiStaticVar = "Hello"; // the non-method prototype member
// Create instances
var inst1 = new MyClass();
var inst2 = new MyClass();
console.log(inst1.hasOwnProperty("mySemiStaticVar")); // false
inst1.read(); // Hello (looked up along prototype chain)
console.log(inst2.hasOwnProperty("mySemiStaticVar")); // false
inst2.read(); // Hello (looked up along prototype chain)
inst2.write("World"); // inst2 now gets its own mySemiStaticVar
console.log(inst2.hasOwnProperty("mySemiStaticVar")); // true
console.log(inst1.hasOwnProperty("mySemiStaticVar")); // false
inst1.read(); // Hello (looked up along prototype chain)
inst2.read(); // World! (looked up from own property)
console.log(inst2.constructor.prototype.mySemiStaticVar) // What do you expect? If you write a variable with This is where your example becomes a bit hard to talk about because you unfortunately call your prototype member variable CONSTANT which suggests not to talk about the write case. However the write case matters to prototype properties and static variables per sé are writable, too. |
The OOP notion of class variable is 1 solo
As I've mentioned before, the way When we create an object in Java, only non- Every Java object got some extra data too, including the reference for the original class. When we invoke a Java method, Java doesn't even bother looking for it inside the object. Of course other JS class places apart from |
I was adapting from my 1st example which already had a class variable called CONSTANT.
I was very aware of that. As much as I only used the assignment operator accompanied by Assignments over In short, we can only read from a P.S.: Your hasOwnProperty() example emit errors in TS b/c you haven't declared mySemiStaticVar as an instance variable as well! Either apply my hack or @LordJZ '. O_o |
Go to https://www.CompileJava.net/ in order to paste, compile & run the Java example below. Java Example:public class SomeClass {
int instance_var = -99; // it's cloned and goes to "this".
//static ts_static_var = -1; // no exact correspondence in Java!
static int static_var = 10; // stays in the class' "prototype".
public static void main(String[] args) {
final SomeClass some = new SomeClass();
System.out.println(SomeClass.static_var); // "prototype" direct access.
SomeClass.static_var *= 3; // "prototype" direct assignment.
System.out.println(some.static_var); // "prototype" access via "this".
// We can't direct use any assignment operators in JS via "this"
// For it creates an "own" property on-the-fly
// Rather than changing the 1 inside the prototype
// But since Java can't create fields on-the-fly
// It infers we wanna change the static "prototype" field:
some.static_var -= 5; // changes "prototype" in Java but "this" in JS!
System.out.println(some.static_var); // prints out 25
System.out.println(SomeClass.static_var); // 25 in Java but still 30 in JS
}
} TS Example:class SomeClass {
instance_var = -99; // it's cloned and goes to "this".
static ts_static_var = -1; // no exact correspondence in Java!
}
interface SomeClass extends SomeClass.prototype {}
namespace SomeClass {
export interface prototype {
static_var: number; // stays in the class' "prototype".
}
}
SomeClass.prototype.static_var = 10;
const some = new SomeClass;
console.info(SomeClass.prototype.static_var); // "prototype" direct access.
SomeClass.prototype.static_var *= 3; // "prototype" direct assignment.
console.info(some.static_var); // "prototype" access via "this".
// We can't direct use any assignment operators in JS via "this"
// For it creates an "own" property on-the-fly
// Rather than changing the 1 inside the prototype
// But since Java can't create fields on-the-fly
// It infers we wanna change the static "prototype" field:
some.static_var -= 5; // changes "prototype" in Java but "this" in JS!
console.info(some.static_var); // prints out 25
console.info(SomeClass.prototype.static_var); // 25 in Java but still 30 in JS |
You're right. I have updated my example which I wrote in plain JS in a Chrome Console and tried to properly adapt it to TS syntax. Well, you see why I support a more convenient syntax. ;-).
If that's your premise I'd agree, that's possible to achieve. But its another question if it's wise to employ the prototype to achieve it.
From access I entailed you mean read and write access over Said this, I still think it would be nice to have better syntax for members on prototypes, not for resembling a concept from other languages but for the reasons outlined earlier. *AFAIK you even get a warning in some IDEs like e.g. Eclipse if you try do the same in Java. |
I'm not asking for TS to have 100% safe-guards against "shared" re-assignments. So it's crystal clear for the former when we attempt to re-assign some But that's ambiguous for JS! And a brand new instance property is born, canceling the "shared" 1 for that particular instance, when that happens. Nonetheless, I believe it wouldn't be that hard to protect against creating instance variables outta static 1s inside classes. Since they would be properly identified as
Warnings aren't errors and I did the Java example from Notepad2-mod! ;-) |
I'm just starting to use TypeScript to convert from a large CoffeeScript code base to gain the advantages of the type system. That being said, just as demonstrated by GoToLoop earlier, this construct is easily available in CoffeeScript and thus also used frequently. In the codebase I'm converting it is used in most classes for defining default values, and for storing metadata that belongs to the class of the instance (the "static" scenario, without having to access the constructor property to get them). Taking the example from the TS spec https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#841-member-variable-declarations with less fields but with an added instance method we have the following:
Which is equivalent to:
But looking at the source, the behavior looks inconsistent; the member fields are handled differently from the member methods. What I expected when I was converting the CoffeeScript classes was that they would behave in a consistent way, e.g. the field would only be initialized in the constructor when that was actually coded that way, and otherwise on the prototype like that (desired JS output):
This also has runtime performance implications; having the initializations on the prototype makes it so that creating new instances requires less code to run (especially in classes with several fields what should have an initial value) and it may also reduce their memory footprint since no memory needs to be allocted per instance for the properties on the protoype chain. Now I understand that given the current state this is not going to be changed, but there should at least be support for it without the explicit assignment after creation of the object. A keyword would be great, maybe like this:
I'd propose the "default" keyword followed by an expression, since this even allows to keep both in the same line (as in I'm not sure why this is supposed to be a bad practice in general, at least not for types which are immutable (primitives, strings). The compiler could still emit a warning when one uses this syntax for arrays or other mutable objects. |
@avonwyss
Transpilation of TS members vs. methods might seem inconsistent when thinking of methods as just being properties, too. But it is quite consistent with OOP if you think of state and behavior instead, where members reflect state and function properties are behavior. Most commonly you are likely to have instances of a class to have their own state (-> OwnProperties) but share common behavior (-> Proto.-Prop.) so it makes sense to treat members and methods differently. With
I think in terms of (syntactic) consistency all modifiers should be before the actual property name. Further I don't see why you would like to omit the assignment operator. So for the sake of syntactic consistency I think class Employee {
public name: string;
public address: string;
public default retired: boolean = false;
public test() {}
} would be better, yet I think default is not optimal. For example, as you pointed out, member initializations without this special keyword translate to constructor() {
this.retired = false;
} which can be read as: any new instance of Employee will have [1] Douglas Crockford: JavaScript - The good parts; O'Reilly. 2008. Chapter 4 (Augmenting Types) |
@devpunk , thanks for your thoughts. Regarding the inconsistency, one of the patterns which is also used in our code base is to have "abstract" methods on non-abstract classes, e.g. the code performs a falsyness check before invoking them. Translated to TS, this would be something like this:
Since abstract methods can only appear in abstract classes, I cannot use the abstract keyword, yet since I cannot define them properly on the protoype I also cannot use a construct like the code above. Frankly, even though I'm coming from a statically typed language background, the way this has been implemented is a leaky abstraction and it feels like a hack.
Because my suggestion is in fact to use an alternative assignment operator to set the prototype property value, and not a modifier per se. In the end, when reading values, it does not make a difference, and it is already so that fields defined in a class are permitted on its prototype. That being said, there may even be sitations where you actually want both assignments. |
I think the pattern you describe should be discussed separately because this issue is about "non-method prototype members". The compile error you get is about redefining a member property as a method: "Class 'a' defines instance member property 'x', but extended class 'b' defines it as instance member function" Even though: you might be able to apply a similar workaround to get around the compile error (disclaimer: the workaround only works if you don't need access to
|
You can do it like this. class SomeClass { new SomeClass().showStatic().showPrototype(); // Displays 10, then undefined. |
This TS code for class declaration :
Transpiles into the following JS code:
Playground link:
http://www.typescriptlang.org/Playground#src=class%20SomeClass%20%7B%0D%0A%20%20static%20CONSTANT%20%3D%2010%3B%20%2F%2F%20Wanna%20insert%20it%20as%20a%20prototype%20member!%0D%0A%20%20showStatic()%20%7B%20alert(SomeClass.CONSTANT)%3B%20return%20this%3B%20%7D%20%2F%2F%20OK...%0D%0A%20%20showPrototype()%20%7B%20alert(this.CONSTANT)%3B%20return%20this%3B%20%7D%20%20%20%2F%2F%20Error!%0D%0A%7D%0D%0A%0D%0Anew%20SomeClass().showStatic().showPrototype()%3B%20%2F%2F%20displays%2010%20then%20undefined.
Does TS have any syntax to insert static non-method properties into their class' prototype?
So they can be accessed w/
this
and be inheritable just like methods are?If not, do you plan on add this useful feature or have some workaround?
The text was updated successfully, but these errors were encountered: