Home > database >  Extend Function in JavaScript with ES6 class syntax
Extend Function in JavaScript with ES6 class syntax

Time:02-03

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:

  1. Copy own properties, because it is a new object
  2. 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'));

  • Related