Can a child class overwrite a private field inherited from a superclass? - javascript

I'm playing around with ES6 classes in JavaScript and I'm wondering if it's possible for a child class to inherit private properties/methods from a superclass, while allowing the subclass to mutate this private property without using any "set" methods (to keep it read-only).
For example, say I want to create a static private property called #className in my superclass that is read-only, meaning you can only read from it using a method called getClassName() and you cannot access this property by doing class.className. Now, I make a new child class that extends this superclass. The child class will inherit #className and getClassName(), but, I would like #className to be initialized with a different value in the child class than in the superclass. So, in the superclass you could have: #className = 'Parent' but in the child class you would have #className = 'Child'.
My only problem is, it seems like you can't really do this. If I try declaring a new #className in the child class, the child class's getClassName() method still refers to the #className from the superclass. Here's the code I was playing with:
class Parent {
#className = 'Parent' // create private className
constructor() {}
getClassName() {
return this.#className; // return className from object
}
}
class Child extends Parent {
#className = 'Child' // re-define className for this child class
constructor() { super(); } // inherit from Parent class
}
new Child().getClassName() // --> this prints 'Parent' when I want it to print 'Child'
Does anyone have a solution to this? Or an alternative that achieves a similar affect?

JavaScript does not support directly accessing private properties inherited from another class, which is how private members are supposed to work. You seem to want the functionality of protected properties. As of 2022, JavaScript does not support protected properties or members of any kind. Why that is, I can't imagine, since other OOP languages have allowed said functionality since time immemorial.
If you have control over the code of the parent class, you can simulate protected properties by using symbols.
const className = Symbol();
class Parent {
[className] = 'Parent'; // create protected [className]
getClassName() {
return this[className]; // return [className] from object
}
}
class Child extends Parent {
[className] = 'Child'; // re-define [className] for this child class
}
console.log(new Child().getClassName()); // --> this prints 'Child'
I'm not sure why this snippet fails in the preview even with Babel. That exact code appears works in the console of every major browser I've tried.
The reason this works is that a Symbol in JavaScript is a primitive type that's guaranteed to be unique. Unlike other primitives, when used as a key in an object, it cannot be [easily] accessed or iterated over, effectively making it protected.
See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
Whether a property is truly "protected" is primarily determined by the scope within which you define the symbol (or whether you pass it around).
For instance, if the above code is module-scoped then that symbol will only be accessible to anything that's within that scope. As with any variable in JavaScript, you can scope a symbol within a function definition, or an if-statement, or any block. It's up to you.
There are a few cases where those symbols can be accessed. This includes the use of Object.getOwnPropertySymbols, Object.getOwnPropertyDescriptors, to name a few. Thus, it's not a perfect system, but it's a step above the old-school way of creating "private" members in JavaScript by prefixing names with an underscore.
In my own work, I use this technique to avoid using private class member syntax because of the gotcha you describe. But that's because I have never cared about preventing that capability in subclasses. The only drawback to using symbols is it requires a bit more code. Since a symbol is still a value that can be passed around, that makes it possible to create "loopholes" in your code for testing purposes or the occasional edge-case.
To truly eliminate leakage of conceptually protected properties, a WeakMap within the scope of the class definitions can be used.
const protectedClassNames = new WeakMap();
class Parent {
constructor() {
protectedClassNames.set(this, 'Parent');
}
getClassName() {
return protectedClassNames.get(this);
}
}
class Child extends Parent {
constructor() {
super();
protectedClassNames.set(this, 'Child'); // re-define className for this child class
}
}
console.log(new Child().getClassName()); // --> this prints 'Child'
A WeakMap is a key-value store that takes an object as a key and will dereference the object when said object has been garbage collected.
As long as the protectedClassNames WeakMap is only scoped to the class definitions that need it, then its values won't be possibly leaked elsewhere. However, the downside is that you run into a similar problem to the original issue if a class that's out of scope of the weak map tries to inherit from one of the classes that uses it.
WeakMaps aren't strictly necessary for this, but its function is important for managing memory.
Unfortunately, there appears to be no proposal in progress for adding protected members to the JavaScript standard. However, decorator syntax combined with either of the approaches described here may be a convenient way of implementing protected members for those willing to use a JavaScript transpiler.

Related

What determines what super.prop refers to in a class?

I'm confused about why exactly super gets set the way it does inside of a class.
In static elements, super.x refers to Parent.x
In methods & field declaration assignments, super.x refers to Parent.prototype.x
MDN says super is either Parent or Parent.prototype, depending on whether the execution context is instance creation or class initialization. To me this makes sense for static elements, where super is seen during class initialization, and for field declarations, where super is seen during instance creation. However, aren't methods evaluated during class initialization, so that they can be put on the class's prototype, and not during instance creation?
aren't methods evaluated during class initialization
No
Methods are not cloned or evaluated, prototype chain is checked when method is not found in instance of the class
class Parent {
doSomething() {}
}
const parent = new Parent()
class Child extends Parent {}
const child = new Child()
console.log(parent.doSomething === child.doSomething)

function and static reserved word in JS class [duplicate]

