call, apply and bind
Mental model: call and apply lend this for one call. bind welds it on for good.
Level: intermediate · about 13 minutes
Rule 2 from the last lesson gets its own lesson because it is the escape hatch. When the call site cannot give you the right this, these three methods let you supply it yourself.
function introduce(role, team) {
return `${this.name} is ${role} on ${team}`;
}
const ada = { name: 'Ada' };
console.log(introduce.call(ada, 'lead', 'core')); // args spread out
console.log(introduce.apply(ada, ['lead', 'core'])); // args in an array
console.log(introduce.bind(ada)('lead', 'core')); // returns a functionSame target, same result, three different shapes.
| Method | Arguments | Calls the function? | Returns |
|---|---|---|---|
fn.call(t, a, b) | listed individually | yes, immediately | whatever fn returns |
fn.apply(t, [a, b]) | one array (or array-like) | yes, immediately | whatever fn returns |
fn.bind(t, a) | optional preset arguments | no | a new, permanently bound function |
Borrowing methods
A method is just a function that expects a particular shape of this. If your value has that shape, you can borrow the method even though it is not on your prototype.
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
// Not an array, so it has no join. Borrow one.
console.log(Array.prototype.join.call(arrayLike, '-')); // 'a-b-c'
console.log(Array.prototype.map.call(arrayLike, (s) => s.toUpperCase())); // ['A','B','C']
// Borrow a safe hasOwnProperty for an object that may have shadowed it.
const risky = { hasOwnProperty: 'oops', real: 1 };
console.log(Object.prototype.hasOwnProperty.call(risky, 'real')); // true
const tag = (v) => Object.prototype.toString.call(v).slice(8, -1);
console.log(tag([]), tag(null), tag(new Date(0)), tag(/x/));
// 'Array' 'Null' 'Date' 'RegExp'
console.log(typeof [], typeof null); // 'object' 'object' - useless hereThe classic type check: nothing else distinguishes these four.
Partial application with bind
Any argument you pass to bind after the this value is baked into the front of the argument list. That gives you a specialised function for free, no wrapper needed.
function log(level, module, message) {
return `[${level}] ${module}: ${message}`;
}
const warn = log.bind(null, 'WARN'); // fix the first argument
const authWarn = warn.bind(null, 'auth'); // fix the second too
console.log(warn('db', 'slow query')); // '[WARN] db: slow query'
console.log(authWarn('token expired')); // '[WARN] auth: token expired'
function who() { return this.name; }
const first = who.bind({ name: 'first' });
const second = first.bind({ name: 'second' });
console.log(first()); // 'first'
console.log(second()); // 'first', not 'second'
console.log(first.call({ name: 'x' })); // 'first' - call cannot win either
console.log(first.name); // 'bound who'
const obj = { v: 1, get() { return this.v; } };
const a = obj.get;
const b = obj.get.bind(obj);
const c = b.bind({ v: 99 });
console.log([b(), c(), typeof a]);b is hard-bound to obj, so it returns 1. Rebinding a bound function does nothing to its this, so c() also returns 1. a is only referenced, never called, so no error is thrown.
Interactive visualiser: this. Enable JavaScript to use it.
Implementing bind yourself
This is a standard interview question because it forces you to combine closures, rest and spread, apply, and the new binding rule. Build it in two passes.
- Pass one: capture and forward Close over the target function, the
thisvalue and the preset arguments. Return a function that merges preset with later arguments. - Pass two: respect
newRule 1 beats rule 2, so a bound function used withnewmust ignore the boundthisand construct instead.new.targettells you which happened. - Check it against the real thing The native version also drops
.prototype, names itselfbound fn, and reports a reducedlength. Those details rarely matter; the two behaviours above always do.
`new.target`- the function
newwas called on, otherwiseundefined `fn.length` after bind- reduced by the number of preset arguments
`fn.name` after bind- prefixed with 'bound '
`Function.prototype.call.bind(fn)`- turns a method into a standalone function taking the receiver first
Cost of `bind`- a new function object each time, so do not bind inside a render loop
If you can write
bind, you understand closures andthisat the same time. That is why it is asked.
Try it yourself
Method borrowing
function collect() {
// `arguments` is array-like, not an array
return Array.prototype.slice.call(arguments).join('|');
}
console.log(collect('a', 'b', 'c'));
const nodeLike = { length: 2, 0: { id: 1 }, 1: { id: 2 } };
console.log(Array.prototype.map.call(nodeLike, (n) => n.id));
console.log(Array.from(nodeLike, (n) => n.id));
Rewrite each borrowed call using a modern equivalent (Array.from, spread, Object.hasOwn). Which reads better?
Partial application
const multiply = (a, b) => a * b;
const double = multiply.bind(null, 2);
const triple = multiply.bind(null, 3);
console.log(double(10), triple(10)); // 20 30
console.log(multiply.length, double.length); // 2 1
Add a fetchJson = request.bind(null, "GET") style helper. Then try binding the second argument only, and see why you cannot.
Exercises
Implement bind from scratch
Write myBind(fn, thisArg, ...preset). It returns a new function that calls fn with this set to thisArg, passing the preset arguments first and any later arguments after them. The bound function must not be hijackable with call, and when it is used with new it must construct an instance of fn instead of using thisArg. Do not call the native bind.
Implement call without call
Write myCall(fn, thisArg, ...args) that invokes fn with this set to thisArg, without using call, apply, bind or Reflect. The only remaining way to set this is implicit binding, so put the function on the object temporarily, call it through a dot, then clean up so the object is left exactly as you found it. Objectify primitive receivers, and use a fresh empty object when the receiver is null or undefined.
Check yourself
- What does this log?
- 'one' 'one' — The first
bindhard-bindsthis. The secondbindwraps the already bound function, and that wrapper’sthisis ignored by the inner bound function, so both calls report"one". - What is the difference between
callandapply? - Only the argument shape:
calltakes a list,applytakes one array-like — They are identical apart from how arguments are passed.applyis still handy when the arguments already live in an array-like without aSymbol.iterator, where spread would fail. - Why does
Object.prototype.toString.call(value)beattypeof valuefor identifying built-ins? typeofreturns"object"for arrays, dates, regexes and null, whiletoStringexposes the internal tag —typeofcollapses every non-function object to"object". BorrowingObject.prototype.toStringreads the internal tag, giving"[object Array]","[object Date]","[object Null]"and so on.Array.isArrayis still the right tool for arrays specifically.
Common mistakes
- Expecting
bindto call the function. It only returns a new one. - Rebinding an already bound function and expecting the new
thisto stick. - Calling
bindinside a render or a loop and creating a fresh function object every pass. - Using
applywith a huge array to spread arguments and blowing the argument limit. Chunk it instead. - Forgetting that
bindalso presets arguments, sofn.bind(null, x)shifts every later parameter along by one.
Takeaways
callandapplyinvoke immediately; only the argument shape differs.bindreturns a new function withthisfrozen, plus optional preset arguments.- A hard binding cannot be overridden by
callor a secondbind, butnewstill wins. - Borrowing prototype methods works because a method only cares about the shape of
this. - Writing
bindyourself is closures, spread and thenewrule in ten lines.