What is the value of `this` in different contexts?
Top-level `this` in a script is the global object (`window` in browsers, `globalThis` in Node sloppy). In ES modules and strict mode it's `undefined`. Inside functions, it depends on how they're called; arrows inherit lexically.
this is the single most context-sensitive value in JavaScript, and "what does this print?" output questions are an interview favorite specifically because there are eight distinct rules and most candidates can only recall three. The safest mental model is "who called me, and how?" — not "where am I written?" — with one critical exception: arrow functions, which capture this lexically at definition time.
The rules, by context:
1. Top-level in a browser <script> (non-module). this === window. The script body runs in the global execution context.
2. Top-level in a browser <script type="module">. this === undefined. ES modules are strict by default, and the spec deliberately removed top-level this from modules so that module code can't accidentally leak into globals.
3. Top-level in Node CommonJS (.js with require). this === module.exports, which is an empty object at startup. This is one of the most-asked output puzzles for the Node interview because it surprises people who expect global.
4. Top-level in Node ES Module (.mjs or "type": "module"). this === undefined — same as browser modules.
5. Inside a regular function called bare (fn()). Default binding: undefined in strict mode (and inside ES modules); the global object (window / global / globalThis) in sloppy mode. This is the source of "I extracted a method to a variable and lost this" bugs.
6. Inside a method called as obj.fn(). Implicit binding: this === obj. Crucially, the binding is determined by the call expression, not the function reference. const f = obj.fn; f() loses the binding because the dot is gone.
7. Inside a constructor called with new Foo(). new binding: this is the freshly created object whose internal [[Prototype]] points at Foo.prototype. Highest priority — overrides any earlier bind.
8. Inside an arrow function. Lexical: arrows have no this of their own; lookup falls through to the enclosing function's this at the moment the arrow was defined. call/apply/bind cannot change an arrow's this.
9. Inside a class method. Same as regular method when called via the instance; lost if detached. Static methods' this is the class itself, not an instance.
10. Inside an event handler. element.addEventListener('click', function() { /* this === element */ }) — the dispatching element. With an arrow handler, this is the surrounding scope's. Same for inline onclick="this.style..." — this is the element.
11. Inside callbacks to forEach/map/filter etc. Default binding (so undefined in strict). Array.prototype.forEach accepts an optional second thisArg parameter that explicitly sets this inside the callback. Same for map, some, every, find.
12. With explicit call / apply / bind. Whatever you pass in. bind is one-shot and persistent; call/apply are for that single invocation. Higher priority than implicit but lower than new.
13. Inside setTimeout/setInterval callback with a regular function: default binding (so undefined strict / global sloppy). With an arrow: the enclosing this. This is why timer callbacks in classes often use arrows or explicit bind.
File 4's specific output question: console.log(this) at top level.
- In a browser DevTools console: the implementation-defined global, usually
Window. - In a
<script>tag in the browser:Window. - In a Node REPL: a special object (
globalin old versions,Object [global]summary). - In a
.jsfile run by Node (CommonJS):{}(it'smodule.exports). - In a
.mjsfile run by Node (ESM):undefined.
The interviewer is usually probing the Node vs browser distinction. The high-value answer is: "In Node CommonJS, top-level this is module.exports, which is empty; in a browser script it's window; in any module it's undefined."
Common output puzzles:
const obj = {
name: 'A',
arrow: () => this.name,
regular() { return this.name; }
};
obj.arrow(); // undefined (arrow's this is the module's, not obj)
obj.regular(); // 'A'
const f = obj.regular;
f(); // undefined / TypeError (lost this)Strict-mode strictness matters. ES modules and 'use strict' make default this undefined instead of globalThis. This catches bugs early but breaks legacy snippets pasted in from sloppy contexts.
Common mistakes:
- Using an arrow function as an object method when
thisshould refer to the object. - Detaching a method into a variable and calling it bare.
- Forgetting that callbacks (forEach, setTimeout, event handlers) reset
thisfor regular functions. - Conflating
this(call-site dependent) with closure variables (lexical).
Senior signal: name the eight contexts, recognize the Node CommonJS module.exports trick, and articulate that arrows are the only function form whose this is lexical.
Code
Follow-up questions
- •Why is module-level `this` undefined?
- •How does the new globalThis differ across environments?
- •What's `this` inside a setTimeout callback?
Common mistakes
- •Pasting code from one context into another and being surprised it broke.
- •Using arrow functions as object methods.
- •Forgetting that detaching a method (`const f = obj.fn`) loses `this`.
Performance considerations
- •There is no perf cost to `this` itself; the cost is mistakes (extra binds, creating new functions in render).
Edge cases
- •`this` inside a tagged template literal is the function's own `this`, not the template's.
- •`new Function('return this')()` returns globalThis even inside a strict module — because it's a Function constructor.
Real-world examples
- •Linters like `no-invalid-this` warn when `this` is read in a context where it's almost certainly a bug.