How to handle class object with circular references? - javascript

I am thinking about simple problem. I have given an class for example Model
class Model {
constructor(parameters = {}) {
this.id = parameters.id;
}
}
so as you can see we can create new Model objects like: let model = new Model(). More complex example would look like this:
//we have some data given from API maybe?
let parameters = {id: 1};
let model = new Model(parameters );
And here we are at the point where i started to wander What if the object with given id already exists ?
The question is what pattern should i use to instantiate object with given id only once?
Let's go further: what if we will get nested objects with circural references ? Let assume we got another class called AnotherModel and our code looks like:
class Model {
constructor(parameters = {}) {
this.id = parameters.id;
this.anotherModel= nulld;
if (parameters.anotherModel) {
this.anotherModel= parameters.anotherModel instanceof AnotherModel
? parameters.anotherModel
: new AnotherModel(parameters.anotherModel);
}
}
}
class AnotherModel {
constructor(parameters = {}) {
this.id = parameters.id;
this.models = [];
if (parameters.models) {
for (let i = 0; i < parameters.models.length; i++) {
let model = parameters.models[i];
model.anotherModel= this;
this.models.push(new Model(model));
}
}
}
}
So AnotherModel contains a collection of Models and Model object contains reference to AnotherModel.
What is the nice way to resolve this issue ? What we want to achive is to have only one object with the same id.
What i was thinking is to do some kind of ObjectPool where i will store all objects for given class or classes and when new object is instantiated our pool would create a new one if it does not exist or return the existing one?
But here is a little disadventage, if for example we already have written some code we would have to refactore and change the way we instatiate them from new Model() to ObjectPool.get(Model, parameters)?
What are your ideas ?

You could use an object pool (either on the class or outside of it) to keep track of your instances. By defining it in the constructor, you can still instantiate models with:
new Model();
new AnotherModel();
If the id already exists in the pool, you can just return the existing instance.
Outside of the class:
const modelPool = {};
class Model {
constructor(parameters = {}) {
if (modelPool[parameters.id] instanceof Model) {
return modelPool[parameters.id];
}
modelPool[parameters.id] = this;
this.id = parameters.id;
this.anotherModel= null;
// ...
}
}
const anotherModelPool = {};
class AnotherModel {
constructor(parameters = {}) {
if (anotherModelPool[parameters.id] instanceof AnotherModel) {
return anotherModelPool[parameters.id];
}
anotherModelPool[parameters.id] = this;
this.id = parameters.id;
this.models = [];
//...
}
}
Or as a (non-enumerable, non-writeable, non-configurable) property on the class (not the instance):
class Model {
constructor(parameters = {}) {
if (Model.pool[parameters.id] instanceof Model) {
return Model.pool[parameters.id];
}
Model.pool[parameters.id] = this;
this.id = parameters.id;
this.anotherModel= null;
//...
}
}
Object.defineProperty(Model, 'pool', {
value: {}
});
class AnotherModel {
constructor(parameters = {}) {
if (AnotherModel.pool[parameters.id] instanceof AnotherModel) {
return AnotherModel.pool[parameters.id];
}
AnotherModel.pool[parameters.id]
this.id = parameters.id;
this.models = [];
//...
}
}
Object.defineProperty(AnotherModel, 'pool', {
value: {}
});
As added by #Vardius, one can also create a pseudo-abstract class (as JS does not have abstract classes) which can be extended from. Using new.target.name, a namespace within the pool of the abstract class can be created:
class Entity {
constructor(parameters = {}) {
if (Entity.pool[this.constructor.name] && Entity.pool[this.constructor.name][parameters.id] instanceof Entity) {
return Entity.pool[new.target.name][parameters.id];
}
Entity.pool[new.target.name][parameters.id] = this;
}
}
Object.defineProperty(Entity, 'pool', {value: {} });

Related

call a methods of a class without using NEW keyword inside other class node js

