JavaScript is an asynchronous programming language. Because of this, functions are often called as other function callbacks. There's a particularly large amount of it in the browser, where there's a callback upon callback upon callback. While we've been working with simple functions, this hasn't been a problem, but things change when we use methods.
We haven't worked with asynchrony yet at this point, but that shouldn't stop us from getting the idea. In a nutshell, the setTimeout function takes a function on input, and the time, after which, it should be called. When the time comes, I'll do it. That's pretty much it.
Try running this code:
const printer = {
name: 'Hexlet',
print(greeting = 'hello') {
console.log(`${greeting}, ${this.name}`);
}
};
// Direct launch
printer.print(); // => "hello, Hexlet"
Now the same code, but we don't want to it immediately, but rather after a second. To do this, use the setTimeout()
, function, which calls the passed function after the specified amount of time.
// We want to run the print method after a second
// Be sure to run this code on your computer
// to get a feel for how setTimeout works
// 1000 means 1000 milliseconds or 1 second
// printer.print is not a call, but a function pass
setTimeout(printer.print, 1000);
// After a second
// => "hello, undefined"
This code will output hello, undefined
. Why? Because we didn't pass the printer object inside setTimeout()
but rather the print()
function without an object. This means that this function has lost its connection to the object itself and its this
no longer points to the object. That's how you can illustrate what's going on:
const print = printer.print;
// Somewhere inside setTimeout
print(); // => "hello, undefined"
If there's no context, then this
equals an empty object, if we talk about normal functions.
This behavior is often undesirable. Almost always, when a method is passed, it's assumed that it will be called in the context of the object to which it belongs. There are several ways to achieve this behavior. The easiest is to wrap the function in a function while we call the function.
setTimeout(() => printer.print(), 1000);
// After a second
// => "hello, Hexlet"
// Or without setTimeout
const fn = () => printer.print();
// It all works because print() is called from printer
fn(); // => "hello, Hexlet"
This is a common solution that also helps to grab external variables when they're needed for a call:
// Wrapping it in a function helps to pass data inside
const value = 'hi';
setTimeout(() => printer.print(value), 1000);
// => "hi, Hexlet"
Bind
Another way is to use the bind()
method. The bind()
method is available to functions, and its task is to bind a function to some context. The bind()
results in a new function that works like the original one but with context bound to it.
// The context is the same printer object in which the method is defined
// It looks pretty weird, but life is complicated.
// bind is called on a function and returns a function
const boundPrint = printer.print.bind(printer);
// Now you can do it like this
boundPrint(); // => "hello, Hexlet"
setTimeout(boundPrint, 1000);
// In a second
// => "hello, Hexlet"
// It's possible to call bind on the spot
// as the function returns
setTimeout(printer.print.bind(printer), 1000);
// hello, Hexlet
The linked function merges tightly with its context. this
won't change anymore.
bind()
takes the parameters that the function requires as input in addition to the context. And not all of them at once, but any part of them. bind()
will substitute them partially in the new function (the one that's returned from the bind()
) method). This technique is called “partial function application”. That way, you can apply the right arguments right away:
setTimeout(printer.print.bind(printer, 'hi'), 1000);
// After a second
// => "hi, Hexlet"
The bind()
approach was popular before the advent of arrow functions, now it isn't used so often. Arrow functions are easier to understand and are used everywhere.
Apply & Call
bind()
is useful where context binding and function calls occur in different places and usually at different times. We will encounter such code when we move on to asynchrony in JavaScript.
Sometimes, functions that use this
internally are called straight away, with context binding alongside it. This can be done directly by immediately calling the function returned by bind
: ...bind(/* context */)()
:
const print = printer.print;
print.bind(printer)('hi'); // => "hi, Hexlet"
or you can use the apply()
and call()
functions specially created for this purpose:
// func.apply(thisArg, [ argsArray])
print.apply(printer, ['hi']); // hi, Hexlet
// func.call([thisArg[, arg1, arg2, ...argN]])
print.call(printer, 'hi'); // hi, Hexlet
These functions do two things internally: they change the context and immediately make a function call. The only difference is how they handle the arguments of these functions: apply()
takes arguments with an array as the second parameter, while call()
takes positional arguments as input.
These features allow you to do some pretty unusual things, like this:
// If there's no context, it passes null
const numbers = [1, 10, 33, 9, 15]
const max = Math.max.apply(null, numbers); // 33
const numbers = [1, 10, 33, 9, 15]
const max = Math.max.call(null, ...numbers); // 33
The call above is just a demonstration, it's of little practical use. The real use of call()
and apply()
shows up in the binding of contexts to functions from the prototypes. We'll talk about this in future lessons.
Recommended materials
Are there any more questions? Ask them in the Discussion section.
The Hexlet support team or other students will answer you.
For full access to the course you need a professional subscription.
A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.