Calculate and define a constant in class constructor - javascript

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)

Related

what is the difference between symbol and weakmap in javascript private methods?

I see using symbol and weak map to create private members inside an object. are they similar? they both assigned to the a property outside the object and then get called inside the object.
const _length=Symbol() or const _length=new Weakmap()
I understand symbol() easily however I do not get weakmap. In which case I should use weakmap or Can I use symbol all the time?
Here's a full example for each of these techniques:
const Foo = (() => {
const _length = Symbol('_length');
return class Foo {
[_length] = 0;
get length () { return this[_length]; }
set length (value) { this[_length] = value; }
}
})();
const bar = new Foo();
console.log(bar.length);
const Foo = (() => {
const _length = new WeakMap();
return class Foo {
constructor () { _length.set(this, 0); }
get length () { return _length.get(this); }
set length (value) { _length.set(this, value); }
}
})();
const bar = new Foo();
console.log(bar.length);
However, the Symbol approach is not truly private, as demonstrated here:
const Foo = (() => {
const _length = Symbol('_length');
return class Foo {
[_length] = 0;
get length () { return this[_length]; }
set length (value) { this[_length] = value; }
}
})();
const bar = new Foo();
const _length = Object.getOwnPropertySymbols(bar)[0]; // private key exposed
console.log(bar[_length]); // private property exposed
The reason that WeakMap is recommended for the second approach is because it allows instances of the class Foo to be garbage collected when they are no longer referenced anywhere else in the program.
In contrast, a normal Map will hold a strong reference to each instance and prevent it from being garbage collected, which leads to memory leaks in the program.
There's also now a third approach that should eventually make its way into the ECMAScript specification: private fields which are part of the TC39 class fields proposal currently in Stage 3.
class Foo {
#length = 0;
get length () { return this.#length; }
set length (value) { this.#length = value; }
}
const bar = new Foo();
console.log(bar.length);
In contrast to the Symbol approach, these are truly private properties. In addition, they're much easier to write than when using the approach with WeakMap.

Creating immutable class for Node CommonJS module

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

Override default get in javascript class such as __get in php

I'm building a javascript library and I would like to be able to do exactly like the PHP's __get does.
My library has a attributes property which stores each model's attributes. Now, I am force to get an attribute using a .get method. But I would be able to do it with a getter. Let's says that User extends my model class.
let instance = new User({firstname: 'John', lastname: 'Doe'});
console.log(instance.get('firstname')); // gives me 'John'
I want to be able to do instance.firstname which will call the .get method passing 'firstname' as parameter. In PHP you can do it that way : http://php.net/manual/fr/language.oop5.overloading.php#object.get
Is this something possible?
Thank you all
This is easy using ES 2015 classes:
class Foo {
constructor () {
this._bar = null;
}
get bar () {
doStuff();
return this._bar;
}
set bar (val) {
doOtherStuff();
this._bar = val;
return this;
}
};
var foo = new Foo();
foo.bar = 3; // calls setter function
console.log(foo.bar); // calls getter function
here's the (simplified) output from babel:
var Foo = function () {
function Foo() {
this._bar = null;
}
_createClass(Foo, [{
key: "bar",
get: function get() {
doStuff();
return this._bar;
},
set: function set(val) {
doOtherStuff();
this._bar = val;
return this;
}
}]);
return Foo;
}();
Note that this isn't just for classes, any arbitrary object can have these:
var baz = {
get qux() {
// arbitrary code
},
set qux(val) {
// arbitrary code
}
};
Source.
EDIT
What you want is possible but only in native ES 6 environments, as Proxy cannot be polyfilled.
var getter = function(target, property, proxy) {
console.log(`Getting the ${property} property of the obj.`);
return target[property];
};
var setter = function(target, property, value, proxy) {
console.log(`Setting the ${property} property to ${value}.`);
target[property] = value;
};
var emptyObj = {};
var obj = new Proxy(emptyObj, {
get: getter,
set: setter
});
obj.a = 3; // logs 'Setting the a property to 3'
var foo = obj.a; // logs 'Getting the a property of the obj'
Quite simply assign the properties in a loop:
User = function (attrs) {
for (var name in attrs) {
this[name] = attrs[name];
}
}
User.prototype = {
// further methods
}
Using the ES6 class syntax, - I have to admit I do not see the point of writing things this way:
class User {
constructor (attrs) {
for (var name in attrs) {
this[name] = attrs[name];
}
}
// further methods
}
Remember: the second syntax is exactly what happens with the first one, only with some sugar on top.

Javascript Proxy set() local property on inherited objects

According to MDN,
handler.set() can trap Inherited property assignment:
Object.create(proxy)[foo] = bar;
In which case, how does one both monitor and allow local assignments on inherited objects?
var base = {
foo: function(){
return "foo";
}
}
var proxy = new Proxy(base, {
set: function(target, property, value, receiver){
console.log("called: " + property + " = " + value, "on", receiver);
//receiver[property] = value; //Infinite loop!?!?!?!?!
//target[property] = value // This is incorrect -> it will set the property on base.
/*
Fill in code here.
*/
return true;
}
})
var inherited = {}
Object.setPrototypeOf(inherited, Object.create(proxy));
inherited.bar = function(){
return "bar";
}
//Test cases
console.log(base.foo); //function foo
console.log(base.bar); //undefined
console.log(inherited.hasOwnProperty("bar")) //true
After some additional thought, i noticed that it intercepts 3 ops:
Property assignment: proxy[foo] = bar and proxy.foo = bar
Inherited property assignment: Object.create(proxy)[foo] = bar
Reflect.set()
but not Object.defineProperty() which appears to be even lower level than the = operator.
Thus the following works:
var base = {
foo: function(){
return "foo";
}
};
var proxy = new Proxy(base, {
set: function(target, property, value, receiver){
var p = Object.getPrototypeOf(receiver);
Object.defineProperty(receiver, property, { value: value }); // ***
return true;
}
});
var inherited = {};
Object.setPrototypeOf(inherited, Object.create(proxy));
inherited.bar = function(){
return "bar";
};
// Test cases
console.log(base.foo); // function foo
console.log(base.bar); // undefined
console.log(inherited.bar); // function bar
console.log(inherited.hasOwnProperty("bar")) // true
I see two options (maybe):
Store the property in a Map, keeping the Maps for various receivers in a WeakMap keyed by the receiver. Satisfy get by checking the Map and returning the mapping there instead of from the object. (Also has.) Slight problem is that you also need to proxy the receiver (not just base) in order to handle ownKeys. So this could be unworkable.
Temporarily get the proxy out of the inheritance chain while setting.
Here's that second one:
var base = {
foo: function(){
return "foo";
}
};
var proxy = new Proxy(base, {
set: function(target, property, value, receiver){
const p = Object.getPrototypeOf(receiver); // ***
Object.setPrototypeOf(receiver, null); // ***
receiver[property] = value; // ***
Object.setPrototypeOf(receiver, p); // ***
return true;
}
});
var inherited = {};
Object.setPrototypeOf(inherited, Object.create(proxy));
inherited.bar = function(){
return "bar";
};
// Test cases
console.log("base.foo:", base.foo); // function foo
console.log("base.bar:", base.bar); // undefined
console.log("inherited.bar:", inherited.bar); // function bar
console.log("inherited has own bar?", inherited.hasOwnProperty("bar")); // true

Default enum value in javascript class constructor

I have a simple class, and I'm trying to figure out how to set a default value in the constructor:
var Foo = function(bar = Foo.someEnum.VAL1) {
this.bar = bar;
someEnum = {VAL1 : 1, VAL2: 2};
}
and to use it like:
var myFoo = new Foo(Foo.someEnum.VAL1);
but this is apparently wrong. What's the correct way to set a default enum value, or do I need to set the default to null, and check for the null in the constructor and set accordingly?
To clarify, bar is an enum for the Foo class. There are other properties in the class that are not shown. Also, updated class code.
You can try this if you want to make bar an optional parameter:
function Foo(bar) {
this.bar = bar || Foo.enum.VAL1; //If bar is null, set to enum value.
}
//Define static enum on Foo.
Foo.enum = { VAL1: 1, VAL2: 2, VAL3: 3 };
console.log(new Foo().bar); //1
console.log(new Foo(Foo.enum.VAL3).bar); //3
Do you just want bar to be defined inside the function?
var Foo = function() {
var bar = {VAL1 : 1, VAL2: 2};
}
or for a blank starting object:
var Foo = function() {
var bar = {};
}
Or are you wanting it to be set from a parameter that's passed into the function?
var Foo = function(barIn) {
var bar = barIn;
}
Another option - create the object (enum) from values passed in:
var Foo = function(val1, val2) {
var bar = {VAL1 : val1, VAL2: val2};
}
The possibilities go on, but it's not entirely clear what you're trying to achieve.
I'm not entirely sure what you are trying to do but maybe it is this...
var Foo = function (bar = 1) {
this.bar = bar;
}
Foo.VAL1 = 1;
Foo.VAL2 = 2;
Now you can do:
foo1 = new Foo();
alert(foo1.bar); //alerts 1;
foo2 = new Foo(Foo.VAL2);
alert(foo1.bar); //alerts 2;

Categories

Resources