I want to access Main class methods to another Person class without creating a new instance Is it possible??
Can we access it without creating an instance of a class
let myInstance = new Person();
class Main {
constructor(args) {
this.hooks = [];
}
add_hooks(name, func) {
if (!this.hooks[name]) this.hooks[name] = [];
this.hooks[name].push(func);
}
call_hooks(name, ...params) {
if (this.hooks[name]) this.hooks[name].forEach((func) => func(...params));
}
}
other class Person how to access without using new keyword
const Main = require("./main.js");
class Person {
exec() {
const action = Main();
action.add_hook("jump", console.log.bind(console, "this will log "));
}
}
There is no big magic to it. Since the OP just wants to reuse prototypal Main methods, one is going to explicitly delegate the method/s of interest which was/were provided/accessed before via Main.prototype ...
class Main {
constructor(args) {
this.hooks = {};
}
add_hooks(name, func) {
if (!this.hooks[name]) {
this.hooks[name] = [];
}
this.hooks[name].push(func);
}
call_hooks(name, ...params) {
if (this.hooks[name]) {
this.hooks[name].forEach(func => func(...params));
}
}
}
// const Main = require("./main.js");
class Person {
// // ... either add `hooks` as public property at instantiation time ...
// hooks = {};
exec() {
const ref = Main.prototype;
ref.add_hooks.call(this, "jump", console.log.bind(console, "this will log"));
}
}
// ... or add `hooks` via additional glue code ...
function createPersonWithHooksAndExecute() {
const type = new Person();
type.hooks = {};
type.exec();
return type;
}
const someone = createPersonWithHooksAndExecute();
console.log({ someone });
// this will log
Main.prototype.call_hooks.call(someone, "jump");
.as-console-wrapper { min-height: 100%!important; top: 0; }
If you're not planning on instantiating the object, and you don't care about having multiple instances with each having their own state, you don't need a class.
Just create individual functions, or export an object.
const hooks = [];
export function add_hooks(name, func) {
if (!hooks[name]) hooks[name] = [];
hooks[name].push(func);
}
export function call_hooks(name, ...params) {
if (!hooks[name]) return;
for (const func of this.hooks[name]) {
func(...params);
}
}
It's possible too to do this with static methods, and that would be the likely answer if you write Java where everything has to be a class, but I wouldn't recommended it in Javascript.

Referring to parent object from the child

class Test {
constructor() {
this.childObject = new Child();
}
parentHello() {
console.log("Parent Hello World!");
}
}
class Child {
childHello() {
// How do I call parentHello() from here?
}
}
const obj = new Test();
obj.childObject.childHello();
I'm trying to call the parentHello() from the childHello(). The only way I came up with is to change the structure to circular like this:
class Test {
constructor() {
this.childObject = new Child(this);
}
parentHello() {
console.log("Parent Hello World!");
}
}
class Child {
constructor(parent) {
this.parent = parent;
}
childHello() {
this.parent.parentHello();
}
}
const obj = new Test();
obj.childObject.childHello();
But after doing this I'm no longer able to convert it to JSON. Is there a proper way to do this?
EDIT: I also tried super() but it only works when extending.
Your solution with reference to parent via constructor is ok I think. To allow serialization to JSON, you can use second parameter of JSON.stringify (replacer):
A function that alters the behavior of the stringification process, or an array of String and Number objects that serve as a whitelist for selecting/filtering the properties of the value object to be included in the JSON string. If this value is null or not provided, all properties of the object are included in the resulting JSON string.
class Test {
constructor() {
this.childObject = new Child(this);
}
parentHello() {
console.log("Parent Hello World!");
}
}
class Child {
constructor(parent) {
this.parent = parent;
}
childHello() {
this.parent.parentHello();
}
}
const obj = new Test();
var cache = [];
var json = JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (cache.includes(value)) {
return;
}
cache.push(value);
}
return value;
});
console.log(json);

Update an instance's property, from another instance

