60

I am using JavaScript and getting a problem with the statement

console.log.bind(console)

Please tell me what does this statement actually do. I have applied this several times but it did not do anything.

Ondra Žižka
  • 36,997
  • 35
  • 184
  • 250
shubham
  • 1,119
  • 1
  • 11
  • 26

4 Answers4

94

In JavaScript, this within a function call is determined by how the function is called (for normal functions, see * below). If it's called as part of an expression retrieving an object property (e.g., foo.bar() calls bar() as part of a property retrieval operation getting it from foo), this is set to the object that the property came from during the call to the function.

Suppose you wanted a shorter form of console.log, like f. You might do this:

var f = console.log; // <== Suspect!

...but if the log function relies on this referring to the console object during the call, then calling f("Message here") won't work, because this won't refer to console.

Function#bind is for just that situation: It lets you create a new function that, when called, will call the original with this set to the value you give. So

var f = console.log.bind(console); // Still suspect, for a different reason

...should, in theory, give you a function, f, that you can call to log to the console.

Except: Host-provided functions like console.log (and alert and getElementById) aren't required to be "real" JavaScript functions (although on modern browsers they tend to be, or at least very close), and aren't required to have all of their features, inculding bind. So if you're getting an error on that line, it may be that the engine you're using that line on doesn't support bind on the console.log function.

So what are "host-provided functions"? Any function not explicitly defined in the specification as being part of JavaScript, the language. So again, on a browser that's browser-related functions like alert or console.log and such.

I can think of two reasons that line might be giving you trouble:

  1. The above: You're using a JavaScript engine that doesn't make console.log a real function.

  2. You're using the line above on IE with the Dev Tools closed. On IE when the dev tools aren't open, the console object isn't defined, and so that line will throw a ReferenceError.

If the end goal is to get a function you can call, say f("Message here"), for console.log, here's how you can do that dealing with both #1 and #2 above:

function f(item) {
    if (typeof console != "undefined" && console.log) {
        console.log(item);
    }
}

That only lets you give one item, whereas console.log lets you give multiple items (console.log("this", "that", "and the other")), but if console.log may not be a real JavaScript function, then it may not have Function#apply, which makes it very hard to wrap it.

Now, if you don't care about getting the same output you'd get from console.log("this", "that", "and the other") so long as you can see what's there, simply use console.log(arguments); (arguments is the built-in identifier for all arguments passed into a function). But if you want to replicate the exact output, you end up doing something like this:

function f() {
    var a = arguments;

    if (typeof console != "undefined" && console.log) {
        if (console.log.apply) {
            // It has Function#apply, use it
            console.log.apply(console, arguments);
        } else {
            // Ugh, no Function#apply
            switch (a.length) {
                case 0: console.log(); break;
                case 1: console.log(a[0]); break;
                case 2: console.log(a[0], a[1]); break;
                case 3: console.log(a[0], a[1], a[2]); break;
                case 4: console.log(a[0], a[1], a[2], a[3]); break;
                case 5: console.log(a[0], a[1], a[2], a[3], a[4]); break;
                default:
                    throw "f() only supports up to 5 arguments";
            }
        }
    }
}

...and that's just ugly.


* ES5 added bound functions, which are functions that have their this value attached to them via binding:

// Normal function
function foo() {
    console.log(this.name);
}

// Create a bound function:
var f = foo.bind(someObject);

It doesn't matter how you call f, it will call foo with this set to someObject.

* ES2015 (aka ES6) added arrow functions. With arrow functions, this is not set by how the function is called; instead, the function inherits this from the context in which it was created:

// Whatever `this` is here...
var f = () => {                             // <== Creates an arrow function
    // Is what `this` will be here
};

Arrow functions are really handy when you're doing something like Array#forEach within an object method:

