Learn JS Series (#23) - call, apply, and bind: Controlling this Explicitly
Published on HivePostify by @scipio · Sat Sep 05 2026
Learn JS Series (#23) - call, apply, and bind: Controlling this Explicitly
What will I learn - You will learn exactly what call, apply, and bind do, and how they differ; - how to borrow a method from one object and run it on another, with no inheritance; - how bind creates a permanently locked function, and where that solves real bugs; - how to partially apply arguments with bind to build specialized functions; - when arrow functions make these tools unnecessary, and when you still need them.
Requirements - A working modern computer running macOS, Windows or Ubuntu; - An installed Node.js (20+) distribution, or just a modern browser console; - Episodes 1-22 read, especially the five this rules from last time.
Difficulty - Intermediate
Curriculum (of the Learn JS Series): - [Learn JS Series (#1) - What Is JavaScript, Why It Runs Everywhere, and How to Run It](https://hive.blog/hive-196387/@scipio/learn-js-series-1-what-is-javascript-why-it-runs-everywhere-and-how-to-run-it) - [Learn JS Series (#2) - Variables and Bindings](https://hive.blog/hive-196387/@scipio/learn-js-series-2-variables-and-bindings) - [Learn JS Series (#3) - The Primitive Types: number, string, boolean, null, undefined, symbol, bigint](https://hive.blog/hive-196387/@scipio/learn-js-series-3-the-primitive-types-number-string-boolean-null-undefined-symbol-bigint) - [Learn JS Series (#4) - Operators and Expressions: Arithmetic, Comparison, Logical, and Short-Circuiting](https://hive.blog/hive-196387/@scipio/learn-js-series-4-operators-and-expressions-arithmetic-comparison-logical-and-short-circuiting) - [Learn JS Series (#5) - Strings: Template Literals, Unicode, and the Methods You Actually Use](https://hive.blog/hive-196387/@scipio/learn-js-series-5-strings-template-literals-unicode-and-the-methods-you-actually-use) - [Learn JS Series (#6) - Numbers: IEEE 754, Why 0.1 + 0.2 Is Not 0.3, and How to Cope](https://hive.blog/hive-196387/@scipio/learn-js-series-6-numbers-ieee-754-why-01-02-is-not-03-and-how-to-cope) - [Learn JS Series (#7) - Control Flow: if/else, switch, and the Ternary Expression](https://hive.blog/hive-196387/@scipio/learn-js-series-7-control-flow-ifelse-switch-and-the-ternary-expression) - [Learn JS Series (#8) - Loops: for, while, for...of, for...in, and When to Use Which](https://hive.blog/hive-196387/@scipio/learn-js-series-8-loops-for-while-forof-forin-and-when-to-use-which) - [Learn JS Series (#9) - Functions: Declarations, Parameters, Return Values, and Hoisting](https://hive.blog/hive-196387/@scipio/learn-js-series-9-functions-declarations-parameters-return-values-and-hoisting) - [Learn JS Series (#10) - Scope and the Temporal Dead Zone: How JavaScript Finds Your Variables](https://hive.blog/hive-196387/@scipio/learn-js-series-10-scope-and-the-temporal-dead-zone-how-javascript-finds-your-variables) - [Learn JS Series (#11) - Arrays: The Workhorse Data Structure and Its Core Methods](https://hive.blog/hive-196387/@scipio/learn-js-series-11-arrays-the-workhorse-data-structure-and-its-core-methods) - [Learn JS Series (#12) - Objects: Key-Value Data, Dot vs Bracket Access, and Nesting](https://hive.blog/hive-196387/@scipio/learn-js-series-12-objects-key-value-data-dot-vs-bracket-access-and-nesting) - [Learn JS Series (#13) - Truthiness, Equality, and Coercion: == vs === Done Properly](https://hive.blog/hive-196387/@scipio/learn-js-series-13-truthiness-equality-and-coercion-vs-done-properly) - [Learn JS Series (#14) - Mini Project: A Command-Line Tip Calculator](https://hive.blog/hive-196387/@scipio/learn-js-series-14-mini-project-a-command-line-tip-calculator) - [Learn JS Series (#15) - First-Class Functions: Passing, Returning, and Storing Functions](https://hive.blog/hive-196387/@scipio/learn-js-series-15-first-class-functions-passing-returning-and-storing-functions) - [Learn JS Series (#16) - Arrow Functions vs function: Syntax, this, and When Each Wins](https://hive.blog/hive-196387/@scipio/learn-js-series-16-arrow-functions-vs-function-syntax-this-and-when-each-wins) - [Learn JS Series (#17) - Closures: The Single Most Important Idea in JavaScript](https://hive.blog/hive-196387/@scipio/learn-js-series-17-closures-the-single-most-important-idea-in-javascript) - [Learn JS Series (#18) - Higher-Order Functions: Functions That Take or Return Functions](https://hive.blog/hive-196387/@scipio/learn-js-series-18-higher-order-functions-functions-that-take-or-return-functions) - [Learn JS Series (#19) - Callbacks and the Callback Pattern (Before We Reach Promises)](https://hive.blog/hive-196387/@scipio/learn-js-series-19-callbacks-and-the-callback-pattern-before-we-reach-promises) - [Learn JS Series (#20) - Default, Rest, and Spread: Flexible Function Signatures](https://hive.blog/hive-196387/@scipio/learn-js-series-20-default-rest-and-spread-flexible-function-signatures) - [Learn JS Series (#21) - Destructuring Parameters: Named Arguments the JS Way](https://hive.blog/hive-196387/@scipio/learn-js-series-21-destructuring-parameters-named-arguments-the-js-way) - [Learn JS Series (#22) - The this Keyword: Five Rules That Explain Every Case](https://hive.blog/hive-196387/@scipio/learn-js-series-22-the-this-keyword-five-rules-that-explain-every-case) - [Learn JS Series (#23) - call, apply, and bind: Controlling this Explicitly](https://hive.blog/hive-196387/@scipio/learn-js-series-23-call-apply-and-bind-controlling-this-explicitly) (this post)
Learn JS Series (#23) - call, apply, and bind: Controlling this Explicitly
Solutions to Episode 22 Exercises
Exercise 1 - method call versus detached call:
js const dog = { name: "Rex", speak() { return ${this.name} says woof; }, }; console.log(dog.speak()); // "Rex says woof" - rule 1, 'this' is dog const detached = dog.speak; // console.log(detached()); // ERROR (runtime): rule 2, 'this' is undefined
The insight: dog.speak() is a method call (rule 1), so this is the object before the dot; detached() is a plain call (rule 2) with this === undefined, and this.name then throws.
Exercise 2 - fixing this with call and bind:
js function introduce() { return I am ${this.name}; } const person = { name: "scipio" }; console.log(introduce.call(person)); // "I am scipio" - runs now const bound = introduce.bind(person); console.log(bound()); // "I am scipio" - locked function
The insight: call invokes immediately with a chosen this; bind returns a new function permanently tied to that this that you can store and call later.
Exercise 3 - regular callback versus arrow:
js const box = { size: 5, reportLater() { // setTimeout(function () { console.log(this.size); }, 10); // rule 2: undefined setTimeout(() => { console.log(this.size); }, 10); // rule 5: keeps box }, }; // box.reportLater(); // arrow prints 5; the regular function would fail
The insight: the regular callback is a plain call (rule 2, this undefined); the arrow uses the enclosing this (rule 5, the box), which is why it just works.
Right, with the five rules fresh, let's give rule 3 the full treatment it deserves. Last episode I called call, apply, and bind "a taste" and promised the real meal this time. Here it is. These three are the tools you reach for when you do NOT want to leave this to the mercy of the call site -- when you want to set it by hand, precisely, and know exactly what it will be.
Every function has call, apply, and bind
Start with a fact that explains why these three even exist: in JavaScript, functions are objects (a Phase 3 detail we will unpack properly later, but take it on trust for now). Because a function is an object, it can carry methods of its own, and every function you ever write inherits three of them straight from Function.prototype: call, apply, and bind. You did not add them, they are just there, on every function, always.
All three answer the same question -- "what should this be when this function runs?" -- and they let YOU answer it instead of the call site. They differ along just two axes: whether they run the function right now or hand you a new function for later, and how they take the function's normal arguments. Get those two axes straight and there is genuinely nothing else to learn here. Let's take them one at a time.
call: run now, arguments listed
call invokes the function immediately. Its first argument becomes this, and every argument after that is passed through to the function as its normal parameters, listed out one by one:
js function greet(greeting, punctuation) { return ${greeting}, ${this.name}${punctuation}; } const user = { name: "scipio" }; console.log(greet.call(user, "Hi", "!")); // "Hi, scipio!"
Read that call out loud as a sentence and it stops being mysterious: "call greet, set its this to user, and pass "Hi" and "!" as the ordinary arguments." That is the whole of call. It is the most direct way in the language to run a function with a this that you pick, right now, this instant.
One thing worth pinning down: the FIRST argument is special (it is the this value), and everything else shifts over to become the real parameters. So greet.call(user, "Hi", "!") maps user to this, "Hi" to greeting, and "!" to punctuation. Miscounting that offset is the single most common call mistake, so keep the mental picture that the first slot is "stolen" by this.
apply: run now, arguments as an array
apply does exactly what call does, with one solitary difference: it takes the function's arguments as a single array instead of listed out individually. That is the only distinction between them. call spreads, apply packs:
js console.log(greet.apply(user, ["Hey", "."])); // "Hey, scipio."
A memory aid that has stuck with me for years: apply takes an array, call takes a comma-separated list. Same first letter, same shape. Once that clicks you never mix them up again.
So when is packing the arguments into an array actually useful? When you already HAVE them in an array and do not want to unpack them by hand. The classic example is calling a function that expects separate numbers, on a list of numbers you already hold:
js const nums = [5, 12, 8, 130, 44]; console.log(Math.max.apply(null, nums)); // 130 - array spread as separate args console.log(Math.max(...nums)); // 130 - the modern spread equivalent
Math.max wants Math.max(5, 12, 8, ...), not an array, and before the spread operator existed, apply was THE way to feed it an array. Notice we passed null as the first argument, because Math.max does not use this at all, so any placeholder will do. In modern code the spread operator ... has made apply largely redundant (you can write greet.call(user, ...args) and get the same effect), but you will still meet apply constantly in existing code, so you must be able to read it fluently.
Method borrowing: the real payoff
Here is where call and apply genuinely earn their keep, not as trivia but as a technique: method borrowing. If an object owns a useful method, you can run that method with a completely different object as its this, without any inheritance, without any shared prototype, without copying anything. You just point the method's this wherever you like:
js const person = { fullName() { return ${this.first} ${this.last}; }, }; const other = { first: "scipio", last: "the great" }; console.log(person.fullName.call(other)); // "scipio the great"
other has no fullName method of its own. It does not inherit one. But we grabbed person's method and ran it with this aimed at other, and because fullName only ever touches this.first and this.last, it works perfectly. That is method borrowing in one line: a method is just a function that reads this, so give it whichever this you want.
This was historically vital for a very practical reason. Old-school JavaScript had "array-like" objects -- things with a length and numeric indices but WITHOUT the real array methods. The arguments object inside a function is the classic case. To use real array methods on them, you borrowed from Array.prototype:
js function collectArgs() { // borrow Array's slice to turn array-like 'arguments' into a real array return Array.prototype.slice.call(arguments); } console.log(collectArgs(1, 2, 3)); // [1, 2, 3]
arguments is not a real array, so it has no .slice(). But slice only cares that its this has a length and indexed elements, so borrowing it via call works a treat. Modern code writes [...arguments] or a rest parameter (...args) in stead, but you will run into Array.prototype.slice.call(arguments) in older codebases forever, and now you know exactly what it is doing and why. Understanding this one idiom demystifies quit a lot of pre-2015 JavaScript.
bind: create a permanently locked function
Now for the different one. call and apply both run the function immediately. bind does NOT. Instead, bind returns a brand new function whose this is permanently locked to whatever you passed. It does not invoke anything -- it manufactures a new function for you to call whenever you please, and no matter how you later call it, its this cannot change:
js const counter = { count: 0, increment() { this.count += 1; return this.count; }, }; const inc = counter.increment.bind(counter); // locked to counter forever console.log(inc()); // 1 - works even though called as a plain function console.log(inc()); // 2
That inc is a plain, bare function now -- there is no counter. in front of it -- and yet it still increments counter correctly, because the this is baked in. This is the definitive cure for the "lost this" bug we diagnosed last episode. Remember the trap: the moment you pass a method somewhere as a callback, it gets detached, rule 2 kicks in, and this becomes undefined. A bound method carries its this with it, so it survives the trip:
js // safe to pass around: it will always have the right 'this' setTimeout(counter.increment.bind(counter), 100); // increments correctly later
And the lock is genuinely permanent -- this is important, so let me show it. Once a function is bound, you cannot re-point its this even with call. The first bind wins, permanently:
js function whoAmI() { return this.label; } const a = { label: "A" }; const b = { label: "B" }; const boundToA = whoAmI.bind(a); console.log(boundToA()); // "A" console.log(boundToA.call(b)); // "A" - bind wins, call cannot override it
That permanence is exactly the property you want for event handlers and stored callbacks. A hugely common real-world pattern is to bind a method once in a constructor, so that every copy of that handler you hand out is already safe to detach:
js class Button { constructor(label) { this.label = label; this.handleClick = this.handleClick.bind(this); // lock 'this' once, up front } handleClick() { return clicked ${this.label}; } } const save = new Button("Save"); const handler = save.handleClick; // detached, but already bound in the constructor console.log(handler()); // "clicked Save" - survives detachment
Before arrow functions existed, bind was THE canonical way to keep this correct in callbacks, and you will see that constructor-binding pattern in mountains of older class-based code (React components from the mid-2010s are packed with it). Arrows now handle many of those cases more cleanly, but bind is still the right tool when you need a reusable, detachable function that is permanently welded to a specific object.
Partial application with bind
Tags: #stem#stemsocial#steemstem#javascript#programming