Call, Apply, and Bind
This isn't to highlight best-practice tips™.
It's here to shed light on the irregularity of this
modification;
problems you see are easier to fix — modifying a function's this
doesn't get weird, it starts weird.
The difference
When you first come across a need for call
, apply
, or bind
,
it's almost certainly for a one-off use.
This article’s about why using these tools for a one-off use isn't quite intuitive.
What we're used to
There's a theme in javascript, one everybody's used to, conciously or otherwise.
light.on = user.isHome;
light.turnOn(user.isHome);
When it’s time to change your data, you’re either using a method(.turnOn
) or operator(=
)
// changee | changer | change by
light.on = user.isHome ; // changes `light` with `=`
light .turnOn( user.isHome ); // changes `light` with a method
What’s on the left is what’s changed — in the lines above and hundreds of others you’ve seen.
Your ‘object to modify’, ‘target’, ‘changee’, whatever-you-call-it, is leftward.
The Surprise
So you want to use a method found elsewhere as if it belongs to your object.
arguments
is a good candidate; it looks like an array, but it only has .length
showArgs('foo', 'bar') // logs: ['foo','bar']
function showArgs(){
console.log(arguments);
}
You want to use array methods to join arguments
into a space-separated string,
and arguments
is kind of an array, so your gut says
do something like this:
focus on `arguments`
get a function from somewhere else
use `array.join`
now execute it
use ' ' as the separator
so you might do either of the following
arguments.join = Array.prototype.join;
arguments.join(' '); // which works! but it's two lines.
// they'll always need to be modified together and
// you'll need both lines each time you want to use one new function
/* so you shoot for a one-liner */
// change-ee followed by change-er
arguments.borrow(Array.prototype.join)(' '); // intuitive, but fruitless.
// or
arguments.borrow(Array.prototype.join).callWith(' '); // intuitive, but fruitless.
Alas, you’ll find out the line you want has to start with Array
,
despite it being all about arguments
.
Array.prototype.join.call(arguments, ' '); // works, but it breaks the mold
All that changee leftwards
scheme doesn't fit.
It's different in two big ways:
- the changee isn't leftwards
- it uses a method of a function (
function.call
)
Neither makes it easier to remember.
Memory Tricks
this
manipulation hasn't cooperated with the norm, syntactically or lexically.
So, since following precedent didn't work, here are some memory tricks.
Remembering Syntax
Writing code gives absolute control over what you make, but for this, imagine your objects as moody and/or possesive; pretend they're people:
- my neighbor and I are objects
- I can't mow the lawn
- my neighbor can
here's the two of us.
me = {
lawn: 'long-grass',
};
neighbor = {
lawn: 'short-grass',
runLawnmower: function(){ this.lawn = 'short-grass'; }
};
with that scenario
// can't call
me.runLawnmower() // !!! error
// and your gut says this syntax would be nice
me.borrowAndExec(neighbor.runLawnmower) // !!! fake syntax
// the line above is stealing; objects want to loan things out
// Instead you have to ask your neighbor to do it
// neighbor.runLawnmower.loanTo(me)
// with the correct identifier
neighbor.runLawnmower.call(me)
hopefully, a little personification can go a long way.
Remembering Vocab
call and apply
Because they're alive, they'll respond right away
and to differentiate between the two
apply uses an Array, and call uses Commas
Use them for quick or one-off applications.
Array.prototype.join.call(callInContext, 'separator');
Array.prototype.join.apply(callInContext, ['separator']);
bind
with
.bind
things stick around.
Calling bind
lets you save things for later:
boundJoin = Array.prototype.join.bind(callInContext);
// do other stuff
boundJoin(' ');