nested properties on a class instance - javascript

I need to build a structure (i need to do it for jestjs, in order to stub a module) that allows me to access a method in the following way:
// I make an instance of a class
const myTest = new Test()
// I access a method in this way
const result = myTest.prop1.prop2.prop3.getAll()
A basic class would look like
class Test2 {
constructor(){}
//getter
getAll(){
return 'Salutes'
}
}
And I can access getAll()
const myTest2 = new Test()
const result = myTest.getAll()
//in this way result contains a string (*salutes*)
So how can I add more properties in the middle? I found something, but in typescript... I need to do it in javascript

Assuming you need exactly the structure you've specified, in Test you'd have to create the object and have any methods you need be arrow functions or bound functions (if they need to access information from the Test instance; your example didn't so it wouldn't matter whether they had the right this). For instance:
class Test {
constructor() {
this.prop1 = {
prop2: {
prop3: {
getAll: () => {
return "Salutes";
},
getAll2() { // Also works
return "Salutes";
},
getAll3: function() { // Also works
return "Salutes";
}
}
}
}
}
}
const myTest = new Test();
const result = myTest.prop1.prop2.prop3.getAll();
console.log(result);
console.log(myTest.prop1.prop2.prop3.getAll2());
console.log(myTest.prop1.prop2.prop3.getAll3());
Example of using information on the Test instance (it's the same except for the constructor parameter and message property):
class Test {
constructor(message) {
this.message = message;
this.prop1 = {
prop2: {
prop3: {
getAll: () => {
// `this` refers to the `Test` instance because
// this is an arrow function
return this.message;
}
// `getAll2` and `getAll3` wouldn't work because
// they would get `this` based on how they're called
// (it would be the `this.prop1.prop2.prop3` object).
}
}
}
}
}
// I make an instance of a class
const myTest = new Test("Salutes encore!");
// I access a method in this way
const result = myTest.prop1.prop2.prop3.getAll();
console.log(result);

If you want to achieve it without modifying the Test2 class, you can use Proxy class and define custom handler for get method:
class Test2 {
constructor(){}
getAll(){
return 'Salutes'
}
}
let instance = new Test2();
const handler = {
get: function(target, prop, receiver) {
if (['prop1', 'prop2', 'prop3'].includes(prop)) {
return receiver;
}
return Reflect.get(...arguments);
}
};
const proxy = new Proxy(instance, handler);
console.log(proxy.prop1.prop2.prop3.getAll());
console.log(proxy.prop7);
console.log(proxy.getAll());

Related

How to access to a class's property without an instance?

Currently, I have a class of this sort:
class MyClass {
constructor(c) {
this.a = "a";
this.b = "b";
}
myMethod() {
return c;
}
}
As you can see c would be a property that I want to be returned when I execute a method on an instance, but it is not in the object instance.
Private properties would not work, because if I stringify the object, the property is also in the string, and I don't want it there.
Is there any way to achieve this? Not necessarily a complete solution but some hints would be enough.
Private properties would not work, because if I stringify the object, the property is also in the string
No it's not? This works fine:
class MyClass {
#c;
constructor(c) {
this.a="a";
this.b="b";
this.#c=c;
}
myMethod() {
return this.#c;
}
}
const obj = new MyClass('hi');
console.log(JSON.stringify(obj));
console.log(obj.myMethod());
An alternative would be to create the method in the constructor, as a closure over the c variable:
class MyClass {
constructor(c) {
this.a="a";
this.b="b";
this.myMethod = () => {
return c;
};
}
}
const obj = new MyClass('hi');
console.log(JSON.stringify(obj));
console.log(obj.myMethod());
Further alternatives that work with normal properties and prevent inclusion in the JSON.stringify result are to make the c property non-enumerable or to define a custom toJSON method.
In my real world class I added the properties:
class MyClass{
//other props
preQ: string;
postQ: string;
constructor(data: InputData = { cli: cli.flags }) {
Object.defineProperties(this, {
preQ: { enumerable: false },
postQ: { enumerable: false },
});
// other stuff
}
}
As indicated to in the first comment by Pointy. The properties are not present in the JSON.stringify result.
Which are properties that I do not want to be sent to the server.
Use static Keyword :
MDN Documentation :
Static properties cannot be directly accessed on instances of the
class. Instead, they're accessed on the class itself.
Static methods are often utility functions, such as functions to
create or clone objects, whereas static properties are useful for
caches, fixed-configuration, or any other data you don't need to be
replicated across instances.
Example
class Student {
name: string;
static age: number;
constructor(age: number) {
this.name = 'Jhon';
Student.age = age;
}
static getAge = () => Student.age;
}
const student = new Student(20);
const json = JSON.stringify(student); // {"name":"Jhon"}
console.log(json);
console.log(Student.getAge()); // 20
Your code:
class MyClass {
a: string;
b: string;
static c: string;
constructor(c:string) {
this.a = 'a';
this.b = 'b';
MyClass.c = c;
}
myMethod() {
return MyClass.c;
}
}
const obj = new MyClass('hi');
console.log(JSON.stringify(obj)); // {"a":"a","b":"b"}
console.log(obj.myMethod()); // hi

is there any chance to get the outer scope in Proxy?

I am Proxing an object in a custom class and I'd like to get access to the methods and properties of that same class within my Proxy Object. Is it possible?
I thought there is a way to bind the context but it didn't work for me neight with apply, call nor bind.
Any suggestions would be appreciated!
class MyClass {
constructor() {
this.state = new Proxy(this.initialState, {
set(target, prop, value) {
// some logic goes here
}
})
}
methodIneedToReach() {}
}
I need it to structure the code and prevent the mess.
Either store the value of this in a variable called that for example and use that.methodIneedToReach inside the set method, or, better yet, use an arrow function for set. Since arrow functions don't have their own this keyword, they will use the surrounding one which is, in this case, the instance of your class:
class MyClass {
constructor() {
this.state = new Proxy(this.initialState, {
set: (target, prop, value) => { // set is an arrow function
this.methodIneedToReach(); // works because 'this' inside here points to your class
}
})
}
methodIneedToReach() {}
}
Demo:
class MyClass {
constructor() {
this.initialState = { message: "Hello World!" };
this.state = new Proxy(this.initialState, {
set: (target, prop, value) => {
this.methodIneedToReach();
}
})
}
methodIneedToReach(value) {
console.log("'methodIneedToReach' is called");
}
}
let inst = new MyClass();
inst.state.message = "Bye world!";

Getting/setting a function as a property on a class (TypeScript)

I am trying to create a class which looks like this:
class Foo {
_bar?: () => void; // this is a property which will invoke a function, if it is defined
set bar(barFunctionDef: () => void) { // this stores a reference to the function which we'll want to invoke
this._bar = barFunctionDef;
}
get bar() { // this should either invoke the function, or return a reference to the function so that it can be invoked
return this._bar;
}
doSomething() { // this is what will be called in an external class
if(condition) {
this.bar; // this is where I would like to invoke the function (bar)
}
}
}
Essentially, I want to have a class which will store references to functions which will be optionally set. There will be many "bar" class properties which will all have the type () => void.
Is there a "correct" way to call this.bar inside doSomething in order to invoke the function that is stored on that property?
Don't know if that's what you're trying to achieve, but I set up an example of generic implementation here
type MethodReturnsVoid = () => void;
class Foo {
methods: Map<string, MethodReturnsVoid>;
constructor() {
this.methods = new Map();
}
public getMethod(methodName: string): MethodReturnsVoid {
if (this.methods.has(methodName)) {
return this.methods.get(methodName);
}
return null;
}
public setMethod(methodName: string, method: () => void): void {
this.methods.set(methodName, method);
}
}
const foo = new Foo();
const func: MethodReturnsVoid = () => console.log('func');
const anotherFunc: MethodReturnsVoid = () => console.log('anotherFunc');
foo.setMethod('func', func);
foo.setMethod('anotherFunc', anotherFunc);
const methodFunc = foo.getMethod('func');
if (methodFunc) methodFunc();
const methodAnotherFunc = foo.getMethod('anotherFunc');
if (methodAnotherFunc) methodAnotherFunc();

A way to retrieve the context without passing `.this` as parameter

Here is a little background.
So I have my parent class. Constructor receives two objects: the first one is the class where the instance of the parent object was created (using others classes which are inheriting parent class).
The second object are just some parameters
parent.js
class ParentClass {
constructor(nativeObject, params) {
this.nativeObject = nativeObject;
this.param1 = params.param1;
this.param2 = params.param2;
}
// some logic here
}
module.exports = { ParentClass };
child.js
class ChildClass extends ParentClass {
// some logic here
}
module.exports = { ChildClass };
I'm using the child class in the other class methods for creating it. I can have multiple files like below:
usingclasses.js
let { ChildClass } = require('path/to/my/child/class');
let opts = {
param1: 'idkSomeString';
param2: 42;
}
class MyCoolClass {
createChild() {
return new ChildClass(this, opts);
}
}
module.exports = { MyCoolClass };
anotherclasses.js
let { ChildClass } = require('path/to/my/child/class');
let opts = {
param1: 'thatAstringToo';
param2: 123;
}
class AnotherCoolClass{
alsoCreatesChild() {
return new ChildClass(this, opts);
}
}
module.exports = { AnotherCoolClass};
I can create multiple instances of the ChildClass in a different methods within one class:
thirdexample.js
let { ChildClass } = require('path/to/my/child/class');
let opts1 = {
param1: 'aStringAgain';
param2: 27;
}
let opts2 = {
param1: 'ABC';
param2: 33;
}
class ClassAgain{
firstMethod() {
return new ChildClass(this, opts1);
}
secondMethod() {
return new ChildClass(this, opts2);
}
}
module.exports = { ClassAgain };
The reason why I'm passing .this because I'm working with it in ParentClass
e.g for logging console.log(`I know this was created in ${this.nativeObject .constructor.name}`);
What I'm looking for is the way to get rid of passing .this every time I'm creating a new instance of the ChildClass
Reason behind this is that in one file (class) I can create up to 10+ instances of the ChildClass.
In the previous question I already got an answer which helped me to make the code more structured and more easy to maintain:
Instead of:
createChild() {
return new ChildClass(this, param1, param2, param3, paramN);
}
I'm doing:
createChild() {
return new ChildClass(this, paramObj);
}
So now I can store all my objects with parameters at the top of the file (of even separate file) and easily find and change it if needed.
...
Now I'm trying to find a way to get rid of .this every time I'm creating ChildClass
Is there any other way how ParentClass can know where does this ChildClass instances "lives"?
I'm trying to figure out can I do something using .apply or .call but seems like this is not a droids I'm looking for
UPDATE:
Since I have zero progress on this and I even got a comment that most likely this is not possible let's assume for now that I need a name of the class that is creating instance of the ChildClass only for logging.
How can I retrieve the name of the class without passing it every time when creating a ChildClass?
let opts = {
className: 'MyCoolClass'
param1: 'idkSomeString';
param2: 42;
}
class MyCoolClass {
createChild() {
return new ChildClass(opts);
}
}
module.exports = { MyCoolClass };
The problem is that I will have to set className: 'MyCoolClass' in each version of opts again and again and again... Just repeat the same for 10-15 times
You can just instantiate your classes using Reflect.construct.
class ParentClass {
constructor (nat, x) {
this.nat = nat
this.x = x
}
log () {
console.log('from ', this.nat.constructor.name, this.x)
}
}
class ChildClass extends ParentClass{}
class ClassAgain{
firstMethod () {
return this.makeInstance({ a: 1 })
}
secondMethod () {
return this.makeInstance({ b: 2 })
}
makeInstance (params) {
return Reflect.construct(ChildClass, [this, params])
}
}
const ca = new ClassAgain()
ca.firstMethod().log()
ca.secondMethod().log()

convert javascript plain object into model class instance

I need to implement small ODM like feature. I get plain javascript object from database, and I need to convert it into my model class instance. Let's assume model looks like:
class Model{
constructor(){
this.a = '777';
---- whole bunch of other things ---
}
print(){
console.log(this.a);
}
}
So I need convert var a = {b:999, c:666} to instance of model and being able to call a.print() after, and when a.print() executed 777 should be placed in console. How to do that?
There have a simple method. Just assign the object to instance(this)
class Model
{
constructor(obj){
Object.assign(this, obj)
}
print(){
console.log(this.a);
}
}
let obj = {a: 'a', b: 'b', c: 'c'}
let m = new Model(obj)
console.log(m)
m.print() // 'a'
If I understand the question correctly, you can export a factory function and make use of Object.assign to extend your base Model:
// Export the factory function for creating Model instances
export default const createModel = function createModel(a) {
const model = new Model();
return Object.assign(model, a);
};
// Define your base class
class Model {
constructor() {
this.a = 777;
}
print() {
console.log(this.a, this.b, this.c)
}
}
And call it like:
const myModel = createModel({ b: 999, c: 666 });
myModel.print();
Babel REPL Example
Or, of course, you could forego the factory and pass a in as a parameter (or rest parameters) to the constructor but it depends on your preferred coding style.
I would suggest rewriting your class to store all its properties in a single JS object this.props and accept this object in its constructor:
class Model {
constructor (props = this.initProps()) {
this.props = props
// other stuff
}
initProps () {
return {a: '777'}
}
print () {
console.log(this.props.a)
}
}
Then you'll be able to store this.props in your database as a plain JS object and then use it to easily recreate corresponding class instance:
new Model(propsFromDatabase)
Though, if you don't want to move all properties to this.props, you could use Object.assign to keep your object plain:
class Model {
constructor (props = this.initProps()) {
Object.assign(this, props)
// other stuff
}
initProps () {
return {a: '777'}
}
print () {
console.log(this.a)
}
}
But I would recommend using the former approach, because it'll keep you safe from name collisions.
If you need to typecast more consistently, you can also create your own typecast function like generic function
function typecast(Class, obj) {
let t = new Class()
return Object.assign(t,obj)
}
// arbitrary class
class Person {
constructor(name,age) {
this.name = name
this.age = age
}
print() {
console.log(this.name,this.age)
}
}
call it to typecast any object to any class instance like
let person = typecast(Person,{name:'Something',age:20})
person.print() // Something 20
How about this?:
var a = Object.create(Model.prototype, {
b: {
enumerable: true, // makes it visible for Object.keys()
writable: true, // makes the property writable
value: 999
}, c: {
value: 666
}
});
You'd be basically creating a new instance of Model from it's prototype and assigning your new properties to it. You should be able to call print as well.
You could have a static Model.from or Model.parse method, that returns a new Model with those properties:
class Model {
static defaults = { a: 777, b: 888, c: 999, d: 111, e: 222 };
constructor() {
const { defaults } = Model;
for (const key in defaults) this[key] = defaults[key];
}
print() {
console.log(this.a);
}
static from(data) {
const { defaults } = Model;
return Object.assign(
new Model(),
defaults,
Object.fromEntries(
Object.entries(data).filter(([key]) => key in defaults)
)
);
}
}
const data = {
a: "a", b: "b", c: "c", ajkls: "this wont be included"
};
const myModel = Model.from(data);
console.log("myModel =", myModel);
console.log("myModel instanceof Model:", myModel instanceof Model);
console.log("myModel.print():")
myModel.print();
Just like G_hi3's answer, but it "automates" the creation of the properties object
function Model() {
this.a = '777';
}
Model.prototype.print = function(){
console.log(this.a);
}
// Customize this if you don't want the default settings on the properties object.
function makePropertiesObj(obj) {
return Object.keys(obj).reduce(function(propertiesObj, currentKey){
propertiesObj[currentKey] = {value: obj[currentKey]};
return propertiesObj;
}, {}); // The object passed in is the propertiesObj in the callback
}
var data = {a: '888'};
var modelInstance = Object.create(Model.prototype, makePropertiesObj(data));
// If you have some non trivial initialization, you would need to call the constructor.
Model.call(modelInstance);
modelInstance.print(); // 888
First declare a class in which you want to convert JSON:
class LoginResponse {
constructor(obj) {
Object.assign(this, obj);
}
access_token;
token_type;
expires_in;
}
Now convert the general javascript object into your desired class object:
const obj = {
access_token: 'This is access token1',
token_type: 'Bearer1',
expires_in: 123,
};
let desiredObject = new LoginResponse(obj);
console.log(desiredObject);
Output will be:
LOG {"access_token": "This is access token1", "expires_in": 123, "token_type": "Bearer1"}

Categories