Getting started
Binding priority
| Priority | Rule | this value |
|---|---|---|
| 1 | Arrow function | Lexical (enclosing scope) |
| 2 | new fn() |
New object |
| 3 | call / apply / bind |
Specified thisArg |
| 4 | obj.fn() |
obj |
| 5 | fn() |
globalThis or undefined |
Higher priority wins. Arrow functions ignore all other rules.
Quick determination
this = ?
│
├─ Arrow function?
│ └─ Yes → enclosing scope's this
│
├─ Called with new?
│ └─ Yes → the new object
│
├─ Called with call/apply/bind?
│ └─ Yes → the specified thisArg
│
├─ Called as obj.fn()?
│ └─ Yes → obj
│
└─ Plain fn() call
├─ Strict mode → undefined
└─ Sloppy mode → globalThis
globalThis
globalThis === window; // Browser
globalThis === global; // Node.js
globalThis === self; // Web Worker
Universal reference to the global object. Works in all environments (ES2020).
Global context
Browser vs Node.js
| Environment | this value |
|---|---|
<script> |
window |
<script type="module"> |
undefined |
| Node.js CommonJS | module.exports |
| Node.js ESM | undefined |
| Web Worker | self |
Examples
// Browser <script>
console.log(this === window); // true
// Browser <script type="module">
console.log(this); // undefined
// Node.js CommonJS top-level
console.log(this === module.exports); // true
// Node.js ESM
console.log(this); // undefined
Module contexts always have this === undefined.
Functions
Non-strict mode
function fn() {
return this;
}
fn(); // globalThis (window in browser)
Without an explicit binding, this defaults to the global object.
Strict mode
"use strict";
function fn() {
return this;
}
fn(); // undefined
Strict mode prevents accidental global access. ES modules are always strict.
Methods & objects
Object method
const obj = {
name: "obj",
getName() {
return this.name;
},
};
obj.getName(); // "obj" — this = object before the dot
this is determined by the call site, not where the function is defined.
Prototype chain
const proto = {
greet() {
return this.name;
},
};
const child = Object.create(proto);
child.name = "child";
child.greet(); // "child" — this = child, not proto
this always refers to the object the method was called on, even if the method lives on the prototype.
Constructors
new keyword
function Person(name) {
this.name = name; // this = new object
}
const p = new Person("Alice");
p.name; // "Alice"
new creates a fresh object and binds this to it.
Return override
function Foo() {
this.a = 1;
return { a: 99 }; // overrides this
}
new Foo().a; // 99
function Bar() {
this.a = 1;
return 42; // ignored (primitive)
}
new Bar().a; // 1
Returning an object from a constructor replaces this. Returning a primitive is ignored.
Arrow functions
Lexical binding
const obj = {
name: "outer",
getNameDelayed() {
setTimeout(() => {
console.log(this.name); // "outer"
}, 100);
},
};
Arrow functions have no own this — they inherit from the enclosing lexical scope at definition time.
call / apply / bind ignored
const arrow = () => this;
arrow.call({ a: 1 }); // outer this (NOT {a:1})
arrow.apply({ a: 1 }); // outer this
arrow.bind({ a: 1 })(); // outer this
You cannot rebind an arrow function's this. call, apply, and bind are all silently ignored.
Explicit binding
call()
function greet(greeting) {
return `${greeting}, ${this.name}`;
}
greet.call({ name: "Alice" }, "Hi");
// "Hi, Alice"
Calls immediately. Pass args individually.
apply()
function greet(greeting, punct) {
return `${greeting}, ${this.name}${punct}`;
}
greet.apply({ name: "Bob" }, ["Hey", "!"]);
// "Hey, Bob!"
Calls immediately. Pass args as array.
bind()
function greet(greeting) {
return `${greeting}, ${this.name}`;
}
const greetAlice = greet.bind({ name: "Alice" });
greetAlice("Hello"); // "Hello, Alice"
Returns a new function with this permanently bound. Binding only works once — re-binding a bound function is ignored.
Classes
Constructor
class Dog {
constructor(name) {
this.name = name; // this = new instance
}
}
new Dog("Rex").name; // "Rex"
Must call super() before using this in derived classes.
Methods
class Dog {
constructor(name) {
this.name = name;
}
bark() {
return `${this.name} says woof`;
}
static create(name) {
return new this(name); // this = Dog class
}
}
Instance methods: this = instance. Static methods: this = the class itself.
Arrow class fields
class Button {
constructor(label) {
this.label = label;
}
// Arrow = permanently bound to instance
handleClick = () => {
console.log(this.label);
};
}
const btn = new Button("OK");
const fn = btn.handleClick;
fn(); // "OK" — still works!
Arrow class fields solve the "lost this" problem in callbacks and event handlers.
Event handlers
addEventListener
button.addEventListener("click", function (e) {
console.log(this === button); // true
console.log(this === e.currentTarget); // true
});
In a regular function handler, this is the element the listener is attached to (e.currentTarget).
Arrow gotcha
button.addEventListener("click", (e) => {
console.log(this); // enclosing scope (NOT button!)
// Use e.currentTarget instead
console.log(e.currentTarget === button); // true
});
Arrow functions do not receive the element as this. Use e.currentTarget to access the element.
Common gotchas
Losing this
const obj = {
name: "obj",
getName() {
return this.name;
},
};
// Extracting method
const fn = obj.getName;
fn(); // undefined — lost this!
// Destructuring
const { getName } = obj;
getName(); // undefined — lost this!
// Callback
["a"].forEach(obj.getName); // undefined
Assigning a method to a variable or passing it as a callback detaches it from its object.
Fixes
// Fix 1: Arrow function
["a"].forEach(() => obj.getName());
// Fix 2: bind
["a"].forEach(obj.getName.bind(obj));
// Fix 3: Arrow class field
class Obj {
name = "obj";
getName = () => this.name; // always bound
}
setTimeout and setInterval also lose this — use the same fixes.
Also see
- MDN: this (developer.mozilla.org)
- MDN: Arrow functions (developer.mozilla.org)
- MDN: Function.prototype.bind() (developer.mozilla.org)
- MDN: Function.prototype.call() (developer.mozilla.org)
- MDN: globalThis (developer.mozilla.org)