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?
CodePudding user response:
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'));
CodePudding user response:
this
does not actually refer to the function object itself within function bodies. To do that you need arguments.callee, although this is becoming deprecated and I don't know how else to work around it.
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'));
CodePudding user response:
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'));