I am wondering whether dot-abstraction methods (e.g. dog.bark
) bind at runtime or at compile-time. My question concerns the following code, which throws an error:
(true ? ''.toLowerCase : ''.toUpperCase)()
But the following does not:
true ? ''.toLowerCase() : ''.toUpperCase()
Why is my string literal ''
not getting resolved in the first example?
Summary. The bind() method creates a new function, when invoked, has the this sets to a provided value. The bind() method allows an object to borrow a method from another object without making a copy of that method. This is known as function borrowing in JavaScript.
JavaScript Function bind() With the bind() method, an object can borrow a method from another object. The example below creates 2 objects (person and member).
JavaScript Context (this) Binding function contextEvery function has a bind method, which will create a wrapped function that will call it with the correct context.
Call invokes the function and allows you to pass in arguments one by one. Apply invokes the function and allows you to pass in arguments as an array. Bind returns a new function, allowing you to pass in a this array and any number of arguments.
(true ? ''.toLowerCase : ''.toUpperCase)()
is equivalent to:
String.prototype.toLowerCase.call() // or: String.prototype.toLowerCase.call(undefined)
However,
true ? ''.toLowerCase() : ''.toUpperCase()
is equivalent to:
String.prototype.toLowerCase.call('')
In both cases, the first argument to call
is converted to an object, which the this
in String.prototype.toLowerCase
will reference to.
undefined
can't be converted to an object, but the empty string can:
function logThis () { console.log(this); } logThis.call('');
The SO snippet console only shows {}
, but it's actually the same thing that you get from new String('')
. Read about the string wrapper on MDN.
This is actually quite simple once you get how methods work in javascript behind the scenes.
toUpperCase
is a method. This is a function that operates on a specific object... usually via the this
variable.
Javascript is a prototypal language... meaning that the functions attached to objects are just functions and can be copied. There is some work behind the scenes that makes sure this
is set to the right thing when you call a method, but this work only happens when you call it as a method... as in the obj.method()
form.
In other words: ''.toUpperCase()
makes sure that this
is set to the string ''
when you call it.
When you call it as toUpperCase()
this
is not set to anything in particular (different environments do different things with this
in this case)
What your code does could be rewritten as this:
var function_to_call; if (true) { function_to_call = ''.toLowerCase; } else { function_to_call = ''.toUpperCase; } function_to_call();
Because your function call: function_to_call()
is not in the object.method()
syntax, the thing that sets this
to the correct object is not done, and your function call executes with this
not set to what you want.
As other people have pointed out, you can use func.call(thing_to_make_this)
or func.apply()
to attach the correct thing to this explicitly.
I find it much more helpful to use .bind()
- which is extremely under-used in my opinion. function_name.bind(this_object)
gives you a new function that will always have this
attached to the correct thing:
// assuming function_to_call is set function_that_works = function_to_call.bind(my_object) function_that_works(); // equivalent to my_object.function_to_call()
and this means you can pass around the function you get back from bind()
as you would a normal function, and it will work on the object you want. This is especially useful in callbacks, as you can create an anonymous function that is bound to the object it was created in:
// this won't work because when this runs, 'this' doesn't mean what you think setTimeout(function() { this.display_message('success'); }, 2000); // this will work, because we have given setTimeout a pre-bound function. setTimeout(function() { this.display_message('success'); }.bind(this), 2000);
TL;DR: You can't call a method as a function and expect it to work, because it doesn't know what this
should be. If you want to use that function, you have to use .call()
, .apply()
or .bind()
to make sure this
is set correctly by the time the function executes.
Hope that helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With