If you really need an instance of Actors
to be callable, then that constructor must return a function object. There are essentially two ways to do that:
The constructor creates a local function, assigns all other properties to that object, and returns it. To make sure that this function object identifies as an Actors
instance, you would need to change its prototype from Function
to Actors
.
Make Actors
a subclass of Function
, and call super
to make sure the instance executes the code we need it to execute. If we want that code to dynamically access the other properties of the instance, we must overcome the fact that the function will be called without specific this
binding. So either:
- we need to bind that function beforehand and let the constructor return that function, or
- we get a reference to the executing function itself, using the deprecated
arguments.callee
reference.
Either way, it is going to be ugly, but I suppose this was a code challenge. Still, you should not really use this pattern in serious coding.
There is another obstacle here: name
is a read-only property of the Function prototype, and so you cannot just assign it a new value with a property accessor. Instead you need to be explicit in stating that you want the instance to get its own property with that name.
1. Return local function object
Here is a working solution using the first option:
class Actors {
constructor(obj) {
const Avenger = () => `${Avenger.alias.toUpperCase()}\n${Avenger.powers.join("\n")}`;
Avenger.toString = () => `name:${Avenger.name} \ngender:${Avenger.gender}\nage:${Avenger.age}`;
const {name, ...rest} = obj;
Object.defineProperty(Avenger, "name", {value: name});
Object.assign(Avenger, rest);
Object.setPrototypeOf(Avenger, Actors.prototype);
return Avenger;
}
}
const act1= new Actors({
name: 'Tony Stark',
alias: 'Iron Man',
gender: 'man',
age: 38,
powers: ["intelligence", "durability", "magnetism"]
})
const act2= new Actors({
name: 'Natasha Romanoff',
alias: 'Black Widow',
gender: 'woman',
age: 35,
powers: ["agility", "martial arts"]
})
const examine = (avenger) => {
console.count('Actors');
console.group('*** Actors introduced ***');
console.log(avenger.toString());
console.groupEnd();
console.group('*** Actors called ***');
console.log(avenger());
console.groupEnd();
console.group('*** Actors\'s internals ***');
console.log(avenger, '\n');
console.groupEnd();
console.log(avenger instanceof Actors);
}
examine(act1);
examine(act2);
Note that Stack Snippets has its own implementation of console
, and when you run the above snippet, the output of console.log(avenger)
is not the source of the function, like you will get elsewhere (Chrome, Firefox, ...).
2. Subclass Function
Here is a possible implementation of the second option I listed above.
Advantages:
- Does not alter prototypes of existing objects
- Constructor does not return a different object, but
this
- As a consequence of the previous point:
Actors
prototype methods can be used on the instance
Disadvantages:
Function
constructor is called -- necessarily with a string argument, which leads to runtime code parsing every time the constructor is called (unless the engine applies some optimisation).
- Deprecated
arguments.callee
is used. This is needed because the function will be called without this
binding, and so the only available reference to the instance is this callee
reference.
- The source of the function will show an anonymous function, not one with the name
Avenger
(a request that was clarified in comments).
Here it is:
class Actors extends Function {
constructor(obj) {
super(`return arguments.callee.avenger()`);
let {name, ...rest} = obj;
Object.defineProperty(this, "name", {value: name});
Object.assign(this, rest);
}
toString() {
return `name: ${this.name}\ngender: ${this.gender}\nage: ${this.age}`;
}
avenger() {
return `${this.alias.toUpperCase()}\n${this.powers.join("\n")}`;
}
}
const act1= new Actors({
name: 'Tony Stark',
alias: 'Iron Man',
gender: 'man',
age: 38,
powers: ["intelligence", "durability", "magnetism"]
})
const act2= new Actors({
name: 'Natasha Romanoff',
alias: 'Black Widow',
gender: 'woman',
age: 35,
powers: ["agility", "martial arts"]
})
const examine = (avenger) => {
console.count('Actors');
console.group('*** Actors introduced ***');
console.log(avenger.toString());
console.groupEnd();
console.group('*** Actors called ***');
console.log(avenger());
console.groupEnd();
console.group('*** Actors\'s internals ***');
console.log(avenger, '\n');
console.groupEnd();
console.log(avenger instanceof Actors);
}
examine(act1);
examine(act2);
None of the available options represent good coding practice. There is no good reason why you would want a constructor to behave as you require in the question, except when this answers some nifty code challenge, which has no other purpose than that.