Let's say I have two ES6 classes like this:
class Base {
static something() {
console.log(this);
}
}
class Derived extends Base {
}
And then I make a call like this:
Derived.something();
Note that I am making a call to a static method defined on the super class via sub class.
This does not give me errors. It prints
[Function: Derived]
So accessing this within a static method seems to work here.
I need a common static method for all sub-classes of a super class and I need to be able to know what sub-class is calling this method.
Now my question is whether using this within a static method is legal. I know these static methods become class methods, and hence this would naturally point to the class object they are called on. (The class object being the constructor.)
But I can't seem to find any definitive resource that states that this is allowed by the ES specification.
This looks like a good introduction to ES6 classes but does not talk about this with static.
Under typical circumstances, the this in any call to something.method() will refer to something as long as the function is not an arrow function, bound function, or something like that (and it is neither of those in this case).
Class inheritance, or even ES6, aren't really relevant here. All you need to know is that you are calling Derived.something(), so this will refer to Derived.
Yes, this is legal in static methods, that's the way this should be done.
this refers to class instance in prototype methods and refers to class constructor in static methods, unless a method was unbound from its original context.
Similarly, super refers to parent class prototype in instance methods and refers to parent class constructor in static methods.
As long as the static method is invoked as a member expression, e.g.
Derived.something();
as opposed to
const { something } = Derived;
something();
then this will refer to Derived. Derived.something() is identical to something.call(Derived) if Derived.something is stored to an intermediate variable, because that's how a member expression with a nested call expression is evaluated, essentially.

Prototype property inside class constructor [duplicate]

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:
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
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?
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" };
})();
}
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.
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.
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}}
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
class MyClass {
constructor() {
MyClass.prototype.prop2 = "Marry";
}
}
const mc = new MyClass()
mc.__proto__ // { prop2: "Marry" }

What is a difference between an object literal and a class with values in constructor in javascript?

