
The value of this is the single most confusing part of JavaScript for many developers, and prototypes are close behind. Both appear constantly in interviews as "what does this print?" questions, and both explain real bugs, such as a method that loses its object when passed as a callback.
This is Part 6 of the Learn Frontend series. We cover the four rules that decide this, how arrow functions differ, call, apply and bind, the prototype chain, and how ES6 classes sit on top of it.
1. The four rules for this
In a regular function, this is decided by how the function is called, not where it is written. Check these rules from highest to lowest priority:
- new binding:
new User()creates a fresh object and setsthisto it. - Explicit binding:
fn.call(obj),fn.apply(obj)or a function created withfn.bind(obj)usesobj. - Implicit binding:
obj.method()uses the object before the dot. - Default binding: a plain call
fn()givesundefinedin strict mode (and in modules and classes), or the global object in sloppy mode.
'use strict';
const user = {
name: 'Asha',
hello() { return 'Hi ' + this.name; },
};
user.hello(); // 'Hi Asha' implicit: object before the dot
const fn = user.hello;
fn(); // TypeError: this is undefined (default binding)
fn.call({ name: 'Ravi' }); // 'Hi Ravi' explicit
const bound = fn.bind(user);
bound(); // 'Hi Asha' bound for good
function Person(name) { this.name = name; }
const p = new Person('Meera'); // new: this is the new object2. Arrow functions and lost this
Arrow functions do not have their own this. They capture this from the surrounding scope at the moment they are created, and call, apply and bind cannot change it.
- That makes arrows ideal for callbacks inside methods, such as
setTimeout(() => this.save(), 1000). - It makes them a poor choice for object methods that need the object:
{ name: 'A', greet: () => this.name }does not seename.
Losing this: passing obj.method as a callback (to an event listener or setTimeout) detaches it from obj, so the call falls back to default binding. Fix it with obj.method.bind(obj), an arrow wrapper () => obj.method(), or class fields declared as arrow functions. In DOM event listeners written as regular functions, this is the element the listener is attached to.
3. call, apply and bind
| Method | Calls immediately? | Arguments |
|---|---|---|
fn.call(thisArg, a, b) | Yes | One by one |
fn.apply(thisArg, [a, b]) | Yes | As an array |
fn.bind(thisArg, a) | No, returns a new function | Pre-filled (partial application) |
"Write a polyfill for bind" is a classic interview task that tests closures, this and rest/spread together:
Function.prototype.myBind = function (thisArg, ...preset) {
const original = this; // the function being bound
return function (...later) {
return original.apply(thisArg, [...preset, ...later]);
};
};
function greet(greeting, punct) { return greeting + ', ' + this.name + punct; }
const hi = greet.myBind({ name: 'Dev' }, 'Hello');
console.log(hi('!')); // 'Hello, Dev!'4. Prototypes and the prototype chain
Every object has a hidden link, [[Prototype]], to another object (readable with Object.getPrototypeOf(obj)). When you read a property that the object does not have, JavaScript walks up this prototype chain until it finds it or reaches null.
- Arrays get
mapandpushfromArray.prototype; every object getstoStringfromObject.prototype. - Functions have a
prototypeproperty; objects created withnew Fn()link toFn.prototype, so methods placed there are shared by all instances instead of being copied. Object.create(proto)makes a new object linked directly toproto.obj.hasOwnProperty(key)(orObject.hasOwn) checks only the object itself, not its chain.
function Animal(name) { this.name = name; }
Animal.prototype.speak = function () { return this.name + ' makes a sound'; };
const dog = new Animal('Rex');
dog.speak(); // found on Animal.prototype
Object.getPrototypeOf(dog) === Animal.prototype; // true
dog.hasOwnProperty('speak'); // false
// The same thing with class syntax
class Cat extends Animal {
#lives = 9; // private field
speak() { return super.speak() + ' (meow)'; }
get lives() { return this.#lives; }
}5. ES6 classes
class syntax is a cleaner way to write constructor functions and prototype methods; underneath it is still prototypes.
constructorruns onnew; methods are placed on the prototype.extendssets up the prototype chain;super(...)must be called before usingthisin a subclass constructor.staticmembers live on the class itself, not on instances.#privatefields and methods are truly private to the class.- Class bodies always run in strict mode, so a detached method call gives
this === undefined.
Modern React uses function components and hooks instead of classes (Part 10), but classes still appear in libraries, error boundaries, interviews and other frameworks.
- In regular functions, this depends on the call: new, then explicit (call/apply/bind), then implicit (obj.method()), then default.
- Arrow functions capture this from the surrounding scope and cannot be rebound.
- Passing obj.method as a callback loses this; fix it with bind or an arrow wrapper.
- Objects inherit through the prototype chain; shared methods live on the prototype.
- ES6 classes are syntax over prototypes, with extends, super, static and #private members.
Practice problems
Predict each output before running it in the console. These mirror real interview questions.
- Predict the output of calling an object method with setTimeout(obj.method, 0), then fix it two ways.
- Predict what an arrow function method on an object literal returns for this.name.
- Implement myCall and myApply polyfills.
- Implement the myBind polyfill and make it work with new.
- Create a three-level prototype chain with Object.create and trace a property lookup.
- Rewrite a constructor function plus prototype methods as an ES6 class with a private field.
Interview questions and FAQ
How is the value of this determined?
For regular functions it depends on the call site: a new call binds the new object, call/apply/bind bind the given object, a method call binds the object before the dot, and a plain call gives undefined in strict mode or the global object otherwise.
What is the difference between call, apply and bind?
call and apply invoke the function immediately with a chosen this; call takes arguments individually and apply takes an array. bind returns a new function with this (and optionally some arguments) permanently set.
Why do arrow functions not have their own this?
By design, arrows use lexical this, taking it from the enclosing scope. This makes them convenient for callbacks inside methods but unsuitable as object methods or constructors.
What is prototypal inheritance?
Objects link to other objects through their prototype. When a property is missing on an object, JavaScript looks it up along the prototype chain, so objects can share behaviour without copying it.
Are JavaScript classes real classes?
They are syntactic sugar over constructor functions and prototypes. They add conveniences such as extends, super, static members and private fields, but inheritance still works through the prototype chain.
Comments
Post a Comment