1

I was attempting to write a proof of concept for a class that extends Function in order to demonstrate a function constructor that could be initialized based on another function, and reflect this and arguments in the following manner:

class Handle extends Function {
  constructor(functor) {
    super("functor", "slice", `
      "use strict";
      return functor
        .call(this, ...slice.call(arguments, 2));
    `);

    return this.bind(this, functor, Array.prototype.slice)
  }
}

let handle = new Handle(function test() {
  console.log(this instanceof Handle, arguments.length)
})

console.log(handle instanceof Handle, handle.length)

handle(1, 2, 3)

However, I thought that this would produce identical behavior based on my understanding of call and apply:

class Handle extends Function {
  constructor(functor) {
    super("instance", "call", "slice", `
      "use strict";
      return call(this, instance, ...slice.call(arguments, 3));
    `);

    return Function.bind
      .call(this, functor, this, Function.call, Array.prototype.slice)
  }
}

let handle = new Handle(function test() {
  console.log(this instanceof Handle, arguments.length)
})

console.log(handle instanceof Handle, handle.length)

handle(1, 2, 3)

This throws

Uncaught TypeError: call is not a function
  at Function.eval (eval at Handle (js:4), <anonymous>:5:14)

so it's something wrong with the call() function. My understanding was that if call() wasn't part of a call expression, its first argument would become the function being called and the rest of the arguments would then become the context and the arguments of the function, sort of like Function.call.call(this, instance, ...slice.call(arguments, 3)) would do:

class Handle extends Function {
  constructor(functor) {
    super("instance", "call", "slice", `
      "use strict";
      return Function.call.call(this, instance, ...slice.call(arguments, 3));
    `);

    return Function.bind
      .call(this, functor, this, Function.call, Array.prototype.slice)
  }
}

let handle = new Handle(function test() {
  console.log(this instanceof Handle, arguments.length)
})

console.log(handle instanceof Handle, handle.length)

handle(1, 2, 3)

Can someone explain what I'm misunderstanding, or why this does not appear to be the case?

Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
  • Have a look [here](https://stackoverflow.com/a/36871498/1048572) for a much easier way to extend `Function` – Bergi Jun 27 '17 at 03:05
  • @Bergi while I upvoted your answer since it was interesting, it doesn't exactly make use of inheritance. Other than enforcing its use as a constructor, your `ExtensibleFunction` basically discards the `this` that is instantiated, which seems to side-step the issue rather than solving it in a "satisfying" way, to quote you. – Patrick Roberts Jun 27 '17 at 03:14
  • No, there is no `this` to be discarded since it's never instantiated when we don't call `super` - and we don't want to call `super` as that means parsing and evaling a function source string every time an instance is created. – Bergi Jun 27 '17 at 03:19
  • @Bergi oh, that's interesting... I never realized `super()` caused the instantiation, I just thought that ES6 had some weird convention where it had to be first, if the constructor of an extended class was explicit. – Patrick Roberts Jun 27 '17 at 03:20

2 Answers2

1

You’re calling Function.call (Function.prototype.call would be more correct to use, I think) without a this. It needs one, though, because the this value is the function it calls. A shorter example of the issue:

var call = Function.prototype.call;
call();

Maybe you intended call.call(this, …)?

class Handle extends Function {
  constructor(functor) {
    super("instance", "call", "slice", `
      "use strict";
      return call.call(this, instance, ...slice.call(arguments, 3));
    `);

    return Function.bind
      .call(this, functor, this, Function.call, Array.prototype.slice)
  }
}

let handle = new Handle(function test() {
  console.log(this instanceof Handle, arguments.length)
})

console.log(handle instanceof Handle, handle.length)

handle(1, 2, 3)
Ry-
  • 218,210
  • 55
  • 464
  • 476
  • `call.call(...)`? _That_ was the problem all along? *facepalm* But `Function.call` and `Function.prototype.call` are equally valid, since `Function instanceof Function`, so it will have the member method `call`, being an instance of itself. – Patrick Roberts Jun 27 '17 at 03:08
  • @PatrickRoberts: Right; I just think it’s misleading, like passing `{}.toString.call` or something. Luckily, there’s no need, since you can just use your original method. – Ry- Jun 27 '17 at 03:14
  • I know you answered first, and I did upvote both answers, but I like Bergi's explanation better. It gets to the point really well, and in addition, I think some observations in your answer are slightly opinionated. This doesn't make it incorrect, I just think Bergi's answer is more tailored to my style, if that makes sense. – Patrick Roberts Jun 27 '17 at 03:27
1
Uncaught TypeError: call is not a function

so it's something wrong with the call() function.

Notice that this message is misleading, it's not call that is not a function but the thing that call is trying to call.

My understanding was that if call() wasn't part of a call expression, its first argument would become the function being called

No. The function being called is always the this context of the call invocation, and there's nothing that changes this when call is not invoked as a method. You need to do call.call(functor, context, ...args).

Bergi
  • 630,263
  • 148
  • 957
  • 1,375