1

I noticed something curious earlier today. I can't seem to store a reference to the call property of a function, then execute it. Example:

var log = console.log;
log.call(console, 'This works'); 

var logCall = console.log.call;
logCall(console, 'This does not');

To me, this seems like perfectly legal Javascript, but the second invocation always gives me the error that undefined is not a function. Feel free to play around with it here, you'll get the same results.

So why does Javascript prevent me from calling call in this manner?

EDIT: I finally got it straight in my head after reading SimpleJ's answer. So I'm going to update this with how you can get the above to work:

var log = console.log;
log.call(console, 'This works'); 

var logCall = console.log.call;
logCall.call(console.log, console, 'This works now too');

The problem was that console.log was receiving the proper this value, but console.log.call wasn't given a proper this value. So as you can see, I basically had to execute console.log.call.call. Obviously you'd never really use code like this, I was just curious.

GJK
  • 37,023
  • 8
  • 55
  • 74
  • When you stored console.log.call in a variable, it lost reference to console.log, meaning it no longer knew what to execute the arguments on. I would expect your first example to fail in some browsers due to that fact (depending on how console.log is defined by the browser being used) – Kevin B Jun 12 '14 at 18:44
  • It's related to how `this` works: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this . Since you are calling the function "normally" (`logCall()`), `this` either refers to `window` or is `undefined`. `Function.prototype.call` doesn't know which function to act upon. – Felix Kling Jun 12 '14 at 18:45
  • Yep. Neither example works in Chrome – Carlos Rodriguez Jun 12 '14 at 18:45
  • FWIW, the first example work in Chrome 35. *edit:* Uh, the jsBin works, but the it doesn't if I executed it directly in the console o_O – Felix Kling Jun 12 '14 at 18:46
  • I know that the first example works because Ember.js uses similar code everywhere (which is what I was working with when I found this). – GJK Jun 12 '14 at 18:47
  • Tested on Chrome 37 canary, the first method does not work, but `log.call(console, 'This works');` does. – p.s.w.g Jun 12 '14 at 18:48
  • It works, depending on how the function was defined. If the function makes use of `this` in a specific way, it will fail because `this` will be `null` instead of `console` and will not have any of the methods defined on `console` – Kevin B Jun 12 '14 at 18:48
  • @KevinB: it fails if the method makes light use of _this_ as well... – dandavis Jun 12 '14 at 18:49
  • 2
    *"So why does Javascript prevent me from calling call in this manner?"* Again, learn how `this` works: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this . This is a very common question with a simple explanation. – Felix Kling Jun 12 '14 at 18:49
  • the 2nd one dones't fail because of anything relating to _this_, it fails because null is not a function. – dandavis Jun 12 '14 at 18:50
  • @FelixKling: I know how `this` works. What I don't understand is why it's affect this apparently legal function call. If you switch out `console.log` with a function that doesn't access `this` at all, you still get the same issue. – GJK Jun 12 '14 at 18:53
  • Do you have an example? – Kevin B Jun 12 '14 at 18:54
  • Of what I just mentioned? Sure. `var foo = function() { alert() };var fooCall = foo.call;fooCall(null)`. – GJK Jun 12 '14 at 18:54
  • @GJK: `.call` uses `this` (presumably) to refer to the function it has to call! – Felix Kling Jun 12 '14 at 18:55
  • @GJK: call expects a function to be this, or if un-bound, the first argument. – dandavis Jun 12 '14 at 18:55
  • `fooCall.call(foo)` does, effectively setting `this` back to `foo` – Kevin B Jun 12 '14 at 18:57
  • @FelixKling, I see what you're saying now. There's two different `this` variables and we weren't on the same page. – GJK Jun 12 '14 at 18:57
  • @GJK: It's the same concept though. The equivalent to `foo.bar()` is `bar.call(foo)` or `bar.bind(foo)()`, no matter which function you are working with. – Felix Kling Jun 12 '14 at 18:58
  • So given `baz.call()`, you either need to do `call.call(bar)` or `call.bind(bar)()`. – Felix Kling Jun 12 '14 at 19:04

2 Answers2

3

You need to keep the binding to console. Try this:

var logCall = console.log.call.bind(console.log);
// example: logCall(console, "foobar");

or

var log = console.log.bind(console);
// example: log("foobar");

For a bound reference to log.

Edit: jsfiddle: http://jsfiddle.net/67mfQ/2/

SimpleJ
  • 13,812
  • 13
  • 53
  • 93
  • I was thinking along the same lines, but this produces `TypeError: Illegal invocation` when you try to run it.... – Ethan Brown Jun 12 '14 at 18:50
  • 1
    That's because it's binding call. The first argument to `call` is a context which should be `console`. – SimpleJ Jun 12 '14 at 18:51
  • @SimpleJ: You were right if `.bind` was called on `console.log`. But it's called on `console.log.call`. – Felix Kling Jun 12 '14 at 18:51
  • To make it work everywhere, you would might have to write `console.log.call.bind(console.log, console)` – Felix Kling Jun 12 '14 at 18:52
  • @FelixKling I assumed he was trying to reference `call` not `log`. – SimpleJ Jun 12 '14 at 18:52
  • @FelixKling You can just use `console.log.bind(console)` to get a bound reference to `log`. – SimpleJ Jun 12 '14 at 18:53
  • @SimpleJ: But not to `call`, which is what the question was about I though. – Felix Kling Jun 12 '14 at 18:54
  • My brain hurts a little, but I see my mistake now. It's not that `console.log` needs a `this` value, it's that `console.log.call` needs a `this` value. I giving the former a `this` value, but not that latter. Thank you for your answer. – GJK Jun 12 '14 at 19:10
3

This is my favorite code in JavaScript:

var bind = Function.bind;
var call = Function.call;

var bindable = bind.bind(bind);
var callable = bindable(call);

You can use the bindable function to grab a reference to f.bind. Similarly you can use the callable function to grab a reference to f.call as follows:

var log = callable(console.log, console);

Now all you need to do is call the log function like any other function:

log("Hello World!");

That's all folks.

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • 1
    how is "callable(console.log, console);" any better than "console.log.bind(console);" ? – dandavis Jun 12 '14 at 18:54
  • Don't forget: [Explain bindbind() function](http://stackoverflow.com/q/13504936/1048572) – Bergi Jun 12 '14 at 18:54
  • @dandavis: It would work even if `console.log` was not a `Function` instance (such can happen in IE DOM), but you're right - it's only more confusing. – Bergi Jun 12 '14 at 18:55
  • @dandavis In 5 lines of code you got three useful functions - `bindable`, `callable` and `log`. In addition I feel the `callable` function is more descriptive than `object.method.bind(object)`. To me that looks very ugly. – Aadit M Shah Jun 12 '14 at 18:55