I've been working on end to end test in testcafe and in their documentation I found following solution for Page Model:
class Page {
constructor () {
this.nameInput = Selector('#developer-name');
}
}
export default new Page();
I've been doing some research and I cannot get my head around why it is not resolved with an object literal:
export const Page = {
nameInput: Selector('#developer-name');
}
What are consequences of using each of them?
The difference is significant but fundamentally both are JavaScript objects, albeit with different properties and values. Listing all differences on the level of the language would be a long story and you'd be wise to read and understand on JavaScript prototype-based inheritance, but the most important differences are:
Page is a prototype object: whenever you create an object of class Page with new Page(), the constructor function is called with this referring to the object being created, not Page itself. Any property you access on the object is searched along the so-called "prototype chain", including the so-called prototype object. This prototype object can be accessed with Page.prototype and in fact, all methods you define in the class Page are also properties of this prototype object. Unlike own properties designed to refer to unique objects or primitives specific to an object, functions in JavaScript don't have to be bound to an object during object or function creation and can be shared between objects (belonging to the same class, for instance) and are called on the actual instance, not the prototype to which they may belong. In other words, this.nameInput in your constructor actually adds a property named nameInput to the object being created with new Page(), not the prototype, while the constructor itself (constructor) and any non-static methods you might add to Page will be added as properties of Page.prototype. The constructor is accessed as Page.prototype.constructor, by the way, as you'd naturally expect. Page.prototype.constructor === Page evaluates to true, by the way.
An expression of the form like { nameInput: ... } creates an object which prototype is Object.prototype, in practice the most basic form of object with "no prototype" and thus no superclass or any traits beyond what the fundamental object prototype object could provide. Any properties any such { ... } object may seem to have through its prototype chain, including methods, are properties of Object.prototype. This is why you can do ({}).toString() or ({}).hasOwnProperty("foobar") without actually having toString or hasOwnProperty properties in your object -- toString and hasOwnProperty are properties of Object.prototype referring to two distinct methods called toString and hasOwnProperty, respectively, and JavaScript creates a special property on your object called __proto__ referring to Object.prototype. This is how it knows how to "walk the prototype chain". The names of functions themselves do not matter like that, by the way -- I may add a property on an object referring to an anonymous function: var foo = ({}); foo.bar = function() { }; and call said unnamed function with foo.bar().
One mistake you appear to be making is confusing an object of a class with the class, otherwise you wouldn't compare export default class Page { ... } to export const Page = { nameInput: Selector(...) } -- the former creates a class accessible as Page which is used as the prototype object whenever objects of the class are created, while the latter creates an object accessible as Page which contains nameInput referring to result of evaluating expression Selector("#developer-name") (calling Selector with the sole argument "#developer-name"). Not the same thing at all, not to mention that former has Page refer to a class (invariably a prototype in JavaScript), while latter has Page refer to an object that does not seem to fit the pattern of a class.
The interesting things start when you realize that since a class is an object like any other in JavaScript, any object can be used as a class if you know how prototype-based inheritance works:
new (function() { this.nameInput = Selector("#developer-name"); })();
What happens here? You create a new object with an unnamed function as the object constructor. The effect is absolutely equivalent to otherwise creating the object with new Page with Page being your original ES6 class (ECMAScript 6 is the language specification that adds class syntax to JavaScript).
You can also do this, again equivalent to if you defined Page with class Page ...:
function Page() {
this.nameInput = Selector("#developer-name");
}
var foo = new Page();
Page.prototype will be the prototype object for foo, accessible as foo.__proto__ and otherwise making it possible for you to call instance methods on foo like foo.bar(), provided you define bar property on at least Page.prototype:
function Page() {
this.nameInput = Selector("#developer-name");
}
Page.prototype.bar = function() {
console.log(this.nameInput);
}
var foo = new Page();
foo.bar();
In fact, the above is what browser would do internally if it had to interpret the following code:
class Page {
constructor() {
this.nameInput = Selector("#developer-name");
}
bar() {
console.log(this.nameInput);
}
}
It is beyond the scope of my answer to list differences between the two last approaches (isn't the same thing as the two approaches you proposed), but one difference is that with class Page ..., Page is not a property of window in some user agents while with function Page ... it is. It's partly historical reasons, but rest assured that so far defining constructors and prototype using either approach is pretty much the same, although I can imagine smarter JavaScript runtimes will be able to optimize the latter form better (because it's an atomic declaration, and not just a sequence of expressions and statements).
If you understand prototype-based inheritance at the heart of all of this, all your questions about this will fall away by themselves as very few fundamental mechanisms of JavaScript support 99% of its idiosyncrasies. You'll also be able to optimize your object design and access patterns, knowing when to choose ES6 classes, when not to, when using object literals ({ prop: value, ... }) and when not to, and how to share fewer objects between properties.
Classes can be thought of as a blueprint, they both provide an object in the end. But as the object literals name implies, you literally create it there and then with this 'literal' syntax. A class however, we would use to instantiate new instances from 1 base blueprint.
let x = { myProp: undefined }
let y = { myProp: undefined }
x.myProp = "test";
y.myProp // undefined
Here we see we make two separate instances, but we will have to repeat code.
class X { }
let x = new X();
let y = new X();
A class does not need to repeat the code, as it is all encapsulated in the idea of what X should be, a blueprint.
Similar to above [in the literal] we have two separate instances but it's cleaner, more readable, and any change we wish to make to every instance of this 'X' object can now be changed simply in the class.
There's a plethora of other benefits and even a paradigm dedicated to Object-Oriented Programming, read here for more:
https://www.internalpointers.com/post/object-literals-vs-constructors-javascript
To go further into the constructor question... In other languages we have fields.
I believe when you assign a field in the constructor, it just creates an underthehood like field (I say underthehood like because JavaScript is prototype based, and the class syntax is syntactical sugar to help write prototypes easier for programmers familiar with class syntax in other languages).
Here is an example in C#.
public class X{
private int y;
X() {
this.y = 5;
}
}
It's more a convention to assign fields in the constructor in other languages, so I assume this has something to do with it in JavaScript.
Hope this helps.
By declaring it as a Class you can later identify what type of object it is with .constructor.name:
class Page {
constructor () {
this.nameInput = "something";
}
// No comma
anotherMethod() {
}
}
const pageClass = new Page();
const pageLiteral = {
nameInput: "something"
, // must have a comma
anotherMethod() {
}
}
console.log("Name of constructor for class: ", pageClass.constructor.name); // Page
console.log("Name of constructor for literal: ", pageLiteral.constructor.name); // Object

What's the difference between static and instance members?

I want to know what the difference between these two functions at this sample class in javascript?
class Sample{
constructor(){}
sampleOne(){
console.log('this is test')
}
static sampleTwo(){
console.log('this is test too')
}
}
The first one is an instance method. This means, you have to create a new instance of the Sample class to call it:
let instance = new Sample();
instance.sampleOne();
The second one is a static method, so you don't have to have an instance of the class to call it:
Sample.sampleTwo();
Javascript doesn't have classes, therefore explanations in terms of "class" and "instance" are inaccurate. In javascript, there are only objects, which have constructors (functions) and prototypes (other objects). The ES6 "class" syntax is just a decorator, which simply translates java-like class declarations into prototypes, namely:
classes are turned into constructors (functions):
class Sample => function Sample()
ordinary methods are attached to the prototype property of the constructor, and copied to the object.[[Prototype]] later on when you do new Sample():
sampleOne => Sample.prototype.sampleOne
"static" methods are attached directly to the constructor:
sampleTwo => Sample.sampleTwo
Illustration:
static is a declaration to say this method sampleTwo can be called on the class level and not the instance level. sampleOne however, is an instance method and so you would need to instantiate an instance of your sample class.
const test = Sample();
test.sampleOne(); //works
test.sampleTwo(); //won't work cause it is defined on class level
Sample.sampleOne(); //won't work cause not static defined
Sample.sampleTwo(); //works because it is static.
Static methods are not bound to a class instance. But to the class itself. So while convenient you will not have access to this as there is no instance to point to.
Static methods are called on the class itself.
For example, the following would work perfectly
Sample.sampleOne()
// undefined
while
Sample.sampleTwo()
// this is a test two

Categories

Resources