Create an inherited method that copies the class instance [duplicate] - javascript

This question already has answers here:
How do you create a new object of the same type as some other object
(1 answer)
How to create new instance of parent class in javascript
(1 answer)
Is there a way to instantiate a new instance of a subclass from inside a super class method?
(2 answers)
Closed last month.
Overview
I have the JavaScript classes Parent, Child1, and Child2. Both children extend Parent. I want to write a method that "semi-deep" copies (explained later) the object. Because in actuallity there could be hundreds of children, I'd write this method on the parent and all children inherit that method. But the copy always returns the type that called it.
For instance, the usage would be the following:
const parent = new Parent([1, 2, 3])
const parentCopy = parent.copy() // parentCopy has type Parent
const child1 = new Child1([1, 2, 3])
const child1Copy = child1.copy() // child1Copy has type Child1
const child2 = new Child2([1, 2, 3])
const child2Copy = child2.copy() // child2Copy has type Child2
How can I do this?
What I've tried
If this were not a class, you could use destructuring:
const copy = {...original}
This is the same affect I want. However, since this is a class, I assume I'll need to eventually create a new Thing. For instance, here's how I could create a copy of Parent.
class Parent {
// ...
copy() {
return new Parent()
}
}
However, this solution means I'd need to override it for every single child. How can I say instead to create a new WhateverThisTypeActuallyis? So if this is a Child1 calling the copy, it'll create a new Child1().
What does "semi-deep" mean?
Briefly, a shallow copy is just a reference to the actual object. For instance, the following code snippet performs a shallow copy.
let object = { a: 2, b: 3 };
let copy = object;
object.a = 5;
console.log(copy.a);
This is not what I want. I want to create a new version of the object. However, I do want to shallow copy any properties the object may have. So I don't need a structured clone which does a recursive deep copy.
Other requirements
This should be pure JavaScript. I cannot use other dependencies or npm packages.

It looks like you can use this.constructor to achieve this effect. It even preserves the children's methods.
class Parent {
constructor(value) {
this.value = value
}
copy() {
return new this.constructor([...this.value])
}
}
class Child extends Parent {
greet() {
console.log("hello world")
}
}
const child = new Child([1, 2, 3])
console.log("child value", child.value)
child.greet()
const copy = child.copy()
child.value.push(4)
console.log("child value", child.value)
console.log("copy value", copy.value)
copy.value.push(4)
console.log("copy value", copy.value)
copy.greet()

Related

Is there way to create types like Array in typescript

Is there a way to create type in typescript with methods that when I call these methods, they have access to the variable's value? Equals what the array, for example, which has the method find.
Example:
const arrayVar: Array = [1,2,3];
array.find(el => el === 1);
In this case, find has access to the value of the array arrayVar without me having to pass it via a parameter to a function, for example, I wanted to create something in this way, for example:
const myVar: MyCustomType = 5;
myVar.add(2); // Nesse caso, o retorno seria 7.
I know it can be done with classes and functions, but then I would have to pass the value of "myVar" as a parameter (function add (value1, value2), for example), I wanted a way to access it directly, just like the type Array does in its methods.
To make a subclass of Number with new methods:
class SwagNumber extends Number {
add(number: number) {
// Tell the TS compiler that `this` is an unboxed Number
return (this as unknown as number) + number;
}
}
Then to use:
const six = new SwagNumber(6);
six will be typed to SwagNumber by the TS compiler.
And to show it works:
six.add(5)
> 11
Let's look at the Constructor used, part of the Class:
> six.constructor
[class SwagNumber extends Number]
This will also leave the original Number prototype unchanged, which will stop any potential issues (double dots are used to use a method on a number, to distinguish the dot from a decimal point!)
> 3..constructor
[Function: Number]
or:
> (3).constructor
[Function: Number]
See Classes on MDN
However there's some danger here
Since SwagNumber is an object, and a regular number isn't an object by default (until you call its methods), comparisons won't work properly:
> six === 6
false
See Why should you not use Number as a constructor?
You could do this by adding your add method to a prototype:
interface Number {
add: (val:number) => number;
}
Number.prototype.add = function(val:number): number{
return (this as number) + val;
}
var myVal: Number = 5
console.log(myVal.add(7))
TS Playground Link
If you want to invoke functions on an object at this point create a dedicated class. Why? Because you have structured zone where you can add/remove/edit and reuse in other part of code.
Sorry, i write in JS but you can change with no effort in TypeScript
Create Class Element
class CustomArray {
constructor() {
this.arr = [];
}
add(value) {
this.arr.push(value)
}
erase(){
this.arr = [];
}
// so on...
print(){
console.log(this.arr)
}
}
//I use Module, so i will use require.
module.exports = CustomArray;
The above class is simplest example. You can decorate with other functions. In theory as many as you want
FOR USE INSIDE OTHER CONTEXT
const CustomArray = require("./CustomArray");
var arr = new CustomArray();
arr.add(2)
arr.print()

Is there someone who may explain can explain the behaviour of this javascript class?

