Given the following:
export class MyClass {
public dataA = 0
private dataB = 123
public myMethod(): any {
return {
test: 'true'
}
}
constructor() {
for (const propOrMethod in this) {
console.log({propOrMethod})
}
}
}
const myInst = new MyClass()
I run this with ts-node index.ts and all i get is:
{ propOrMethod: 'dataA' }
{ propOrMethod: 'dataB' }
With no reference to myMethod. I would like to iterate over all the methods of my class, but they don't appear to exist
for..in iterates over all enumerable properties of the instance and up the prototype chain. But normal methods in a class are not enumerable:
class MyClass {
myMethod() {
return {
test: 'true'
};
}
}
console.log(Object.getOwnPropertyDescriptor(MyClass.prototype, 'myMethod').enumerable);
So it doesn't get iterated over.
If you want to iterate over non-enumerable properties as well, use Object.getOwnPropertyNames (which iterates over the object's own property names, so you'll need to do so recursively if you want all property names anywhere in the prototype chain):
const recurseLog = obj => {
for (const name of Object.getOwnPropertyNames(obj)) {
console.log(name);
}
const proto = Object.getPrototypeOf(obj);
if (proto !== Object.prototype) recurseLog(proto);
};
class MyClass {
dataA = 0;
dataB = 123;
constructor() {
recurseLog(this);
}
myMethod() {
return {
test: 'true'
};
}
}
const myInst = new MyClass();
You could also make the method enumerable:
class MyClass {
dataA = 0;
dataB = 123;
constructor() {
for (const propOrMethod in this) {
console.log({propOrMethod})
}
}
myMethod() {
return {
test: 'true'
};
}
}
Object.defineProperty(MyClass.prototype, 'myMethod', { enumerable: true, value: MyClass.prototype.myMethod });
const myInst = new MyClass();
Or assign the method after the class definition:
class MyClass {
dataA = 0;
dataB = 123;
constructor() {
for (const propOrMethod in this) {
console.log({propOrMethod})
}
}
}
MyClass.prototype.myMethod = () => ({ test: 'true' });
const myInst = new MyClass();
Or assign it to the instance in the constructor:
class MyClass {
dataA = 0;
dataB = 123;
constructor() {
this.myMethod = this.myMethod;
for (const propOrMethod in this) {
console.log({propOrMethod})
}
}
myMethod() {
return {
test: 'true'
};
}
}
const myInst = new MyClass();
Related
This question already has answers here:
How to extend Function with ES6 classes?
(12 answers)
Closed 17 days ago.
My attempt:
class SetOnceDict extends Function {
constructor() {
super('key', 'return this.get(key);')
}
items = {}
add(key, value) {
if (!this.items.hasOwnProperty(key)) {
this.items[key] = value;
} else {
throw new Error(`Duplicate key ${key}`);
}
}
get(key) {
return this.items[key];
}
}
let dict = new SetOnceDict();
dict.add('one', 'foo');
dict.add('two', 'bar');
console.log(dict.items);
console.log(dict('one'));
I'd expect this to log
{ one: 'foo', two: 'bar' }
foo
instead it errors
return this.get(key);
^
TypeError: this.get is not a function
This works:
console.log(dict.bind(dict)('one'));
But why would it have to be bound to itself when it should already have those properties?
Surprisingly (?) neither super.bind(this); nor this.bind(this); in the constructor fix the problem.
How can I make an extension of function using ES6+ class syntax that has custom behaviour when called?
Surprisingly (?) neither super.bind(this); nor this.bind(this); in the constructor fix the problem.
You can call bind in constructor. But it will create a new function so you need to:
Copy own properties, because it is a new object
Return the result.
class SetOnceDict extends Function {
constructor() {
super('key', 'return this.get(key);')
const out = this.bind(this) // create binded version
Object.assign(out, this) // copy own properties
return out // replace
}
items = {}
add(key, value) {
if (!this.items.hasOwnProperty(key)) {
this.items[key] = value;
} else {
throw new Error(`Duplicate key ${key}`);
}
}
get(key) {
return this.items[key];
}
}
let dict = new SetOnceDict();
dict.add('one', 'foo');
dict.add('two', 'bar');
console.log(dict.items);
console.log(dict('one'));
Also you can use a Proxy :)
class SetOnceDict extends Function {
constructor() {
super('key', 'return this.get(key);')
return new Proxy(this, {
apply(target, thisArg, args) {
return target.call(target, ...args)
}
})
}
items = {}
add(key, value) {
if (!this.items.hasOwnProperty(key)) {
this.items[key] = value;
} else {
throw new Error(`Duplicate key ${key}`);
}
}
get(key) {
return this.items[key];
}
}
let dict = new SetOnceDict();
dict.add('one', 'foo');
dict.add('two', 'bar');
console.log(dict.items);
console.log(dict('one'));
I came up with another ugly way that works using Object.assign and bind:
class SetOnceDict extends Function {
constructor() {
super('key', 'return this.get(key);');
return Object.assign(this.bind(this), this);
}
items = {}
add(key, value) {
if (!this.items.hasOwnProperty(key)) {
this.items[key] = value;
} else {
throw new Error(`Duplicate key ${key}`);
}
}
get(key) {
return this.items[key];
}
}
let dict = new SetOnceDict();
dict.add('one', 'foo');
dict.add('two', 'bar');
console.log(dict.items);
console.log(dict('one'));
this does not actually refer to the function object itself within function bodies. To do that you need arguments.callee, although this is deprecated.
class SetOnceDict extends Function {
constructor() {
super('key', 'return arguments.callee.get(key);')
}
items = {}
add(key, value) {
if (!this.items.hasOwnProperty(key)) {
this.items[key] = value;
} else {
throw new Error(`Duplicate key ${key}`);
}
}
get(key) {
return this.items[key];
}
}
let dict = new SetOnceDict();
dict.add('one', 'foo');
dict.add('two', 'bar');
console.log(dict.items);
console.log(dict('one'));
I am trying to make a stack and queue classes, but I cant make the data field private without not being able to use inheritance.
I get an Uncaught SyntaxError: Private field '#data' must be declared in an enclosing class error every time I try.
how can I have the subclasses inherit the private field? code below:
class Datalist {
#data
constructor() {
this.#data = Array.from(arguments)
return this.#data
}
valueOf() {
return this.#data
}
get size() {
return this.#data.length
}
peek() {
if (this.size > 0) {
return this.#data[0]
} else {
return null
}
}
}
class Queue extends Datalist {
constructor() {
super(arguments)
}
enqueue() {
this.#data = this.#data.concat(arguments)
}
dequeue() {
return this.#data.shift()
}
}
class Stack extends Datalist {
constructor() {
super(arguments)
this.#data = this.#data.reverse()
}
push() {
this.#data = this.#data.splice(0, 0, Array.from(...arguments).reverse)
}
pop() {
return this.#data.shift()
}
}
A possible workaround which keeps the approach of extended classes and prototypal methods together with private fields and protected data could be based on WeakMap because one can take advantage of working with a single shared reference for each instantiation regardless of the actual inheritance.
const privateListLookup = new WeakMap;
const getPrivateList = reference =>
privateListLookup.get(reference);
const getClone = value =>
(typeof structuredClone === 'function')
? structuredClone(value)
: JSON.parse(JSON.stringify(value));
// const getCopy = value => [...value];
class DataList {
constructor(...list) {
// enable shared privacy via an instance's
// `this` reference and a weak map.
privateListLookup.set(this, list);
}
valueOf() {
// ensure data protection by not exposing
// the private `list` reference directly.
return getClone(getPrivateList(this));
// // make a decision, clone or shallow copy.
// return getCopy(getPrivateList(this));
}
toString() {
return String(getPrivateList(this));
}
// toJSON() {
// return JSON.stringify(getPrivateList(this));
// }
get size() {
return getPrivateList(this).length;
}
peek() {
return (this.size > 0)
// ? getPrivateList(this).at(0)
? getPrivateList(this)[0]
: null;
}
}
class Queue extends DataList {
constructor(...args) {
super(...args);
}
enqueue(...args) {
getPrivateList(this).push(...args);
}
dequeue() {
return getPrivateList(this).shift();
}
}
class Stack extends DataList {
constructor(...args) {
super(...args);
getPrivateList(this).reverse();
}
push(...args) {
getPrivateList(this).push(...args);
}
pop() {
return getPrivateList(this).pop();
}
}
const queue = new Queue(...['the', 'quick', 'brown', 'fox']);
const stack = new Stack('jumps', 'over', 'the', 'lazy', 'dog');
console.log({
queue: queue.valueOf(),
stack: stack.valueOf(),
});
console.log({
queue: queue.toString(),
stack: stack.toString(),
});
console.log(
'queue.enqueue(stack.pop()) ...'
);
queue.enqueue(stack.pop());
console.log({
queue: queue.toString(),
stack: stack.toString(),
});
console.log(
'queue.enqueue(stack.pop(), stack.pop()) ...'
);
queue.enqueue(stack.pop(), stack.pop());
console.log({
queue: queue.toString(),
stack: stack.toString(),
});
console.log(
'stack.peek() ...', stack.peek()
)
console.log(
'stack.push(queue.dequeue(), queue.dequeue()) ...'
);
stack.push(queue.dequeue(), queue.dequeue());
console.log({
queue: queue.toString(),
stack: stack.toString(),
});
console.log({
queue: queue.valueOf(),
stack: stack.valueOf(),
});
.as-console-wrapper { min-height: 100%!important; top: 0; }
I can defined readFoo in Foo class:
var myFormat = 'foo'
class Foo {
[ "read" + ((format) => format)(myFormat) ]() {
return 123;
}
}
is there any way how to define function base on config like:
var config = ['foo1', 'foo2']
class Foo {
config.map((name) => {
[ "read" + ((format) => format)(name) ]() {
return 123;
}
}
}
Will create functions readFoo1 and readFoo2.
You can iterate through the array and assign to the prototype afterwards:
var config = ['foo1', 'foo2']
class Foo {}
for (const name of config) {
Foo.prototype["read" + name] = function() {
return 123;
};
}
const f = new Foo();
console.log(f.readfoo1());
I want to add functions to JSON Objects, but I can't find function for assigning to all objects, only to one.
This code works with Arrays:
Object.defineProperty(Array.prototype, 'random', {
value: () => {
return this[Math.floor(Math.random() * this.length)];
},
});
I've also found this code:
const obj = {name: 'Bob'};
obj.fullName = function() { return this.name }
But that one only works for specific object, not all of them.
Is it event possible to write global functions for all JSON Objects, and if is, then how to do it?
You could add the function to Object.prototype. Note that this is not considered a very good practice because it could impact the rest of the code (like shown in the comments):
Object.prototype.fullName = function() { return this.name; };
const obj = { name: 'Bob' };
console.log(obj.fullName());
You should consider doing this instead:
const baseObject = { fullName: function() { return this.name; } };
const obj = Object.create(baseObject, { name: { value: 'Bob', writable: true } });
console.log(obj.fullName());
And if your target runtime (browser?) supports ECMAScript 6, you could also create a dedicated class for this:
class MyClass {
constructor(name) {
this.name = name;
}
fullName() { return this.name; }
}
const bob = new MyClass('Bob');
console.log(bob.fullName());
Finally, the class syntax for ECMAScript 5:
function MyClass(name) {
this.name = name;
}
MyClass.prototype.fullName = function() { return this.name; }
const bob = new MyClass('Bob');
console.log(bob.fullName());
Look at the below example:
class Parent {
constructor({ parentOnlyArg = 'default value' } = {}) {
this.parentOnlyArg = parentOnlyArg;
}
}
class Child extends Parent {
// this class and also any class inheriting from it
constructor({ visibleStyle = 'inline' } = {}) {
// I want to pass argument to super as an object
super(/** args **/);
this.visibleStyle = visibleStyle;
}
}
class Child2 extends Parent {
// Specifying parentOnlyArg as default will change the behaviour
constructor({ parentOnlyArg = 'another parent value',
someOther = 'value' } = {}) {
// I want to pass argument to super as an object
super(/** args **/);
this.someOther = someOther;
}
}
Is it possible to pass on the constructor argument to super?
Seems like it was simpler than I thought
super(...arguments);
I can then create Child using
var c1 = new Child(); // c.parentOnlyArg = 'default value'
var c2 = new Child2(); // c.parentOnlyArg = 'another parent value'
var c3 = new Child({ parentOnlyArg: 'changed again' }); // c.parentOnlyArg = 'changed again'
You could use object destructuring with rest properties. It is not yet implemented by browsers, but BabelJs can transpile it.
function assertEmpty(obj) {
if (Object.keys(obj).length > 0) {
throw new Error("Unexpected parameters");
}
}
class A {
constructor({ a = "foo", ...rest } = {}) {
assertEmpty(rest);
console.log("new A " + a);
}
}
class B extends A {
constructor({ b = "bar", ...rest } = {}) {
super(rest);
console.log("new B " + b);
}
}
new B({a:2}); // prints 'new A 2', 'new B bar'
new B({a:4, b:5, c:6}); // throws 'Unexpected parameters'
In the above snippet parent classes don't see the params consumed by the descendants. If you have problems with that you can do it either as #Bergi or #loganfsmyth suggested. For example:
class A {
constructor(params = {}) {
const { a = "foo" } = params;
console.log("a=" + a);
}
}
class B extends A {
constructor(params = {}) {
const { b = "bar" } = params;
super(params);
console.log("b=" + b);
}
}
new B({a:2}); // prints a=2 b=bar
new B({b:5}); // prints a=foo b=5
A quick-win is to use the arguments object. It is an array containing all parameters passed to a function.
More information on the MDN.
In practice, you can access to the first parameter of your function thanks to arguments[0].
class Child extends Parent {
constructor({ parentOnlyArg = 'value',
visibleStyle = 'inline' } = {}) {
super(arguments[0]);
[...]
}
}