this.counter = 0;
this.someArray.forEach(entry => {
    if (entry.has(/* some relevant something */)) {
        ++this.counter;
    }
});
T.J. Crowder
  • 879,024
  • 165
  • 1,615
  • 1,639
  • 1
    Wow! People obviously like this answer but I can't see how it answers the OP question? Maybe I'm just being stupid but it seems to be banging on about a bunch of stuff peripheral to the actual question (as do the other answers). – geoidesic Oct 25 '16 at 12:07
  • 7
    @geoidesic: It directly answers the question: *"So `var f = console.log.bind(console);` should, in theory, give you a function, `f`, that you can call to log to the console."* It also explains why it does that, why you need to do that rather than just `var f = console.log` (which is where `this` comes into it), and how it may or may not work depending on the host implementation. – T.J. Crowder Oct 25 '16 at 12:11
  • @T.J.Crowder Thank you for the answer. I am using `console.log.bind` in Typescript/Angular for a logger, to print stuff in console with the link to the class where the logger was called instead of inside the Logger class itself, like without `bind`. Unfortunately I cannot pass a second parameter to bind, e.g. `console.log.bind(console, 'background: #222; color: #bada55');` will simply print the second argument as a string and not apply it to console. How do I fix this? – Phil May 18 '17 at 12:22
  • @Phil maybe I'm confused, but are you expecting it to apply those styles to the console output? That's not how it works. What you have is roughly equivalent to `(...args) => console.log('background: #222; color: #bada55', ...args)`. That is, it just uses that css string as the first argument to `console.log` in any invocations of your new *bound* function. I don't think you're gonna be able to use `bind` to achieve what you want. That's just not quite how that console.log styling works. More info: https://stackoverflow.com/a/13017382/363701 – Zach Lysobey May 02 '18 at 17:21
  • @T.J.Crowder You mention `...but if the log function relies on this referring to the console object during the call, then calling f("Message here") won't work, because this won't refer to console.`. Is there any such case (where the log relies on this to be console) that can be demonstrated or referred to? – KostasX Jan 30 '20 at 12:14
  • @KostasX - I vaguely recall that some earlish implementation of `console` didn't like it when `log` was called with `this` not referring to `console`, but I don't recall which one. I doubt any modern one cares, not least because I see it used without worrying about `this` a fair bit. – T.J. Crowder Jan 30 '20 at 12:17
9

Quick update on this matter, it seems that you don't need to bind the console to itself anymore.

Chromium started rolling out some deep changes on the console object, which is now already bound to itself. https://chromium.googlesource.com/chromium/src.git/+/807ec9550e8a31517966636e6a5b506474ab4ea9

It also seems that all the other browsers have followed this path (tested on recent version of Firefox and in Node).

I guess, if you need to be compatible with old browsers, you would still need to manually bind the console, but for debug purposes, you can now omit the .bind(console) :)

atomrc
  • 2,206
  • 14
  • 18
6

T.J. Crowder's answer helped me explain and solve a problem I had with redirecting console.log output, but his solution for the "no Function#apply" case seemed arbitrarily limiting for many use-cases.

I rewrote his code like this which is a little cleaner and more functional:

function f() {
    var a = arguments;

    if (typeof console != "undefined" && console.log) {
        if (console.log.apply) {
            // It has Function#apply, use it
            console.log.apply(console, arguments);
        } else {
            // Ugh, no Function#apply
            var output = '';
            for (i=0;i<arguments.length;i++) {
                output += arguments[i] + ' ';
            }
            console.log(output);
        }
    }
}

console.log separates arguments with a space, so I replicated that here as well. The primary limitation for this is that it does not handle arguments that are objects. You can stringify those if needed.

Mordred
  • 2,661
  • 29
  • 46
0

As stated in other answers, it gives the console.error function as the error handler, and bind(console) makes it use the console as the value of this within it's body. Otherwise, this would be set to a global object (window in browsers) and the call would fail. Well explained here.

Often you can see this in Promise error handling (for example, from the Angular 2 quickstart):

System.import("unmarshaller/Unmarshaller.js").then(null, console.error.bind(console));

The offtopic part:

You may want to create your own handler to pre-process the error. In the above example, console.error prints ugly Error in the console because SystemJS only tells that "Error loading Unmarshaller.js". And the other error is hidden in originalErr.

Make a custom handler to unwrap:

function handleError(e) {
    if (e.originalErr)
        throw e.originalErr;
    throw e;
}

System.import("unmarshaller/Unmarshaller.js").then(null, handleError);

No need for .bind(), and will give you the Error originally thrown, like:

Error: Given object doesn't specify "w:winduptype" and no target class given:
[{"w:winduptype":["FileResource","ArchiveModel:","WarArchiveModel"], ...

Ondra Žižka
  • 36,997
  • 35
  • 184
  • 250
  • 2
    This doesn't appear to be an attempt to answer the question at all. It doesn't describe what `console.error.bind(console)` means, it only presents an alternative (which behaves differently in several significant ways) for a very specific usecase (which the question doesn't mention at all).. – Quentin Oct 07 '16 at 14:15
  • 1
    That's your perception of the question, which itself is quite vague. There's no point in repeating the other answers. Changed to be more general, though. – Ondra Žižka Oct 07 '16 at 16:10