I'm looking for the best way to update in object instance, for the example in this case myParentObjects's name property. The only way I understand to achieve this would be to pass reference of the parent object to the child object instance as a parameter into either new myChildObj(this,name) in the constructor, or a method of the myChildObj instance like myChildObj.updateParentProperty(name).
I can't imagine a child object nested 4-5 levels down, and having to update properties on it's parents passing (parent1,parent2,parent3,etc) it's params, that would be a managing nightmare! there must be a better way to update parent properties!
function myParentObj(){
this.name = 'jordan'
this.names = ['jordan','danny','cassie'];
this.init=()=>{
this.names.forEach((name)=>{
var childObj = new myChildObj(this,name);
childObj.updateParentProperty();
})
}
}
function myChildObj(parentObj,name){
this.parent = parentObj;
this.name = name;
this.updateParentProperty=()=>{
this.parent.name = this.name;
};
}
function init(){
var parentObj = new myParentObj();
parentObj.init();
}
document.addEventListener('DOMContentLoaded',init);
Question: What is the best method to update parent object parameters?
What puzzles me is this:
this.init=()=>{
this.names.forEach((name)=>{
var childObj = new myChildObj(this,name);
childObj.updateParentProperty();
})
}
because as soon as all three children are created, your parent will have a name of cassie (since that's the last child calling updateParentProperty). That and your tags is why I'm inclined to claim that you're looking for classical inheritance, like so:
Classical inheritance
function myParentObj (name) {
this.name = name;
this.names = ['jordan','danny','cassie'];
this.init=()=>{
this.names.forEach((name)=>{
var childObj = new myChildObj(name);
console.log(childObj);
});
}
}
function myChildObj (name) {
myParentObj.call(this, name);
// Other stuff, only available for myChildObj
}
function init(){
var parentObj = new myParentObj();
parentObj.init();
}
document.addEventListener('DOMContentLoaded',init);
var parent = new myParentObj();
console.log(parent);
Which would mean the child inherits properties from its parent, but would of course not update them for the parent when their own name changes.
Some simple observer pattern
Okay, your example just has a single property which change should be noticed. I think an observer pattern like in the example below is a bit too much, but for larger applications it's quite useful.. the observer pattern is not distinct to JavaScript only (see enter link description here). Basically, you have an object which changes shall be monitored (observable) and one or many objects which want to perform actions depending on these changes (observers). The implementation can be quite simple (like my demonstration) or really complex. The basic idea is for the observable to maintain a list of its observers. The example uses an own, simple implementation. There are also libraries available, we're using knockout, which offers a MVVM structure for your page and can even update the DOM depending on changes.
function myParentObj(){
this.name = 'jordan'
this.names = ['jordan','danny','cassie'];
this.init=()=>{
this.names.forEach((name)=>{
var childObj = new myChildObj(name);
childObj.subscribe('name', newValue => this.name = newValue);
})
}
}
function myChildObj(name){
this.subscribers = {};
Object.defineProperty(this, 'name', {
set: newValue => {
this._name = newValue;
this.notify('name');
},
get: () => this._name
});
this.subscribe = function (prop, callback) {
if (!this.subscribers.hasOwnProperty(prop)) {
this.subscribers[prop] = [];
}
this.subscribers[prop].push(callback);
};
this.notify = function (prop) {
if (this.subscribers[prop]) {
this.subscribers[prop].forEach(callback => callback(this[prop]));
}
};
}
function init(){
var parentObj = new myParentObj();
parentObj.init();
}
document.addEventListener('DOMContentLoaded',init);
var parent = new myParentObj();
var child = new myChildObj('bob');
child.subscribe('name', newName => parent.name = newName);
console.log(parent.name);
child.name = 'cassie';
console.log(parent.name);
Using Object.assign
You could also use Object.assign, which will copy all enumerable properties from one object to another. You should note, though, that it will copy all properties, even those you might not want to change.
function myParentObj(){
this.name = 'jordan'
this.names = ['jordan','danny','cassie'];
this.init=()=>{
this.names.forEach((name)=>{
var childObj = new myChildObj(this,name);
childObj.updateParentProperty();
})
}
}
function myChildObj(name){
this.name = name;
}
var parent = new myParentObj();
var child = new myChildObj('cassie');
Object.assign(parent, child);
console.log(child);
Classical inheritance
I see you mixed in some ES6, so you might be looking for an answer using the ES6 standards of object-oriented inheritance like so:
class Parent {
constructor(name = 'jordan') {
this.name = name
this.names = ['jordan', 'danny', 'cassie']
}
init() {
return this.names.map((name) => {
return new Child(name)
})
}
}
class Child extends Parent {
constructor(name) {
super(name)
// other stuff for Child
}
}
let parent = new Parent()
console.log(parent)
let children = parent.init()
children.forEach((child) => console.log(child))
In ES5, inheritance was usually implemented like this instead, though this is not exactly what ES6 class does, since ES6 does things like making instance methods non-enumerable, etc.
function Parent(name) {
name = arguments.length > 0 ? name : 'jordan'
this.name = name
this.names = ['jordan', 'danny', 'cassie']
}
// member methods
Parent.prototype = {
init: function init() {
return this.names.map(function (name) {
return new Child(name)
})
}
}
function Child(name) {
// basically, super(name)
Parent.call(this, name)
// other stuff for Child
}
// extending Parent
Child.prototype = Object.create(Parent.prototype)
Parent.prototype.constructor = Parent
var parent = new Parent()
console.log(parent)
var children = parent.init()
children.forEach(function (child) { console.log(child) })
Below is almost exactly the ES5 equivalent for the ES6 code in the first example:
function Parent() {
var name = arguments.length > 0 && name !== undefined ? name : 'jordan'
this.name = name
this.names = ['jordan', 'danny', 'cassie']
}
// member methods
Object.defineProperties(Parent.prototype, {
init: {
configurable: true,
value: function init() {
return this.names.map(function (name) {
return new Child(name)
})
},
writable: true
}
})
function Child(name) {
// basically, super(name)
Parent.call(this, name)
// other stuff for Child
}
// extending Parent
Child.prototype = Object.create(Parent.prototype, {
constructor: {
configurable: true,
value: Child,
writable: true
}
})
var parent = new Parent()
console.log(parent)
var children = parent.init()
children.forEach(function (child) { console.log(child) })
EventEmitter
After looking at the comments below the other answer, it appears you are most interested in observer-type patterns. EventEmitter, defined natively in Node.js, is the most widely used implementation of the observer pattern. Below is a polyfill demo of it for client-side JavaScript:
class Parent extends EventEmitter {
constructor(name = 'jordan') {
super()
this.name = name
}
}
class Child {
constructor(parent, name = parent.name) {
this.parent = parent
this._name = name
}
set name(name) {
console.log('setting child name')
this._name = name
this.parent.emit('name', name)
}
get name() {
return this._name
}
}
let parent = new Parent()
parent.on('name', function (name) {
console.log('setting parent name')
this.name = name
}.bind(parent))
let child = new Child(parent)
console.log('parent', parent.name)
console.log('child', child.name)
child.name = 'danny'
console.log('parent', parent.name)
console.log('child', child.name)
<script src="https://cdn.rawgit.com/mudge/5830382/raw/a4bc230f5bce01ea9a34b0d42247256531b97945/eventemitter.js"></script>
Proxy
Another neat API that JavaScript has is called Proxy, which allows metaprogramming, that is, redefining how JavaScript works in isolated cases:
var parent = {}
var child = {}
var proxy = new Proxy(child, {
set: (target, property, value) => {
// target === child
target['child-' + property] = typeof value + ' ' + value
parent['parent-' + property] = typeof value + ' ' + value
return value
}
})
proxy.name = 'jordan'
proxy.age = 54
console.log(parent)
console.log(child)

JSON stringify ES6 class property with getter/setter

I have a JavaScript ES6 class that has a property set with set and accessed with get functions. It is also a constructor parameter so the class can be instantiated with said property.
class MyClass {
constructor(property) {
this.property = property
}
set property(prop) {
// Some validation etc.
this._property = prop
}
get property() {
return this._property
}
}
I use _property to escape the JS gotcha of using get/set that results in an infinite loop if I set directly to property.
Now I need to stringify an instance of MyClass to send it with a HTTP request. The stringified JSON is an object like:
{
//...
_property:
}
I need the resulting JSON string to preserve property so the service I am sending it to can parse it correctly. I also need property to remain in the constructor because I need to construct instances of MyClass from JSON sent by the service (which is sending objects with property not _property).
How do I get around this? Should I just intercept the MyClass instance before sending it to the HTTP request and mutate _property to property using regex? This seems ugly, but I will be able to keep my current code.
Alternatively I can intercept the JSON being sent to the client from the service and instantiate MyClass with a totally different property name. However this means a different representation of the class either side of the service.
You can use toJSON method to customise the way your class serialises to JSON:
class MyClass {
constructor(property) {
this.property = property
}
set property(prop) {
// Some validation etc.
this._property = prop
}
get property() {
return this._property
}
toJSON() {
return {
property: this.property
}
}
}
If you want to avoid calling toJson, there is another solution using enumerable and writable:
class MyClass {
constructor(property) {
Object.defineProperties(this, {
_property: {writable: true, enumerable: false},
property: {
get: function () { return this._property; },
set: function (property) { this._property = property; },
enumerable: true
}
});
this.property = property;
}
}
I made some adjustments to the script of Alon Bar. Below is a version of the script that works perfectly for me.
toJSON() {
const jsonObj = Object.assign({}, this);
const proto = Object.getPrototypeOf(this);
for (const key of Object.getOwnPropertyNames(proto)) {
const desc = Object.getOwnPropertyDescriptor(proto, key);
const hasGetter = desc && typeof desc.get === 'function';
if (hasGetter) {
jsonObj[key] = this[key];
}
}
return jsonObj;
}
As mentioned by #Amadan you can write your own toJSON method.
Further more, in order to avoid re-updating your method every time you add a property to your class you can use a more generic toJSON implementation.
class MyClass {
get prop1() {
return 'hello';
}
get prop2() {
return 'world';
}
toJSON() {
// start with an empty object (see other alternatives below)
const jsonObj = {};
// add all properties
const proto = Object.getPrototypeOf(this);
for (const key of Object.getOwnPropertyNames(proto)) {
const desc = Object.getOwnPropertyDescriptor(proto, key);
const hasGetter = desc && typeof desc.get === 'function';
if (hasGetter) {
jsonObj[key] = desc.get();
}
}
return jsonObj;
}
}
const instance = new MyClass();
const json = JSON.stringify(instance);
console.log(json); // outputs: {"prop1":"hello","prop2":"world"}
If you want to emit all properties and all fields you can replace const jsonObj = {}; with
const jsonObj = Object.assign({}, this);
Alternatively, if you want to emit all properties and some specific fields you can replace it with
const jsonObj = {
myField: myOtherField
};
Use private fields for internal use.
class PrivateClassFieldTest {
#property;
constructor(value) {
this.property = value;
}
get property() {
return this.#property;
}
set property(value) {
this.#property = value;
}
}
class Test {
constructor(value) {
this.property = value;
}
get property() {
return this._property;
}
set property(value) {
this._property = value;
}
}
class PublicClassFieldTest {
_property;
constructor(value) {
this.property = value;
}
get property() {
return this.property;
}
set property(value) {
this._property = value;
}
}
class PrivateClassFieldTest {
#property;
constructor(value) {
this.property = value;
}
get property() {
return this.#property;
}
set property(value) {
this.#property = value;
}
}
console.log(JSON.stringify(new Test("test")));
console.log(JSON.stringify(new PublicClassFieldTest("test")));
console.log(JSON.stringify(new PrivateClassFieldTest("test")));
I've made an npm module named esserializer to solve such problem: stringify an instance of JavaScript class, so that it can be sent with HTTP request:
// Client side
const ESSerializer = require('esserializer');
const serializedText = ESSerializer.serialize(anInstanceOfMyClass);
// Send HTTP request, with serializedText as data
On service side, use esserializer again to deserialize the data into a perfect copy of anInstanceOfMyClass, with all getter/setter fields (such as property) retained:
// Node.js service side
const deserializedObj = ESSerializer.deserialize(serializedText, [MyClass]);
// deserializedObj is a perfect copy of anInstanceOfMyClass
I ran into the same issue but I have no access to the class construction and I'm not able to add or override the ToJson method
here is the solution that helped me solve it
a simple class with getters and properties
class MyClass {
jack = "yoo"
get prop1() {
return 'hello';
}
get prop2() {
return 'world';
}
}
a class with a child class and also child object with getters
class MyClassB {
constructor() {
this.otherClass = new MyClass()
}
joe = "yoo"
otherObject = {
youplaboum: "yoo",
get propOtherObject() {
return 'propOtherObjectValue';
}
}
get prop1() {
return 'helloClassB';
}
get prop2() {
return 'worldClassB';
}
}
here is the magic recursive function inspired by the ToJSON made by #bits
const objectWithGetters = function (instance) {
const jsonObj = Object.assign({}, instance);
const proto = Object.getPrototypeOf(instance);
for (const key of Object.getOwnPropertyNames(proto)) {
const desc = Object.getOwnPropertyDescriptor(proto, key);
const hasGetter = desc && typeof desc.get === 'function';
if (hasGetter) {
jsonObj[key] = desc.get();
}
}
for (let i in jsonObj) {
let value = jsonObj[i];
if (typeof value === "object" && value.constructor) {
jsonObj[i] = objectWithGetters(value);
}
}
return jsonObj;
}
const instance = new MyClassB();
const jsonObj = objectWithGetters(instance)
console.log(jsonObj)
let json = JSON.parse(jsonObj);
console.log(json)

A JavaScript Framework for Instance/Static Variables?

Here is the current method I use, where everything is either all public or private. However I would like to differentiate between static and instance. How would I add this in?
obj holds the class or object have you, and config_module determines what type of module this is.
All private, All public ( a collection of statics ), or instance based on a constructor method.
If I do use an instance based, how do I differentiate between statics and instance based properties?
$P.support = $P.parsel = function (obj, config_module) {
$R.Parsel[obj.Name] = obj;
// all properties are private
if (!config_module) {
return undefined;
}
// all properties are public
if (config_module === true) {
return obj;
}
// constructor based, all properties are public
if (config_module === 'constructor') {
var object_public;
if (obj.constructor) {
object_public = obj.constructor;
delete obj.constructor;
}
$A.someKey(obj, function (val, key) {
// like this ?
if (/^s_/.test(key)) {
object_public[key] = val;
// like this ?
} else if (/^p_/.test(key)) {
object_public.prototype[key] = val;
} else {
object_public.prototype[key] = val;
}
});
return object_public;
}
};
You can have (pseudo) static stuff by adding properties to the constructor:
function Something(){}
Something.getStaticFoo = function(){ return 'foo'; }
Something.getStaticFoo();
var instance = new Something();
instance.getStaticFoo(); // error
If I understand your code, that's the same as object_public[key] = val;.

Categories