class Group {
constructor() {
this.members = [];
}
add(value) {
if (!this.has(value)) {
this.members.push(value);
}
}
delete(value) {
this.members = this.members.filter(v => v !== value);
}
has(value) {
return this.members.includes(value);
}
static from(collection) {
let group = new Group;
for (let value of collection) {
group.add(value);
}
return group;
}
}
let group = Group.from([10, 20]);
console.log(group.has(10));
// → true
console.log(group.has(30));
// → false
group.add(10);
group.delete(10);
console.log(group.has(10));
First, I don't understand how this code with class create a new instance of the Group class without using the new operator
Second, I also have problem with the way the from method works
This is almost identical to Set class, set is a list of non-duplicate values together.
In mathematics, a set is a well-defined collection of distinct objects, considered as an object in its own right.
Just means that there may not be duplicate values*
let set = new Set([1, 2, 3, 3])
// Set(3) {1, 2, 3}
set.add(3)
// Set(3) {1, 2, 3}
set.add(4)
// Set(4) {1, 2, 3, 4}
*however values that are accessed by reference (like arrays or objects) will cause duplicate values, because references are not compared by value, but by address in RAM
let set = new Set([[1], [1]])
// Set(2) {[1], [1]}
Hence same references will be filtered out
let ref = [1]
new Set(ref, ref)
// Set(1) {1}
The static keyword defines a static method or property for a class. Neither static methods nor static properties can be called on instances of the class. Instead, they're called 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.
Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static
Emphasis added by me.
There is no need to call the class constructor to be able to call a static method. The static method from does create a new instance via the new keyword (which in turn calls the constructor that initializes a new instance with a corresponding members property).

JavaScript get/set properties, both must be defined for inherited classes [duplicate]

This question already has an answer here:
Javascript, extending ES6 class setter will inheriting getter
(1 answer)
Closed 3 years ago.
I stumbled onto a peculiarity with JavaScript, not sure if it is by design or a bug in the Chrome browser. If the base class has both a get and set property accessor with the same name, the inherited class can not override just one, it must override both.
class Class1 {
constructor() {
this._item = 1;
}
set item(value) {
this._item = value;
}
get item() {
return this._item;
}
}
class Class2 extends Class1 {
constructor() {
super();
this._item = 2;
}
set item(value) {
this._item = value;
}
}
let c1 = new Class1();
let item1 = c1.item; // item1 is 1
console.log(item1);
let c2 = new Class2();
let item2 = c2.item; // item2 should be 2, but is undefined
console.log(item2);
I believe this is correct.
The class prototype just has a single property named item. This property has a descriptor, which can include get and set properties, which are used as the getter and setter. If either of these properties is missing, that operation is performed normally rather than by calling the function.
Inheritance through the prototype chain is just used to find the property and its descriptor, there's no separate search for the individual properties in the descriptor. When you define the setter in Class2, that creates a new property in that prototype, with its own descriptor. This shadows the entire descriptor that would have been inherited from Class1.

Javascript "new" keyword making references to "class" properties

I have a javascript "class", that I instanciate several times.
My issue is that modifying properties from the instances does actually modify those properties of the "class", so further instances are not initialized in the way I'd like.
Here is a simplified snippet of what happens, the original code is using BackboneJS but this is enough to illustrate my case:
var foo = function() {
this.defaults.id = 1;
};
// This is my property
foo.prototype.defaults = {
id: 0,
};
console.log( foo.prototype.defaults ); // => {id: 0}
var bar = new foo();
console.log( foo.prototype.defaults ); // => {id: 1}
I thought that the "new" keyword would make a brand new object but it seems that it just makes a reference to the prototype properties.
The best option I came with is to clone all properties from inside the constructor...
Do anyone have a clean way to achieve a clean "Class Property" behavior ?
Many thanks in advance,
Romain.
Edit: It does not work from the SO snippet... just copy/paste it to your console if you want.
The prototype is shared among all objects of this type. A new copy is not made. The usual way to initialize properties that are to be unique to this instance is to initialize them directly in the constructor:
this.property = "foo";
This will override any property of the same name in the prototype and will be unique per instance.
In general, the prototype is not used for data properties because they are shared among all instances. If you want to initialize an object with a default set of data properties, the "best practice" for that is to assign a default set of properties into the object instance and then set any overrides. You can't just assign a default object because objects are assigned by pointer, not by copy so all objects would still be pointing at the same object (the same issue you have when using the prototype).
You could initialize your object with a set of defaults like this:
var foo = function() {
this.defaults = {};
// copy default properties
for (var prop in foo.defaults) {
this.defaults[prop] = foo.defaults[prop];
}
this.defaults.id = 1;
};
// defaults (not on the prototype)
foo.defaults = {
id: 0,
something: "whatever",
line: 22
};
Though, in practice, it is usually easier to just code in the defaults like this:
var foo = function() {
this.options = {
id: 1,
something: "whatever",
line: 22
};
};

javascript initializing object attributes?

I'm trying to write a wrapper on an array and I've come up with code below:
myList = function () { };
myList.prototype.innerArray = [];
myList.prototype.add = function(pt) { this.innerArray.push (pt); return this; };
For every object myList i create, I hope to get an empty attribute innerArray. But I'm afraid i haven't really understood the concept of prototypes yet, because:
m = new myList().add(4).add(5);
m.innerArray.length;
returns 2, so far so good, but now I do:
j = new myList();
j.innerArray.length;
which returns also 2, and I would have expected 0 (a fresh new innerArray); I'm afraid i'm missing something fundamental.
You don't want to use prototype with innerArray. In your constructor simply do this.innerArray = [] and now you have an instance variable. By using prototype you are creating a class attribute (e.g. shared amongst class instances), not an instance variable (e.g. unique to an instance of the class).
All instances of myList share the exact same prototype object. It is not copied or cloned to each instance. For functions this is fine because when you call foo.add() then this is the instance, allowing you to fetch per instance values. foo.add and bar.add are the same function object but this is different in each call. But with values directly on the prototype when you change them they change for all instances, since they all point to the same objects.
Instead you want to make a new array in the constructor:
myList = function () {
this.innerArray = [];
};
myList.prototype.add = function(pt) {
this.innerArray.push (pt);
return this;
};
You should do
myList = function () { this.innerArray = []; };
as this:
myList.prototype.innerArray = [];
creates innerArray variable common to all instances.

Categories