JavaScript (ES2026) Module 5 – Objects in depth
call, apply and bind
Call JavaScript functions with an explicit this using call and apply, make bound functions with bind for partial application, and see how bind meets new.
What you will learn
- Call a function with an explicit this using call and apply
- Create bound functions with bind, including partial application
- Explain how bind interacts with new and arrow functions
Before you start
What you’ll learn · Before you start
What you will learn
- Call a function with an explicit this using call and apply
- Create bound functions with bind, including partial application
- Explain how bind interacts with new and arrow functions
Before you start
On this page
Every JavaScript function has three methods that let you choose its this instead of leaving it to the shape of
the call. call runs the function at once with the this you give and the arguments listed one by one:
describe.call(asha, 'Hello', '!'). apply does the same but takes the arguments as an array:
describe.apply(asha, ['Hello', '!']). bind runs nothing: it returns a new function whose this, and
optionally its first arguments, are fixed for good, so describe.bind(asha, 'Hello') can be passed around as a
callback and still greet Asha. call and apply are for one call with a borrowed or chosen this; bind is for a
function you hand to someone else. Two exceptions matter: calling a bound function with new ignores its bound
this, and an arrow function ignores the this of all three. This lesson runs each method, shows how this values
are converted, borrows array methods, compares apply with spread, and builds bind from scratch in the exercise.
call and apply: choose this for one call
// call and apply run a function once, with the this you choose. They differ only in how the arguments are passed.
function describe(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const asha = { name: 'Asha' };
const ravi = { name: 'Ravi' };
console.log(describe.call(asha, 'Hello', '!')); // the arguments one by one
console.log(describe.apply(ravi, ['Namaste', '.'])); // the arguments in an array
console.log(describe.apply(asha, { 0: 'Hi', 1: '?', length: 2 })); // or in an array-like object
// A method of one object, run with another object as this.
const shop = {
name: 'Corner Shop',
label() {
return `Shop: ${this.name}`;
},
};
console.log(shop.label(), '|', shop.label.call(ravi)); Output
Hello, Asha! Namaste, Ravi. Hi, Asha? Shop: Corner Shop | Shop: Ravi
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node explicit-this.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
describe uses this.name, but it is not a method of anything. call supplies the object for one call, followed
by the arguments; apply supplies the object and then a single array of arguments, or any array-like object, one
with a length and numbered keys, as the third line shows. The last line runs a method of shop with ravi as
this, which prints Shop: Ravi: a method has no lasting tie to its object.
The this you pass is given to the function as it is, and then the function’s own mode decides what it becomes:
// The this value a function receives from call(), in sloppy code (this CommonJS file) and in strict code.
function sloppy() {
return this;
}
function strict() {
'use strict';
return this;
}
const show = (value) => {
if (value === globalThis) return 'globalThis';
if (value === null || value === undefined) return String(value);
if (typeof value === 'object') return `${value.constructor.name} object`;
return `${typeof value} ${String(value)}`;
};
console.log('call(...) with'.padEnd(16), 'sloppy gets'.padEnd(18), 'strict gets');
for (const [label, value] of [['undefined', undefined], ['null', null], ['5', 5], ["'chai'", 'chai'], ['{ id: 1 }', { id: 1 }]]) {
console.log(label.padEnd(16), show(sloppy.call(value)).padEnd(18), show(strict.call(value)));
} Output
call(...) with sloppy gets strict gets
undefined globalThis undefined
null globalThis null
5 Number object number 5
'chai' String object string chai
{ id: 1 } Object object Object object
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node this-coercion.cjs
A strict function, which includes every function in a module or a class, receives exactly what you passed:
undefined, null or the number 5. A sloppy function replaces undefined and null with the global object and
wraps a primitive in an object, so sloppy.call(5) sees a Number object. That is one more reason the output of the
same code can differ between an old script and a module.
Borrowing methods
Many built-in methods, the array methods among them, are what the specification calls intentionally generic: they work
on any object that looks right, so you can run them with call on values that do not have them. This used to be the
standard way to treat array-likes as arrays:
// Borrowing a method: running a method of one kind of object on a value that does not have it.
const drinks = { 0: 'chai', 1: 'coffee', 2: 'lassi', length: 3 }; // looks like an array, but is a plain object
console.log(Array.isArray(drinks), typeof drinks.map);
console.log(Array.prototype.map.call(drinks, (drink) => drink.toUpperCase()));
console.log(Array.prototype.join.call(drinks, ' / '));
console.log(Array.prototype.map.call('abc', (letter) => letter.charCodeAt(0))); // a string is array-like too
// Object.prototype.toString, borrowed, names the kind of any value, null and arrays included.
const kindOf = (value) => Object.prototype.toString.call(value);
console.log(kindOf([]), kindOf(null), kindOf(new Map()), kindOf(/x/));
// Modern code often has a direct way: Array.from turns an array-like object into an array.
console.log(Array.from(drinks, (drink) => drink.length)); Output
false undefined [ 'CHAI', 'COFFEE', 'LASSI' ] chai / coffee / lassi [ 97, 98, 99 ] [object Array] [object Null] [object Map] [object RegExp] [ 4, 6, 5 ]
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node borrow-method.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
drinkshas numbered keys and alengthbut nomap.Array.prototype.map.call(drinks, …)runs the array method withdrinksasthis, and the method only needslengthand the numbered keys.- A string is array-like too, so
mapcan walk its characters. Object.prototype.toString.call(value)names the kind of any value, includingnulland arrays, whichtypeofreports as"object". Libraries used it for type checks beforeArray.isArrayexisted.
Today there is often a direct way: Array.from(drinks, fn) builds a real array and maps it in one step, and
Object.hasOwn(obj, key) replaced the old borrowed Object.prototype.hasOwnProperty.call(obj, key). You will still
read the borrowed forms in older code, so they are worth recognising.
apply and spread
Before spread syntax, apply was the only way to call a function with an array of arguments, so code such as
Math.max.apply(null, readings) is everywhere in older libraries.
// apply takes the arguments as an array. Spread syntax does the same in an ordinary call.
const readings = [31.5, 29.8, 33.1, 30.2];
console.log(Math.max.apply(null, readings), Math.max(...readings));
// When the function needs a this, spread keeps the object before the dot; apply takes it as its first argument.
const cart = {
items: ['pen'],
add(...more) {
this.items.push(...more);
return this.items.length;
},
};
console.log(cart.add.apply(cart, ['ink', 'nib']));
console.log(cart.add(...['ruler']));
console.log(Reflect.apply(cart.add, cart, ['glue'])); // the same as apply, from the Reflect object
console.log(cart.items); Output
33.1 33.1 3 4 5 [ 'pen', 'ink', 'nib', 'ruler', 'glue' ]
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node apply-vs-spread.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
Math.max(...readings) gives the same 33.1 and reads better. When the function needs a this, spread keeps the
object before the dot, cart.add(...more), while apply takes it as its first argument. Reflect.apply(fn, thisArg, args) is the same operation as a plain function, which some code prefers because it cannot be broken by an object
that defines its own apply property. Both apply and spread turn every element into a separate argument, so both
hit the engine’s limit on arguments for very large arrays; the
lesson on parameters shows that limit.
bind: a function with this fixed
// bind returns a new function whose this, and optionally its first arguments, are fixed.
function describe(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const asha = { name: 'Asha' };
const ravi = { name: 'Ravi' };
const greetAsha = describe.bind(asha, 'Namaste'); // nothing runs yet
console.log(greetAsha('!'));
console.log(greetAsha.call(ravi, '?')); // a bound this cannot be replaced by call
console.log(greetAsha.bind(ravi)('.')); // or by binding again
console.log(`name: ${greetAsha.name} | length: ${greetAsha.length} (describe has ${describe.length})`);
// new ignores the bound this, but keeps the preset arguments.
function Point(x, y) {
this.x = x;
this.y = y;
}
const target = { ignored: true };
const PointOnYAxis = Point.bind(target, 0);
const p = new PointOnYAxis(5);
console.log(p, p instanceof Point, 'target.x:', target.x);
// An arrow function has no this to bind, but bind can still preset its arguments.
const thisOfArrow = () => this;
const add = (a, b) => a + b;
console.log(thisOfArrow.bind(asha)(), add.bind(null, 10)(5)); Output
Namaste, Asha!
Namaste, Asha?
Namaste, Asha.
name: bound describe | length: 1 (describe has 2)
Point { x: 0, y: 5 } true target.x: undefined
undefined 15
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node bind-rules.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
call and apply run the function now; bind returns a function for later
Text description of the diagram
The diagram has three rows, one per method.
- `f.call(obj, a, b)` runs `f` at once, with `obj` as `this` and the arguments `a` and `b`, listed one by one.
- `f.apply(obj, [a, b])` also runs `f` at once with `obj` as `this`, but takes the arguments from an array (or an array-like object).
- `const g = f.bind(obj, a)` runs nothing. It returns a new function `g`. When `g(b)` is called later, `f` runs with `obj` as `this` and the arguments `a` and `b`: the preset argument first, then the new one.
describe.bind(asha, 'Namaste') returns a bound function, a new function object that remembers its target
function, its this and its preset arguments. The output shows its rules:
- The bound
thiswins.greetAsha.call(ravi, '?')still greets Asha, and binding the bound function again,greetAsha.bind(ravi), does not change it either. Later calls can only add arguments after the preset ones. - It looks like its target. Its
nameis"bound describe", and itslengthis the target’s length minus the preset arguments, here 2 minus 1. It has noprototypeproperty of its own. newignores the boundthis.new PointOnYAxis(5)builds a newPoint: the preset argument 0 becomesx, 5 becomesy, andtarget, the boundthis, is never touched.p instanceof Pointistrue.- Arrow functions cannot be given a
this.thisOfArrow.bind(asha)()still returnsundefined, thethisof the module around it, althoughadd.bind(null, 10)presets an argument as usual.
The most common use of bind is the one from the previous lesson: const addToCart = cart.add.bind(cart) makes one
function you can pass to callbacks, timers and event listeners, and later remove again because it is always the same
function. Calling bind again creates a different function each time, so removeEventListener(type, obj.fn.bind(obj))
removes nothing.
Partial application with bind
Presetting arguments is called partial application: a general function becomes a more specific one.
// Partial application: bind fixes the first arguments and returns a function that takes the rest.
function priceWithTax(rate, amountInPaise) {
return Math.round(amountInPaise * (1 + rate));
}
const withStandardRate = priceWithTax.bind(null, 0.18); // null: this function does not use this
const withReducedRate = priceWithTax.bind(null, 0.05);
console.log(withStandardRate(25000), withReducedRate(25000));
console.log([10000, 4999, 120].map(withStandardRate)); // map's extra arguments (index, array) are ignored
// The same with an arrow function: just as short, and it shows which argument is fixed.
const withStandardRateArrow = (amountInPaise) => priceWithTax(0.18, amountInPaise);
console.log(withStandardRateArrow(25000));
// A logger with a fixed first argument; console stays the this of log.
const warn = console.log.bind(console, '[warn]');
warn('stock is low:', 3); Output
29500 26250 [ 11800, 5899, 142 ] 29500 [warn] stock is low: 3
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node partial.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
priceWithTax.bind(null, 0.18) fixes the rate and returns a function that waits for the amount; null is the this
because priceWithTax does not use one. The bound functions also work directly as map callbacks: map passes the
value, then an index and the array, and priceWithTax ignores the extra ones. console.log.bind(console, '[warn]')
makes a logger whose first argument is always the label.
An arrow function does the same job, (amount) => priceWithTax(0.18, amount), and many teams prefer it because the
fixed argument is visible at the call site and the arrow’s this behaves like the surrounding code. Use bind when
you need the bound function’s guarantees: a this that nobody can replace, or one stable function made from a method.
| Criterion | call | apply | bind | spread in a normal call |
|---|---|---|---|---|
| Runs the function | Now | Now | Later, when the new function is called | Now |
| Arguments | Listed one by one | One array or array-like | Preset ones now, the rest later | Any iterable, spread into the list |
| Sets this | Yes, for this call | Yes, for this call | Yes, for every call | Only through obj.method(...args) |
| Returns | The function's result | The function's result | A new bound function | The function's result |
| When to choose | Borrowing a method for one call | Older code with arguments already in an array | Callbacks and listeners that need a fixed this | Calling with an array of arguments today |
Exercise: write bind yourself
Exercise · Medium · JavaScript
Write bind yourself, with preset arguments and new
Write myBind(fn, thisArg, ...preset) in my-bind.mjs so that it behaves like fn.bind(thisArg, ...preset), without using bind. The function it returns:
- calls
fnwiththisArgasthis, and with thepresetarguments followed by its own arguments, and returns whatfnreturns:myBind(describe, asha, 'Hi')('!')isdescribe.call(asha, 'Hi', '!'); - keeps
thisArgeven when it is called throughcallorapplywith anotherthis; - when it is called with
new, ignoresthisArg, still passes thepresetarguments first, and returns a new instance offn.
The tests replace Function.prototype.bind with a function that throws, so a solution that calls bind fails.
Starter code · my-bind.mjs
// myBind(fn, thisArg, ...preset): what fn.bind(thisArg, ...preset) returns, written without bind (the exercise of
// lesson javascript/call-apply-and-bind). This version ignores thisArg and new.
export function myBind(fn, thisArg, ...preset) {
return function (...args) {
return fn(...preset, ...args);
};
} The sample tests · my-bind.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { myBind } from './my-bind.mjs';
function describe(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const asha = { name: 'Asha' };
const ravi = { name: 'Ravi' };
test('fixes this', () => {
const nameOf = myBind(function () {
return this.name;
}, asha);
assert.equal(nameOf(), 'Asha');
});
test('passes the preset arguments before the call arguments', () => {
const join = (...parts) => parts.join('-');
assert.equal(myBind(join, null, 'a')('b', 'c'), 'a-b-c');
assert.equal(myBind(join, null, 'a', 'b')('c'), 'a-b-c');
assert.equal(myBind(join, null)(), '');
});
test('returns what the function returns, with this and arguments together', () => {
const greet = myBind(describe, asha, 'Hi');
assert.equal(greet('!'), 'Hi, Asha!');
});
test('call and apply cannot replace the bound this', () => {
const greet = myBind(describe, asha, 'Hi');
assert.equal(greet.call(ravi, '?'), 'Hi, Asha?');
assert.equal(greet.apply(ravi, ['.']), 'Hi, Asha.');
});
test('new ignores thisArg and keeps the preset arguments', () => {
function Point(x, y) {
this.x = x;
this.y = y;
}
const target = {};
const OnYAxis = myBind(Point, target, 0);
const p = new OnYAxis(5);
assert.ok(p instanceof Point, 'the result should be a Point');
assert.equal(p.x, 0);
assert.equal(p.y, 5);
assert.deepEqual(target, {}, 'thisArg should not be touched');
});
test('does not use Function.prototype.bind', () => {
const original = Function.prototype.bind;
Function.prototype.bind = function () {
throw new Error('bind was used');
};
try {
const add = myBind(function (a) {
return this.base + a;
}, { base: 40 });
assert.equal(add(2), 42);
} finally {
Function.prototype.bind = original;
}
}); A hint
Return a regular function, not an arrow: it needs new.target, which is undefined in a normal call and set when the function is called with new. In a normal call, fn.apply(thisArg, [...preset, ...args]) runs fn with the right this and arguments. With new, build the object with new fn(...preset, ...args) and return it.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Self-check
Check yourself
6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Interview questions
Fresher · What is the difference between call, apply and bind?
All three let you choose the this of a function. call runs the function immediately with the this you give and
the arguments listed one by one, as in greet.call(user, 'Hi', '!'). apply also runs it immediately but takes the
arguments as one array, greet.apply(user, ['Hi', '!']); spread syntax has replaced most uses of it. bind runs
nothing: it returns a new function whose this, and optionally its first arguments, are fixed, which is useful for
callbacks and event listeners. A bound function’s this cannot be changed by a later call, apply or bind, but
calling it with new ignores the bound this and creates a new object. None of the three can change the this of an
arrow function, which always uses the this of the code around it.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
A curry(fn) helper collects arguments until it has fn.length of them, using bind to remember the ones so far. It works for (a, b, c) => ..., but curry((amount, rate = 0.18) => ...) returns a number after one argument, and curry((...parts) => ...) never waits at all. Explain both, and fix the design.
-
Fresher to mid
A debounce helper is (fn, ms) => { let t; return () => { clearTimeout(t); t = setTimeout(() => fn(), ms); }; }. Used on a method, editor.save = debounce(editor.save, 300), it never saves and the event argument is lost. Explain both bugs and write a version that keeps this and the arguments.
Key takeaways
f.call(thisArg, a, b)andf.apply(thisArg, [a, b])runfonce with thethisyou choose;f.bind(thisArg, a)returns a new function and runs nothing.- A strict function receives the
thisyou pass as it is; a sloppy one turnsundefinedandnullinto the global object and wraps primitives. - Borrowing, such as
Array.prototype.map.call(arrayLike, fn), runs a method on a value that lacks it;Array.fromandObject.hasOwnreplace the most common borrowed forms. - A bound function’s
thiscannot be replaced bycall,applyor anotherbind;newignores it but keeps the preset arguments. - Arrow functions ignore the
thisofcall,applyandbind; spread syntax has replaced most uses ofapply.
References
- ECMAScript 2026 Language Specification: Function.prototype.call (Ecma International)
- ECMAScript 2026 Language Specification: Function.prototype.apply (Ecma International)
- ECMAScript 2026 Language Specification: Function.prototype.bind (Ecma International)
- ECMAScript 2026 Language Specification: bound function exotic objects (Ecma International)
- ECMAScript 2026 Language Specification: OrdinaryCallBindThis (Ecma International)
- ECMAScript 2026 Language Specification: Object.prototype.toString (Ecma International)
- ECMAScript 2026 Language Specification: Array.prototype.map (an intentionally generic method) (Ecma International)
- ECMAScript 2026 Language Specification: Reflect.apply (Ecma International)
- Function.prototype.bind() (MDN Web Docs)
- Function.prototype.call() (MDN Web Docs)
- Function.prototype.apply() (MDN Web Docs)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress