I need to be able to access a parent (not super) class from child classes.
In my case the data structure becomes exactly 3 levels deep:
Class A contains an array with x * Class B containing an array with y * Class C.
instances of Class C need to be able to access properties from Class A.
My idea was to pass down a callback, that would return the needed values.
I can not pass down static values from class A, since they can change over runtime.
Is this the proper way of handling the data flow between my instances, or can this be achieved in a more elegant way?
I set up a fiddle for this case (for the sake of simplicity only 2 levels deep): https://jsfiddle.net/2chngzqg/
class A {
constructor() {
this.foo = "bar";
}
shareProps = () => {
return this.foo;
}
createSubClass() {
this.sub = new B(this.shareProps);
}
}
class B {
constructor(callback) {
this.logShared = callback;
}
}
const a = new A();
a.createSubClass();
console.log(a.sub.logShared())
a.foo = 'baz';
console.log(a.sub.logShared())
How about that?.. In case the already mentioned Cart type contains a list of Product types and each product does list its Option types, and as seen from the example, each type is in control of how to create and/or add other depended types to it's list, then ... just provide this information about the hosting type to the constructor/factory function of the hosted type, thus every option features a product slot/property and every product features a cart slot/property.
The changed code of the OP's provided fiddle then might look like that ...
class Foo {
constructor(host) {
this.foo = "foo";
this.host = host;
}
createMember(Member) {
return (this.member = new Member(this));
}
}
class Bar {
constructor(host) {
this.bar = "bar";
this.host = host;
}
createMember(Member) {
return (this.member = new Member(this));
}
}
class Baz {
constructor(host) {
this.baz = "baz";
this.host = host;
}
createMember(Member) {
return (this.member = new Member(this));
}
}
var foo = (new Foo);
console.log('foo : ', foo);
console.log('foo.foo : ', foo.foo);
console.log('foo.member : ', foo.member);
console.log('foo.host : ', foo.host);
var bar = foo.createMember(Bar);
console.log('bar : ', bar);
console.log('bar.bar : ', bar.bar);
console.log('bar.member : ', bar.member);
console.log('bar.host : ', bar.host);
console.log('bar.host.host : ', bar.host.host);
var baz = bar.createMember(Baz);
console.log('baz : ', baz);
console.log('baz.baz : ', baz.baz);
console.log('baz.member : ', baz.member);
console.log('baz.host : ', baz.host);
console.log('baz.host.host : ', baz.host.host);
console.log('baz.host.host.host : ', baz.host.host.host);
.as-console-wrapper { max-height: 100%!important; top: 0; }
Although not es6 - you can achieve this using functional inheritance.
Using the Object that to hold the class properties. In this example B is instantiated onto the scope of A.
const A = () => {
let that = {}
// Public variable
that.foo = 'bar';
// Public function
that.shareProps = () => (that.foo);
// Subclass functions attached to this classes scope
that.createSubClass = () => {
that = B(that);
}
return that;
}
const B = (that) => {
that.logShared = () => (that.shareProps());
return that;
}
const a = A();
a.createSubClass();
console.log(a.logShared())
a.foo = 'baz';
console.log(a.logShared())
The 'sub classes' can be attached onto other public variables within the function if needs be.
JSFiddle
This is more flexible when you inherit from the 'parent' class in the 'child' classes because you always have access to whatever functions and variables are added to that.
const A = () => {
let that = {}
that.foo = 'bar';
that.shareProps = () => (that.foo);
return that;
}
const B = () => {
// A is inherited
let that = A();
that.logShared = () => (that.shareProps());
return that;
}
const C = () => {
// B is inherited
let that = B();
return that;
}
const c = C();
// Calls the function in B() => that.logShared()
console.log(c.logShared())
// Changes the value in A() => that.foo
c.foo = 'baz';
console.log(c.logShared())
See here for more examples of functional inheritance.
Related
I would like to be able to concatenate strings by creating an object and passing down the constructor, so every time it is called it keeps the references of the previous one. I would like to achieve something like this:
foo = Chain("h")
bar = foo("e")("l")("l")("o")
foo.toString() == "h"
bar.toString() == "hello"
bar.ancestor.toString() == "hell"
What I have so far is a method chaining, when is rather similar but it is not quite what I want to accomplish here. I've been following the following documentation:
Bind
Method binding
Inheritance and prototype chain
function ChainParent() {
}
function Chain(letter) {
this.letter = letter;
this.ancestor = this.letter.substring(0, this.letter.length - 1);
}
Chain.prototype = Object.create(ChainParent.prototype)
Chain.prototype.constructor = Chain;
Chain.prototype.create = function create(letter) {
const letra = this.letter.concat(letter);
return new this.constructor(letra)
}
Chain.prototype.toString = function() {
console.log(this.letter);
}
const foo = new Chain("h");
const bar = foo.create("e").create("l").create("l").create("o");
foo.toString();
bar.toString();
bar.ancestor.toString(); //This won't work
Here's a solution using How to extend Function with ES6 classes? to make the class callable, so there's less boilerplate with the chaining.
In this example, each instance of the class keeps the the letter that it was given to start with, then merges them together using recursion in the toString function. So it acts as a linked list with a reference to the parent only.
// https://stackoverflow.com/a/36871498/13175138
class ExtensibleFunction extends Function {
constructor(f) {
return Object.setPrototypeOf(f, new.target.prototype);
}
}
class Chain extends ExtensibleFunction {
constructor(letter, chain = null) {
super((letter) => new Chain(letter, this));
this.ancestor = chain;
this.letter = letter;
}
toString() {
if (!this.ancestor) {
return this.letter;
}
return this.ancestor.toString() + this.letter;
}
}
const foo = new Chain('h');
const bar = foo('e')('l')('l')('o');
console.log(foo.toString());
console.log(bar.toString());
console.log(bar.ancestor.toString());
const foobar = new Chain('hel');
const barfoo = foobar('ll')('o');
console.log(foobar.toString());
console.log(barfoo.toString());
console.log(barfoo.ancestor.toString());
Here's one possible implementation of chain -
const chain = (s = "") =>
Object.assign
( next => chain(s + next)
, { toString: _ => s }
, { ancestor: _ => chain(s.slice(0, -1)) }
)
const foo = chain("h")
const bar = foo("e")("l")("l")("o")
console.log(foo.toString())
console.log(bar.toString())
console.log(bar.ancestor().toString())
console.log(bar.ancestor().ancestor().toString())
h
hello
hell
hel
Per #IbrahimMahrir's comment, we can make an adjustment to chain to accommodate input strings of any length -
function chain (s = "")
{ const loop = (s = []) =>
Object.assign
( next => loop([ ...s, next ])
, { toString: _ => s.join("") }
, { ancestor: _ => loop(s.slice(0, -1)) }
)
return loop([s])
}
const foo = chain("hh")
const bar = foo("ee")("ll")("ll")("oo")
console.log(foo.toString())
console.log(bar.toString())
console.log(bar.ancestor().toString())
console.log(bar.ancestor().ancestor().toString())
hh
hheelllloo
hheellll
hheell
And here's another implementation of chain I thought about over lunch -
const chain = (s = "", ...ancestor) =>
Object.assign
( next => chain(next, s, ...ancestor)
, { toString: _ => [...ancestor].reverse().join("") + s }
, { ancestor: _ => chain(...ancestor) }
)
const foo = chain("hh")
const bar = foo("ee")("ll")("ll")("oo")
console.log(foo.toString())
console.log(bar.toString())
console.log(bar.ancestor().toString())
console.log(bar.ancestor().ancestor().toString())
hh
hheelllloo
hheellll
hheell
You should just set the ancestor to the current instance (this).
Note that toString is a special method, as it gets called automatically when the JavaScript engine needs to convert an instance to a primitive value. So you should stick to the "interface" for it: it should return the string (not output it).
Here is an implementation of your final block using the modern class syntax. I allows letter to actually be a string with more than one character. The ancestor property will really return what the string is before the most recent extension:
class Chain {
constructor(letter, ancestor=null) {
this.letter = letter;
this.ancestor = ancestor;
}
create(letter) {
return new Chain(this.letter + letter, this);
}
toString() {
return this.letter;
}
}
const foo = new Chain("h");
const bar = foo.create("e").create("l").create("l").create("o");
console.log(foo.toString());
console.log(bar.toString());
console.log(bar.ancestor.toString());
If you don't want a constructor -- so not new -- then the above translates to:
function chain(letter, ancestor=null) {
let that = {
letter,
ancestor,
create(newLetter) {
return chain(letter + newLetter, that);
},
toString() {
return letter;
}
}
return that;
}
const foo = chain("h");
const bar = foo.create("e").create("l").create("l").create("o");
console.log(foo.toString());
console.log(bar.toString());
console.log(bar.ancestor.toString());
If a value of the constant is a pure constant, than it is very easy to define it:
class Foo {
static get bar() { return 123; }
}
However, what if I need to define a constant that needs to be calculated in the constructor?
class Foo {
constructor(m) {
// ...
}
}
let test = new Foo(10);
console.log(test.bar); // OK
test.bar = 1; // ERROR
How can I define bar within the constructor and make this bar accessible as a constant?
The value of this constant is needed many times in other methods. The calculation of this constant value is not trivial, so it would be a waste of resources to calculate it each time when it is needed. On the other hand, I want to ensure that it can't be changed once it is calculated.
UPDATE
Per the discussion in the comments, this can be achieved even more cleanly by simply setting a value property in the config passed to Object.defineProperty() in lieu of a get property; because the writable and configurable properties default to false, this is sufficient in providing the desired behavior outlined in the question:
class Foo {
constructor(m) {
this.m = m;
const bar = m * 100;
Object.defineProperty(this, 'bar', { value: bar });
}
}
let test = new Foo(10);
console.log(test.bar);
test.bar = 300;
console.log(test.bar);
delete test.bar;
console.log(test.bar);
ORIGINAL ANSWER
One option would be to use Object.defineProperty() in the constructor to set a getter for bar, and a setter that prevented its being changed:
class Foo {
constructor(m) {
this.m = m;
const bar = m * 100;
Object.defineProperty(this, 'bar', {
get: () => bar,
set: () => console.warn('bar cannot be set'), // The setter is optional
});
}
}
let test = new Foo(10);
console.log(test.bar);
test.bar = 300;
console.log(test.bar);
How about this
class Foo {
constructor (m) {
this._bar = m + 10; // this is _bar , not bar
}
get bar() {
return this._bar; //_bar is the private methods, which is different from bar
}
}
const foo = new Foo(1);
console.log(foo.bar) // return 11
foo.bar = 0
console.log(foo.bar) // still return 11
You can check this.
class Foo {
constructor(value) {
this._privateValue = value;
}
get bar() {
return this._privateValue;
}
set bar(val) {
console.error("Sorry can't edit me ;) ")
}
}
const test = new Foo(10)
console.log(test.bar)
test.bar = 20;
console.log(test.bar)
I need to extend a singleton class in JavaScript .
The problem that I am facing is that I get the class instance which I am extending from instead of only getting the methods of the class.
I have tried to remove super to not get the instance but then I got an error
Must call super constructor in derived class before accessing 'this' or returning from derived constructor
Code example:
let instanceA = null;
let instanceB = null;
class A {
constructor(options) {
if (instanceA === null) {
this.options = options;
instanceA = this;
}
return instanceA;
}
}
class B extends A {
constructor(options) {
if (instanceB === null) {
super()
console.log('real class is ' + this.constructor.name)
this.options = options
instanceB = this;
}
return instanceB;
}
}
const a = new A({
i_am_a: "aaaaaa"
});
const b = new B({
i_am_b: "bbbbbb"
}) // this change a
console.log(b.options)
console.log(a.options)
So, first of all there's a misconception here:
I have tried to remove super to not get the instance but then I got an error
super() calls the parent class' constructor on the created instance of the child class (i.e. what this is referencing). It does not return a parent class instance. See here for more information.
So, calling super() does not violate the singleton property of the parent class at all. It may well be only constructed a single time if implemented correctly.
With that in mind, you should improve your code a little bit.
A sensible change would be to remove the instance management from the constructors. One solution would be to use static constructors which either create the singleton if no instance exists or return the created instance.
Another one is to drop the arguments to the singleton class constructors. It doesn't really make sense to pass arguments to a class which is supposed to be instantiated once (you're never gonna do anything with the constructor arguments again). You could just make the arguments properties of the singleton right away. Here's a SO answer supporting this point for Java singletons.
A complete example with static constructors and without arguments looks like this:
let instanceA = null;
let instanceB = null;
let counters = { A: 0, B: 0 }; // count class instantiations
class A {
static getInstance() {
if (instanceA === null) {
instanceA = new A();
}
return instanceA;
}
whoami() {
const name = this.constructor.name;
return `${name} #${counters[name]}`;
}
constructor() {
counters[this.constructor.name] += 1;
}
}
class B extends A {
static getInstance() {
if (instanceB === null) {
instanceB = new B();
}
return instanceB;
}
constructor() {
super();
}
}
const a1 = A.getInstance();
const a2 = A.getInstance();
const a3 = A.getInstance();
const b1 = B.getInstance();
const b2 = B.getInstance();
const b3 = B.getInstance();
console.log(a1.whoami());
console.log(a2.whoami());
console.log(a3.whoami());
console.log(b1.whoami());
console.log(b2.whoami());
console.log(b3.whoami());
Note that B inherits whoami from A and that the constructor call counters are never incremented past 1.
Obviously with this approach you can make no guarantee the singleton property holds for each class unless only the static constructors are used to generate instances (since the constructors are still accessible). I think it's a good compromise though.
In JavaScript, a singleton is just an object literal.
const a = {
options: {
i_am_a: "aaaaaa"
}
};
const b = {
options: {
i_am_b: "bbbbbb"
}
};
If you really need a constructor function, you can just write a function that returns an object.
function makeSingleton(options) {
return {
options
}
}
const a = makeSingleton({i_am_a: "aaaaaa"});
const b = makeSingleton({i_am_b: "bbbbbb"});
There's no inheritance chain here, just two object literals. If you absolutely need a class, you can just create one, but it's an unnecessary waste of resources and typing.
class Singleton {
constructor(options) {
this.options = options;
}
}
const a = new Singleton({i_am_a: "aaaaaa"});
const b = new Singleton({i_am_b: "bbbbbb"});
In terms of inheriting, if that's something you really need, you can use Object.create() or Object.assign(), depending on your needs. Be aware that both are shallow - they only work a single layer deep so modifying the child's options property would modify the parent's options property as well.
const a = {
options: {
i_am_a: "aaaaaa"
},
getOptions() {
return this.options;
}
};
const b = Object.create(a);
b.options.i_am_b: "bbbbbb";
a.options.i_am_b; // -> "bbbbbb"
b.getOptions(); // -> { i_am_a: "aaaaaa", i_am_b: "bbbbbb" }
Of course, you could use Object.create() or Object.assign() on the options as well.
To be honest, I think you either need a couple of instances of the same class, or a simple object literal without any inheritance.
const instances = {}
class Singleton {
constructor() {
const instance = instances[this.constructor];
if (instance == null) {
return instances[this.constructor] = this;
}
return instance;
}
}
class Foo extends Singleton {
constructor() {
super();
this.foo = "foo";
}
}
class Bar extends Singleton {
constructor() {
super();
this.foo = "bar";
}
}
const foo1 = new Foo();
const foo2 = new Foo();
const bar1 = new Bar();
const bar2 = new Bar();
console.log(foo1 === foo2, bar1 === bar2, foo1 === bar1, foo1.foo = 123, foo2, bar1);
well i don't know if it the best solution but what i did is to check if the constructor name is different then the class name. if so then i let it create a new instance because that mean i try to extend the class
here is a working example of my test
let instanceA = null;
let instanceB = null;
class A {
constructor(options) {
this.options = options;
if (instanceA === null) {
instanceA = this;
}
if(this.constructor.name !== "A"){
return this;
}
return instanceA;
}
method1(){
console.log(this.constructor.name)
}
}
class B extends A {
constructor(options) {
if (instanceB === null) {
super(options)
instanceB = this;
}
return instanceB;
}
}
const a = new A({
i_am_a: "aaaaaa"
});a
const b = new B({
i_am_b: "bbbbbb"
})
const c = new A({
i_am_c: "ccccc"
});
const d = new B({
i_am_d: "ddddd"
})
console.log(a.options)
console.log(b.options)
console.log(c.options)
console.log(d.options)
a.method1();
b.method1();
c.method1();
d.method1();
I'm creating a basic class in a Node project, and I want to test it using Jest. I'm getting an error that is implying the use of 'strict' mode in the test, which I want to avoid/fix
~/lib/LogRecord.js
module.exports = class LogRecord {
constructor(level, message, timestamp) {
this.level = level;
this.message = message;
this.timestamp = timestamp ? timestamp : Date.now();
}
get level() {
return this.level;
}
get message() {
return this.message;
}
get timestamp() {
return this.timestamp;
}
}
I'm testing it with this:
let LogRecord = require('../lib/logRecord');
describe('Test LogRecord functionality', () => {
test('LogRecord constructor', () => {
let timestamp = Date.now();
let logRecord = new LogRecord('INFO', 'Test Message', timestamp);
expect(logRecord.level).toBe('INFO');
expect(logRecord.message).toBe('Test Message');
expect(logRecord.timestamp).toBe(timestamp);
});
test('LogRecord is read-only', () => {
let timestamp = Date.now();
let logRecord = new LogRecord('INFO', 'Test Message', timestamp);
logRecord.level = 'WARN'
logRecord.message = 'New Message'
logRecord.timestamp = Date.now();
expect(logRecord.level).toBe('INFO');
expect(logRecord.message).toBe('Test Message');
expect(logRecord.timestamp).toBe(timestamp);
});
});
When I run npm test I get the following error on both of the LogRecord tests:
Test LogRecord functionality › LogRecord constructor
TypeError: Cannot set property level of #<LogRecord> which has only a getter
1 | module.exports = class LogRecord {
2 | constructor(level, message, timestamp) {
> 3 | this.level = level;
| ^
4 | this.message = message;
5 | this.timestamp = timestamp ? timestamp : Date.now();
6 | }
at new LogRecord (lib/logRecord.js:3:9)
at Object.test (test/logRecord.test.js:6:25)
Edit - Working class
const data = new WeakMap();
let levelKey = {id:'level'};
let messageKey = {id:'message'};
let timestampKey = {id:'timestamp'};
module.exports = class LogRecord {
constructor(level, message, timestamp) {
data.set(levelKey, level);
data.set(messageKey, message);
data.set(timestampKey, timestamp ? timestamp : Date.now());
}
get level () {
return data.get(levelKey)
}
get message () {
return data.get(messageKey)
}
get timestamp () {
return data.get(timestampKey)
}
}
Testing is about making sure that your code does what you think it does. Consider the following class:
class Foo {
constructor (bar) {
this._bar = bar;
}
get bar () {
return this._bar;
}
}
Here bar is read-only, there is no way to set the bar property:
let foo = new Foo('a foo');
foo.bar; // 'a foo'
foo.bar = 'am not'; // TypeError!
The modules question isn't really relevant: as Logar linked in the comments class bodies are always strict mode irregardless.
So if you want a property to be read only, you don't need to worry about writing it. Workflow might look something like this:
Write empty class Foo class Foo {} and construct an instance foo = new Foo()
Write test that checks for bar which fails because we have an empty class
Add constructor parameter and getter
Check that test now passes
Add test to ensure that trying to set bar throws expected error*
If you don't want read-only properties you can just add a setter:
class Foo {
constructor (bar) {
this._bar = bar;
}
get bar () {
return this._bar;
}
set bar (value) {
this._bar = value;
}
}
In which case you'd add a test that sets bar and the reads the altered value back out.
* You might be wondering why this test is here when this behavior is guaranteed by the spec and I would argue that the test is necessary since someone could (transparently to the callers) refactor the class to be an old-school constructor and create a vector for bugs:
// post refactor Foo
const Foo = function Foo(bar) {
this.bar = bar; // danger! now writable!
};
Hopefully this sort of thing would be caught by a knowledgable reviewer, but I'd write the test anyways.
Update
If what you want is a guaranteed read-only property that you set in the constructor, here is a recipe for such:
const data = new WeakMap();
module.exports = class Foo () {
constructor (bar) {
data.set(this, bar);
}
get bar () {
return data.get(this);
}
};
Because data is not exported no outside code can change it. Attempting to set the bar property of an instance will throw. This is a bit more complicated that just defining an underscore property with getters and setters, but if it's what you want, well... I know this pattern because I've used it.
Update 2
You only create one weakmap per module, not per class or instance. The weakmap stores a unique data entry keyed to individual instances (i.e. this):
const data = new WeakMap();
module.exports = {
Foo: class Foo () {
constructor (bar) {
data.set(this, bar);
}
get bar () {
return data.get(this);
}
},
Bar: class Bar () {
constructor (prop1, prop2) {
// for multiple props we'll store an object...
data.set(this, { prop2, prop1 });
}
get prop1 () {
// ...and retrieve it and access it's props to return
return data.get(this).prop1;
}
get prop2 () {
return data.get(this).prop2;
}
}
};
Note that setting the props with a getter but no setter will still throw...
// in someotherfile.js
const { Foo } = require('path/to/file/with/foo.js');
const foo = new Foo('imma foo');
foo.bar; // 'imma foo'
foo.bar = 'no not really'; // TypeError!
// you can still set random properties that don't have a getter:
foo.baz = 'I do not throw';
foo.baz; // 'I do not throw'
If you want your properties to be read only after object initialization, you can use Object.freeze in the constructor, and remove your getters :
class LogRecord {
constructor(level, message, timestamp) {
this.level = level;
this.message = message;
this.timestamp = timestamp ? timestamp : Date.now();
Object.freeze(this);
}
}
But this will freeze all of your object's properties. You won't be able to modify, remove, or add any after that. Didn't dive too deep into this so it may have some flaws as well
I am using JavaScript prototype chaining technique to chain functions as shown below:
var foo = (function () {
function fn(arg) {
if (!(this instanceof fn)) {
return new fn(arg);
}
this.arg = arg;
return this;
}
var func = function (element) {
return fn(element);
};
fn.prototype = {
bar: function () {
return this;
}
}
func.functions = fn;
return func;
}());
I would like to know how to access fn.prototype so I can add more functionality to the foo prototype outside its closure.
If I just simply do as follows, it won't work:
foo.prototype.baz = function () {
alert(this.arg);
}
foo("hello").baz();
However if fn assigned to the foo (func.functions = fn;) as it shown in the foo private closure I can do as follow and it will works:
foo.functions.prototype.baz = function () {
alert(this.arg);
}
foo("hello").baz();
Is there any other way to achieve this?
I think you are un-necessarily overcomplicating this. You can chain by simply doing this:
const foobar = function(){return this} // Initialize a new Object
const foo = text => {
const me = new foobar()
me.text = text
me.bar = a => (alert(me.text+": "+a), me)
return me
}
foo('A').bar('Test').bar('Test chained')
// Update the foobar class with baz
foobar.prototype.baz = function() {alert('BAZ worked!');return this}
foo('B').bar('1').baz().bar('2')
Note: Click Run code snippet to see the output
That's it!
Edit:
You can also do this with ES6 classes like:
class foobar {
constructor(text) {
this.text = text;
}
bar(a) {alert(this.text+": "+a);return this}
}
const foo = text => new foobar(text)
foo('A').bar('Test').bar('Test chained')
// Update the foobar class with baz
foobar.prototype.baz = function() {alert('BAZ worked!');return this}
foo('B').bar('1').baz().bar('2')