JAVASCRIPT / SCOPE, CLOSURES, AND THIS
What this means in plain functions
Predict and verify what this is inside a plain f() call: globalThis in sloppy code, undefined in strict code, and why the callee decides which.
What you will learn
- Predict this in a plain f() call: globalThis in sloppy mode, undefined in strict mode
- Know the callee's own strictness, not the caller's, picks the fallback
- Spot silent global writes when this.x = ... runs in a sloppy plain call
- Rewrite such functions to take parameters instead of reading ambient this
Understanding What this means in plain functions
A plain call is f(): the function invoked by itself, with nothing before a dot, no new, no explicit binding. In JavaScript the call site is what supplies this, so when a plain call supplies nothing, the language falls back to its default binding rule. In sloppy (non-strict) code that fallback is the global object, spelled globalThis (window in browsers, global in Node); in strict code there is no substitute at all and this is undefined.
The mental model that keeps this straight: every call passes one invisible extra argument named this, and the call form fills it in. A plain call leaves the slot blank, and which fallback gets used depends on the mode of the function being entered, not on the function doing the calling. A strict function that calls a sloppy function does not hand its own undefined down; where a function is written matters only because that is what fixes its mode, since a function inside a strict function or inside a module is strict too.
Sloppy mode makes this rule dangerous because the mistake is silent: this.total = 0 in a plainly called function creates or overwrites a global variable that every call shares, so you get wrong numbers rather than an error. Strict mode turns the same line into an immediate TypeError, which is why ES modules and class bodies are strict by default and why a plain call inside a module sees undefined. The practical consequence is that a function you intend to call plainly should not read this at all; give it parameters and the question stops existing.
Once you separate the two questions, most this confusion in ordinary functions resolves quickly: ask what the call form is, then ask what mode the called function is in.
function reveal() {
return this;
}
function revealStrict() {
'use strict';
return this;
}
function strictCaller() {
'use strict';
return reveal();
}
console.log('sloppy plain call is global:', reveal() === globalThis);
console.log('strict plain call:', revealStrict());
console.log('strictness follows the callee:', strictCaller() === globalThis);A plain call passes no receiver, so this comes from the default binding rule: the global object in sloppy code, undefined in strict code.
Worked examples
Strict mode removes the fallback
Shows that a strict function called plainly gets undefined, and that touching a property on it throws.
'use strict';
function bump() {
console.log('this inside bump:', this);
this.n = 1;
}
try {
bump();
} catch (err) {
console.log('threw:', err.name);
}Example explained
Line 1The file-level 'use strict' directive applies to bump, so its default binding is undefined instead of globalThis.
Line 2The console.log runs first and proves the receiver is missing before anything fails.
Line 3this.n = 1 throws because you cannot set a property on undefined.
Line 4err.name is checked instead of err.message because engines word the message differently.
this is not passed down the call stack
Demonstrates that calling a function through a helper still produces the default binding for that function's own mode.
function run(fn) {
return fn();
}
function sloppyProbe() {
return this === globalThis ? 'global' : 'not global';
}
function strictProbe() {
'use strict';
return this === undefined ? 'undefined' : 'something else';
}
console.log(run(sloppyProbe));
console.log(run(strictProbe));
console.log(run(sloppyProbe) === sloppyProbe());Example explained
Line 1run invokes fn() with nothing in front of it, which is a plain call no matter who wrote the call.
Line 2sloppyProbe still sees globalThis; run's own this is never forwarded into it.
Line 3strictProbe reports undefined because its own directive, not the caller, removes the fallback.
Line 4The last line shows that going through a wrapper gives exactly the same result as calling directly.
Silent global pollution in sloppy mode
Shows how writing to this in a plainly called sloppy function shares one global object across every call.
function makeTally() {
this.total = 0;
return this;
}
const a = makeTally();
a.total = 5;
const b = makeTally();
console.log(a === b);
console.log(a.total);
console.log('total' in globalThis);Example explained
Line 1makeTally is called plainly in sloppy code, so this is globalThis and this.total = 0 writes a global.
Line 2Both calls return that same global object, so a === b is true and there is no per-call state.
Line 3The second call resets total, so the 5 stored through a is gone and 0 prints, with no error anywhere.
Line 4The membership test confirms the function left a property behind on the global namespace.
Important notes
Top-level this is a separate question from the default binding: it is globalThis in a classic script, module.exports in CommonJS, and undefined in an ES module, so do not use it to probe what plain calls do.
The sloppy snippets here assume non-module, non-strict surroundings; run them in a browser console or a plain .js file, since module code makes every function in it strict.
Common mistakes
Assuming a plainly called function inherits this from whatever function called it; in sloppy mode you then read properties off globalThis and quietly get undefined or NaN instead of an error.
Adding 'use strict' but keeping this.x inside a plain call, then reading the resulting "Cannot read properties of undefined" as a missing object rather than a missing receiver.
Testing the rule inside <script type="module"> or an .mjs file and concluding this is always undefined, because module code is strict and disagrees with the same snippet typed in the console.
Try it yourself
Change, predict, then run
In a browser console, define function ping() { this.pings = (this.pings || 0) + 1; return this.pings; }, call ping() three times and then inspect window.pings. Add 'use strict'; as the first line of the function body, redefine it, and confirm the first ping() now throws a TypeError.
Open the JavaScript workspaceCheck your understanding
A sloppy-mode function inner() { return this; } is defined at the top level of a classic script. A separate function with its own 'use strict' directive calls inner(). What does inner return?
- globalThis, because the fallback is chosen by inner's own non-strict mode
- undefined, because the strict caller's mode applies to calls it makes
- The calling function object, since inner was invoked from inside it
- undefined, because any call with no receiver always yields undefined
Show answer
Strictness is a property of the code of the function being entered, so inner stays sloppy and its blank this slot is filled with globalThis. Option 1 is tempting because strictness does spread lexically, but only to functions written inside the strict code; calling a sloppy function from strict code does not make it strict, and nothing about the caller's this is transferred.