Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't Javascript bind my dot expression correctly? [duplicate]

Tags:

javascript

oop

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?

like image 707
Simon Kuang Avatar asked Aug 24 '17 06:08

Simon Kuang


People also ask

How does JavaScript bind work?

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.

How do you bind 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).

What is context binding in JavaScript?

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.

What is call apply bind in JavaScript?

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.


2 Answers

(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.

like image 68
PeterMader Avatar answered Oct 13 '22 19:10

PeterMader


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.

like image 45
JayKuri Avatar answered Oct 13 '22 20:10

JayKuri