How I understand inline caches, there is no check for "or further down in the hidden class tree" (would be costly to do without having some trick). Even though an instance property from a base class would always be at the same offset, an access of a base class property encountering many different subclass instances would mean its inline cache gets overwhelmed (or does it? Perhaps I am wrong here already, strongly typed languages usually do cover this case, perhaps there is a trick being used?). This also would apply to objects in general.
This left me wondering: instead of subclasses, wouldn't it sometimes be better, to have two objects, one with the base properties, one with the subclass properties, both having a reference to the other?
Class extension version:
class A {
x = 0;
}
class B1 extends A {
y = 0;
}
class B2 extends A {
z = 0;
}
const b1 = new B1();
const b2 = new B2();
const f = (p: A) => { p.x; };
// Different hidden classes.
f(b1);
f(b2);
Linked objects version:
class A<T> {
x = 0;
extension: T;
constructor(extension: T) { this.extension = extension; }
}
class B1 {
a: A<B1>;
y = 0;
constructor() { this.a = new A(this); }
}
class B2 {
a: A<B2>;
z = 0;
constructor() { this.a = new A(this); }
}
const b1 = new B1();
const b2 = new B2();
const a1 = b1.a;
const a2 = b2.a;
const f = <T,>(p: A<T>) => { p.x; };
// Same hidden class.
f(a1);
f(a2);
Does this have any tangible performance impact? I guess the answer is always "don't care until you measured it's your bottleneck", but I am left wondering.
In short: Yes.
Subclassing can lead to excessive polymorphism when an engine uses hidden classes and equality comparisons on these hidden classes (like V8 does). (And for the record, that doesn't imply that hidden classes are a bad idea -- they just happen to have both benefits and limitations, like any engineering technique.)
Modifying your object layout can be a good way to avoid that. How exactly to do that depends a lot on your requirements. You may or may not need the bidirectional links; e.g. a variant that might be good enough to address some common usage scenarios is:
class A {
x = 0;
extension;
constructor(extension) { this.extension = extension; }
}
function MakeB1() {
return new A({y: 0});
}
function MakeB2() {
return new A({z: 0});
}
// And then the rest is very similar to your first example, just
// with "MakeB1" instead of "new B1":
const b1 = MakeB1();
...
f(b1);
You may want to include a type property in class A, then you can do things like
if (a.type == B1) ProcessB1(a) else if (a.type == B2) ProcessB2(a);.
Does this have any tangible performance impact?
It might, sometimes...
I guess the answer is always "don't care until you measured it's your bottleneck"
...absolutely. Many/most apps don't need to care, but for certain performance-sensitive code, optimizations like these might help quite a bit. Only profiling can tell you which parts of your app might be worth optimizing.
Related
question
How to set an object property value to its own variable name (during initialization if possible)?
eg
For example, to create a Enum (inside class AA) in Javascript:
class AA {
static Color = {
Red: 'Red',
Green: 'Green',
Blue: 'Blue',
}
}
I have to repeat the String name everytime.
Is there a simpler way to do this, something like eg:
class AA {
static Color = {
Red: this.currentProperty.name,
Green: this.currentProperty.name,
Blue: this.currentProperty.name,
}
}
requirements (not mandatory) & comments
Please make this As Simple As Possible (dont be complicated/cumbersome).
(The hardest requirement. It would be best in the form shown above (during initialization).
I may be a bit picky on this... (I know this is very subjective, and such thing may not even exist)
though, any other thoughts are still welcome)
The variable value & variable name can be refactored at the same time -- without the need to change them separately.
It doesnt have to be an Enum (-- this topic is not limited to Enum only, a normal object will do)
Try to use Es6+
Try to let Jsdoc able to recognize this as an Enum (maybe the use of #emun (?<)), (mainly for autocompletion / Type hint on Vscode)
Try to let Debugger able to recognize this as an Enum & able to view the value as a meaningful string
Im aware of there are some Enum lib in github eg, not sure they are good enough / fit my style.
Im aware of the use of Symbol() on Enum
Im aware of need to make Enum immutable (private + getter / Object.freeze)
I dont think Object.keys() can help. (too cumbersome?)
class AA {
static Color = Object.fromEntries(['Red','Green','Blue'].map(i=>[i,i]))
}
console.log(AA.Color)
or, with a helper method:
function makeEnum(...props) { return Object.fromEntries(props.map(i=>[i,i])) }
class AA {
static Color = makeEnum('Red','Green','Blue')
}
console.log(AA.Color)
this might help with autocompletion:
function makeEnum(obj) { return Object.fromEntries(Object.keys(obj).map(i=>[i,i])) }
class AA {
static Color = makeEnum({Red:'', Green:'', Blue:''})
}
console.log(AA.Color)
or using a proxy:
function makeEnum() {
let o = {}, p = new Proxy(o, {get:(_,i)=>(i==='enum'?o:(o[i]=i,p))})
return p
}
class AA {
static Color = makeEnum().Red.Green.Blue.enum
}
console.log(AA.Color)
including Object.freeze() to prevent reassignment:
function makeEnum() {
let o = {}, p = new Proxy(o, {get:(_,i)=>
(i==='enum'?(Object.freeze(o),o):(o[i]=i,p))})
return p
}
class AA {
static Color = makeEnum().Red.Green.Blue.enum
}
console.log(AA.Color)
AA.Color.Red = 'Yellow'
console.log(AA.Color)
another proxy variant: the new keyword is used to trigger freezing of the object:
function Enum() {
let o={}, p = new Proxy(function() {}, {
construct: () => (Object.freeze(o),o),
get:(_,i)=>(o[i]=i,p)
});
return p;
}
class AA {
static Color = new (Enum().Red.Green.Blue)
}
console.log(AA.Color)
console.log(AA.Color.Red)
AA.Color.Red = 'Yellow' // frozen object can't be changed
console.log(AA.Color.Red)
AA.Color.Orange = 'Orange' // frozen object can't accept new properties
console.log(AA.Color)
How I understand inline caches, there is no check for "or further down in the hidden class tree" (would be costly to do without having some trick). Even though an instance property from a base class would always be at the same offset, an access of a base class property encountering many different subclass instances would mean its inline cache gets overwhelmed (or does it? Perhaps I am wrong here already, strongly typed languages usually do cover this case, perhaps there is a trick being used?). This also would apply to objects in general.
This left me wondering: instead of subclasses, wouldn't it sometimes be better, to have two objects, one with the base properties, one with the subclass properties, both having a reference to the other?
Class extension version:
class A {
x = 0;
}
class B1 extends A {
y = 0;
}
class B2 extends A {
z = 0;
}
const b1 = new B1();
const b2 = new B2();
const f = (p: A) => { p.x; };
// Different hidden classes.
f(b1);
f(b2);
Linked objects version:
class A<T> {
x = 0;
extension: T;
constructor(extension: T) { this.extension = extension; }
}
class B1 {
a: A<B1>;
y = 0;
constructor() { this.a = new A(this); }
}
class B2 {
a: A<B2>;
z = 0;
constructor() { this.a = new A(this); }
}
const b1 = new B1();
const b2 = new B2();
const a1 = b1.a;
const a2 = b2.a;
const f = <T,>(p: A<T>) => { p.x; };
// Same hidden class.
f(a1);
f(a2);
Does this have any tangible performance impact? I guess the answer is always "don't care until you measured it's your bottleneck", but I am left wondering.
In short: Yes.
Subclassing can lead to excessive polymorphism when an engine uses hidden classes and equality comparisons on these hidden classes (like V8 does). (And for the record, that doesn't imply that hidden classes are a bad idea -- they just happen to have both benefits and limitations, like any engineering technique.)
Modifying your object layout can be a good way to avoid that. How exactly to do that depends a lot on your requirements. You may or may not need the bidirectional links; e.g. a variant that might be good enough to address some common usage scenarios is:
class A {
x = 0;
extension;
constructor(extension) { this.extension = extension; }
}
function MakeB1() {
return new A({y: 0});
}
function MakeB2() {
return new A({z: 0});
}
// And then the rest is very similar to your first example, just
// with "MakeB1" instead of "new B1":
const b1 = MakeB1();
...
f(b1);
You may want to include a type property in class A, then you can do things like
if (a.type == B1) ProcessB1(a) else if (a.type == B2) ProcessB2(a);.
Does this have any tangible performance impact?
It might, sometimes...
I guess the answer is always "don't care until you measured it's your bottleneck"
...absolutely. Many/most apps don't need to care, but for certain performance-sensitive code, optimizations like these might help quite a bit. Only profiling can tell you which parts of your app might be worth optimizing.
I am working on a "manager" for selecting what crop shall be placed on a certain plot. Every crop has a completely different design and therefor their own class/object. However instead of writting >40 different lines that will instantiate that class I would like 1 line that simply contains the string that matches the exact name of a class and then run it. That way my code will stay clean. I have tried some stuff but never managed to get it done. Usually resulting in an the following error:
TypeError: this.crop is not a constructor
The code I am attempting to run
export default class CropManager extends Phaser.Group {
constructor (game, className, plotId) {
super(game)
this.x = 0
this.y = 0
this.plotId = plotId
this.className = className
this.cropHandler(this.className)
}
// Defines which class to call
cropHandler (className) {
const ActualClass = 'plot' + className
this.cropclasses = { ActualClass: ActualClass}
this.crop = this.cropclasses[ActualClass]
this.classRun = new this.crop(this.game, this.x, this.y, this.plotId)
this.add(this.classRun)
}
}
Note every crop their classname = crop+cropname (cropCarrots, cropCows, etc)
Rethink the way you're storing key-value pairs in this.cropclasses. The way it's done now, it's going to have 'ActualClass' as the key and 'plotNameOfTheClass' (or whatever 'plot' + className produces) as the value, thus, when accessing it later as an array, this.crop comes out undefined since there isn't a 'plotNameOfTheClass' key in the map.
class SomeClass {
x: 5;
y = 10;
}
const c = new SomeClass();
alert(c.x + ' : ' + c.y);
Why is the code compilable but the value of c.x is undefined?
What is the effect of declaring a class property with :?
Regarding the x: 5 part, although this is a valid javascript code, there is no much use for it.
This is a javascript label and it used (if any) mostly within loops context.
So to answer your questions:
Why is the code compilable
Because technically this is a valid javascript code (yet not a valid class field).
but the value of c.x is undefined
Because the x is a label and not a class field.
What is the effect of declaring a class property with :
You get a label instead of a class field.
Addendum
Another common mistake, is this code of block:
class SomeClass {
z = () => {
x: 5;
};
}
You would think that z() will return an object with an x key:
`{x:5}`
But actually you have a function with a label of x that just run an expression of 5.
Just for completeness sake, the fix will be either to add an explicit return and another set of curly braces
() => {return {x: 5}}
Or just wrap the whole thing with parentheses
() => ({x: 5})
Edit
As a followup to the comments below:
Just to be clear, your code compiles on several environments that i tested as well as stack-snippets as can be seen below:
class SomeClass {
x: 5;
y = 10;
}
const c = new SomeClass();
console.log(c.x + ' : ' + c.y);
The code is not valid ES6.
You seem to be "compiling" with babel, and have inadvertently enabled the flow syntax extension (and also class properties for the second line). In flow, x: 5 is a class field type annotation. Of course, 5 as a type doesn't make sense, but apparently they allow pretty arbitrary expressions.
I have only really looked at python before where there is one way to do anything, javascript is really hard to get an order of how to do things for me.
For example, in the elequentjavascript teaching series, one of the module tests was to output the average ages of each century from an 'ancestry' object. My answer was vastly different from the websites, is it just experience which shows you the correct way?
Website solution:
function average(array) {
function plus(a, b) { return a + b; }
return array.reduce(plus) / array.length;
}
function groupBy(array, groupOf) {
var groups = {};
array.forEach(function(element) {
var groupName = groupOf(element);
if (groupName in groups)
groups[groupName].push(element);
else
groups[groupName] = [element];
});
return groups;
}
var byCentury = groupBy(ancestry, function(person) {
return Math.ceil(person.died / 100);
});
for (var century in byCentury) {
var ages = byCentury[century].map(function(person) {
return person.died - person.born;
});
console.log(century + ": " + average(ages));
}
My attempt:
function displayCenturyAges(array, centuryStart, centuryEnd) {
var ages = 0;
var tempObject = {};
for (var i = centuryStart; i <= centuryEnd; i += 100) {
tempObject[i] = array.filter(function (people) {
return (people.died - i) < 100 && (people.died - i) > 0;
}).map(function (people) {
return people.died - people.born;
});
}
for (var key in tempObject) {
ages = 0;
if (tempObject.hasOwnProperty(key)) {
tempObject[key].forEach(function (elements) {
ages += elements;
});
console.log(key + " average age: " + ages / tempObject[key].length);
}
}
}
I would say "good" Javascript is a combination of clarity and optimization, which is dependent on the type of project you are working on. If you are writing code for an enterprise company, then the code should have optimization as its primary concern, which may sacrifice clarity, but good documentation should take care of that. If you are working with a group of friends on a fun project, then I would say not to worry too much about optimization, but make sure your code is clearly understandable so that other people know what is going on. I hope this helps!
In the world of Software, never exist a "correct way to do" something, exist many ways to solve a problem, it depends of each person, and every one that works, can be applied to a correct answer.
When we talk about which one works better, we entered on another face to the software.
I called: "quality of software development proccess" and "Software engineer", those subjects talk about a better way to developt a software.
It depends on your target it have many ways to make your code and software better, some examples:
Clearity:
Software standards (you can make your own standard)
Design patterns (Singletone, Factory, EAV, Observe, etc...)
Models (MVC model, HMVC, view oriented).
Optimization:
Algoritms and data structures
RUn time Optimization
User interface designs
DB Query optimization
Quality:
Implements process models
Test cases
So exist one correct way?, no, exist many ways to solve it, Which is better? deppends on your approach, which is correct? every one that works and fit to your espectations.