|

JavaScript 40 🧬 The this keyword — call/apply/bind

const user = {
  name: 'Alice',
  greet() {
    console.log('Hello, ' + this.name);
  }
};

user.greet();

function sayHi() {
  console.log('Hi, ' + this.name);
}

const bob = { name: 'Bob' };
sayHi.call(bob);

const alice = { name: 'Alice' };
sayHi.apply(alice);

const boundSayHi = sayHi.bind(bob);
boundSayHi();

const arrow = () => {
  console.log('Arrow this:', this);
};
arrow();

The this keyword is one of the most confusing parts of JavaScript — until you understand one simple rule: this is determined by how a function is called, not where it’s defined. Once you internalize that rule, everything else falls into place.

Key point: this refers to the execution context of a function — the object that the function is invoked on. Different call styles (obj.method(), func(), new Func(), func.call(ctx)) set this differently. Arrow functions are the exception: they inherit this from their enclosing scope.


a – What is this and how it’s determined

this is a special keyword inside every function. Its value depends on how the function was called at runtime.

The one rule to remember:

this = the object the function is called on.

The four ways this is set:

Call stylethis value
Method call — obj.fn()obj
Plain call — fn()undefined (strict) / globalThis (sloppy)
Constructor call — new Fn()The newly created object
Explicit — fn.call(ctx), fn.apply(ctx), fn.bind(ctx)()ctx

Method call — this is the object:

const user = {
  name: 'Alice',
  greet() {
    console.log('Hello, ' + this.name);
  }
};

user.greet();
// [ Hello, Alice ]

this is user because the function is called on user.

Plain call — this is undefined (in strict mode):

'use strict';

function sayHi() {
  console.log(this);
}

sayHi();
// [ undefined ]

In sloppy (non-strict) mode, this would be globalThis:

function sayHi() {
  console.log(this === globalThis);
}
sayHi();
// [ true ]  (in Node.js: global, in browsers: window)

Always use 'use strict' or ES modules so you get undefined instead of the global object. That way, mistakes like this.name on an unbound function throw instead of silently polluting globals.

Constructor call — this is the new object:

function Person(name) {
  this.name = name;
}

const p = new Person('Alice');
console.log(p.name);
// [ Alice ]

new Person('Alice') creates a fresh object, sets this to it, and returns it.

Class methods behave the same:

class Counter {
  constructor() {
    this.count = 0;
  }
  increment() {
    this.count++;
  }
}

const c = new Counter();
c.increment();
console.log(c.count);
// [ 1 ]

Extracting a method loses this:

const user = {
  name: 'Alice',
  greet() {
    console.log('Hello, ' + this.name);
  }
};

const g = user.greet;
g();
// [ Hello, undefined ]  ← this is not user anymore

The call is what matters — not the property. g() is a plain call, so this is undefined (or globalThis).

Why this matters:

  • It lets methods access their own object’s data
  • It enables sharing of one function across many objects
  • It’s essential for callbacks, event handlers, and library design
  • Understanding it prevents the classic “this is undefined” bug

b – call, apply, and bind

You can explicitly control what this is with call, apply, and bind. These are methods on every function.

call — invoke with a given this and arguments:

function greet(greeting, punctuation) {
  console.log(greeting + ', ' + this.name + punctuation);
}

const bob = { name: 'Bob' };
greet.call(bob, 'Hello', '!');
// [ Hello, Bob! ]

Signature: fn.call(thisArg, arg1, arg2, ...)

apply — same as call, but arguments as an array:

greet.apply(bob, ['Hi', '?']);
// [ Hi, Bob? ]

Signature: fn.apply(thisArg, [args])

call vs apply — the only difference:

MethodArgument style
callIndividual arguments
applyArray of arguments

Before ES6 spread, apply was the way to pass a dynamic argument list. Today, fn(...args) is more idiomatic — but apply is still common in older code.

bind — return a new function with this fixed:

const boundGreet = greet.bind(bob);
boundGreet('Hey', '.');
// [ Hey, Bob. ]

bind doesn’t call the function — it returns a new function with this permanently set. You can also partially apply arguments:

const greetBob = greet.bind(bob, 'Hello');
greetBob('!');
// [ Hello, Bob! ]

The three compared:

function describe(age, city) {
  console.log(`${this.name}, ${age}, ${city}`);
}

const alice = { name: 'Alice' };

describe.call(alice, 30, 'Paris');
// [ Alice, 30, Paris ]

describe.apply(alice, [30, 'Paris']);
// [ Alice, 30, Paris ]

const bound = describe.bind(alice, 30);
bound('Paris');
// [ Alice, 30, Paris ]

A table of differences:

MethodCalls function?ArgumentsReturns
callYesIndividualReturn value
applyYesArrayReturn value
bindNoIndividual (partial)New function

Using bind for event handlers:

class Button {
  constructor(label) {
    this.label = label;
  }
  click() {
    console.log('Clicked: ' + this.label);
  }
}

const btn = new Button('Save');
const handler = btn.click.bind(btn);
handler();
// [ Clicked: Save ]

Without bind, this would be lost when the handler is called by the event system.

Borrowing methods from other objects:

const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };

const realArray = Array.prototype.slice.call(arrayLike);
console.log(realArray);
// [ [ 'a', 'b', 'c' ] ]

This pattern was common before Array.from existed.

bind for partial application:

function multiply(a, b) {
  return a * b;
}

const double = multiply.bind(null, 2);
const triple = multiply.bind(null, 3);

console.log(double(5));
// [ 10 ]
console.log(triple(5));
// [ 15 ]

The null is the thisArg — irrelevant here because multiply doesn’t use this.

bind is permanent:

const bound = greet.bind(bob);
const rebound = bound.bind(alice);  // no effect
rebound('Hey', '.');
// [ Hey, Bob. ]  ← still Bob

You can’t re-bind a bound function.

call and apply with no this needed:

function sum(a, b) {
  return a + b;
}

console.log(sum.call(null, 1, 2));
// [ 3 ]
console.log(sum.apply(null, [1, 2]));
// [ 3 ]

Pass null or undefined when the function doesn’t use this.

A practical example — logging with context:

const logger = {
  prefix: '[LOG]',
  log(msg) {
    console.log(`${this.prefix} ${msg}`);
  }
};

logger.log('started');
// [ [LOG] started ]

const detached = logger.log;
const bound = detached.bind(logger);
bound('still works');
// [ [LOG] still works ]

Real-world use — array methods on array-likes:

function argsToArray() {
  return Array.prototype.slice.call(arguments);
}

function show() {
  console.log(argsToArray.apply(null, arguments));
}

bind in callbacks:

class Timer {
  constructor() {
    this.seconds = 0;
  }
  tick() {
    this.seconds++;
    console.log(this.seconds);
  }
  start() {
    this.interval = setInterval(this.tick.bind(this), 1000);
  }
}

Without bind, this inside tick would be undefined (or the timer object) because setInterval calls the function with no context.


c – Arrow functions and this

Arrow functions are the exception to every rule above. They don’t have their own this — they inherit it from where they’re defined.

Regular function vs arrow function:

const obj = {
  name: 'Alice',
  regular() {
    console.log(this.name);
  },
  arrow: () => {
    console.log(this.name);
  }
};

obj.regular();
// [ Alice ]

obj.arrow();
// [ undefined ]  ← this is not obj

The arrow function inherits this from the enclosing lexical scope — in this case, the module or global scope, where this.name is undefined.

this is captured at definition time:

const obj = {
  name: 'Alice',
  greet() {
    const inner = () => {
      console.log(this.name);
    };
    inner();
  }
};

obj.greet();
// [ Alice ]

The inner arrow inherits this from greet, which is obj.

The same with a regular function would fail:

const obj = {
  name: 'Alice',
  greet() {
    function inner() {
      console.log(this.name);   // this is undefined
    }
    inner();
  }
};

obj.greet();
// TypeError: Cannot read properties of undefined

Arrow functions and call/apply/bind:

These have no effect on arrow functions:

const arrow = () => {
  console.log(this);
};

const obj = { name: 'Alice' };
arrow.call(obj);
// [ undefined ]  ← still lexical this

You can’t override an arrow’s this.

When arrows shine — callbacks that need this:

class Counter {
  constructor() {
    this.count = 0;
  }
  start() {
    setInterval(() => {
      this.count++;
      console.log(this.count);
    }, 1000);
  }
}

The arrow captures this from start, which is the Counter instance. No bind needed.

Comparison table:

AspectRegular functionArrow function
Own this?YesNo (lexical)
call/apply/bindWorksIgnored
ConstructorWorks with new❌ TypeError
arguments objectAvailableNot available
Method syntax✅ Recommended❌ Avoid
CallbacksNeeds bindInherits naturally

When to use arrow functions:

  • Callbacks inside methods
  • Array methods (map, filter, reduce)
  • Anywhere you want the outer this
  • Never for object methods or constructors

When NOT to use arrow functions:

  • Object methodsobj.arrow = () => {} breaks this
  • Class methods — use regular methods
  • Constructors — arrows can’t be called with new
  • Event handlers that need event.currentTarget via this (use event.target instead)

The visual:

┌──────────────────────────────────────────────┐
│           Regular function                   │
│                                              │
│  function fn() {                             │
│    this  ← set by caller                     │
│  }                                           │
│                                              │
│  obj.fn()      → this = obj                  │
│  fn()          → this = undefined            │
│  new fn()      → this = new object           │
│  fn.call(x)    → this = x                    │
│  fn.bind(x)()  → this = x                    │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│           Arrow function                     │
│                                              │
│  const fn = () => {                          │
│    this  ← captured at definition            │
│  }                                           │
│                                              │
│  obj.fn()      → this = lexical              │
│  fn()          → this = lexical              │
│  fn.call(x)    → this = lexical              │
│  fn.bind(x)()  → this = lexical              │
│                                              │
└──────────────────────────────────────────────┘

Complete Example Session

// ============================================
// PART 1: METHOD CALL
// ============================================

const user = {
  name: 'Alice',
  greet() {
    console.log('Hello, ' + this.name);
  }
};

user.greet();
// [ Hello, Alice ]

// ============================================
// PART 2: PLAIN CALL
// ============================================

'use strict';

function sayHi() {
  console.log(this);
}

sayHi();
// [ undefined ]

// ============================================
// PART 3: CONSTRUCTOR CALL
// ============================================

function Person(name) {
  this.name = name;
}

const p = new Person('Alice');
console.log(p.name);
// [ Alice ]

// ============================================
// PART 4: DETACHED METHOD
// ============================================

const detached = user.greet;
detached();
// [ Hello, undefined ]

// ============================================
// PART 5: CALL
// ============================================

function greet(greeting, punctuation) {
  console.log(greeting + ', ' + this.name + punctuation);
}

const bob = { name: 'Bob' };
greet.call(bob, 'Hello', '!');
// [ Hello, Bob! ]

// ============================================
// PART 6: APPLY
// ============================================

greet.apply(bob, ['Hi', '?']);
// [ Hi, Bob? ]

// ============================================
// PART 7: BIND
// ============================================

const boundGreet = greet.bind(bob);
boundGreet('Hey', '.');
// [ Hey, Bob. ]

// ============================================
// PART 8: PARTIAL APPLICATION WITH BIND
// ============================================

const greetBob = greet.bind(bob, 'Hello');
greetBob('!');
// [ Hello, Bob! ]

// ============================================
// PART 9: BIND IS PERMANENT
// ============================================

const alice = { name: 'Alice' };
const rebound = boundGreet.bind(alice);
rebound('Hey', '.');
// [ Hey, Bob. ]

// ============================================
// PART 10: BORROWING METHODS
// ============================================

const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };

const realArray = Array.prototype.slice.call(arrayLike);
console.log(realArray);
// [ [ 'a', 'b', 'c' ] ]

// ============================================
// PART 11: PARTIAL APPLICATION
// ============================================

function multiply(a, b) {
  return a * b;
}

const double = multiply.bind(null, 2);
console.log(double(5));
// [ 10 ]

// ============================================
// PART 12: ARROW FUNCTIONS
// ============================================

const obj = {
  name: 'Alice',
  regular() {
    console.log(this.name);
  },
  arrow: () => {
    console.log(this.name);
  }
};

obj.regular();
// [ Alice ]

obj.arrow();
// [ undefined ]

// ============================================
// PART 13: ARROW INSIDE METHOD
// ============================================

const obj2 = {
  name: 'Alice',
  greet() {
    const inner = () => {
      console.log(this.name);
    };
    inner();
  }
};

obj2.greet();
// [ Alice ]

// ============================================
// PART 14: REGULAR FUNCTION INSIDE METHOD
// ============================================

const obj3 = {
  name: 'Alice',
  greet() {
    function inner() {
      console.log(this.name);
    }
    inner();
  }
};

try {
  obj3.greet();
} catch (err) {
  console.log(err.message);
}
// [ Cannot read properties of undefined (reading 'name') ]

// ============================================
// PART 15: ARROWS IGNORE CALL/APPLY/BIND
// ============================================

const arrow = () => {
  console.log(this);
};

const o = { name: 'Alice' };
arrow.call(o);
// [ undefined ]

// ============================================
// PART 16: BIND FOR EVENT HANDLERS
// ============================================

class Button {
  constructor(label) {
    this.label = label;
  }
  click() {
    console.log('Clicked: ' + this.label);
  }
}

const btn = new Button('Save');
const handler = btn.click.bind(btn);
handler();
// [ Clicked: Save ]

// ============================================
// PART 17: ARROW FOR SETINTERVAL
// ============================================

class Counter {
  constructor() {
    this.count = 0;
  }
  start() {
    setTimeout(() => {
      this.count++;
      console.log(this.count);
    }, 100);
  }
}

new Counter().start();
// [ 1 ]

// ============================================
// PART 18: COMPARING CALL/APPLY/BIND
// ============================================

function describe(age, city) {
  console.log(`${this.name}, ${age}, ${city}`);
}

const alice2 = { name: 'Alice' };

describe.call(alice2, 30, 'Paris');
// [ Alice, 30, Paris ]

describe.apply(alice2, [30, 'Paris']);
// [ Alice, 30, Paris ]

const bound = describe.bind(alice2, 30);
bound('Paris');
// [ Alice, 30, Paris ]

// ============================================
// PART 19: LOGGER WITH BOUND METHOD
// ============================================

const logger = {
  prefix: '[LOG]',
  log(msg) {
    console.log(`${this.prefix} ${msg}`);
  }
};

const detachedLog = logger.log;
try {
  detachedLog('oops');
} catch (err) {
  console.log(err.message);
}
// [ Cannot read properties of undefined (reading 'prefix') ]

const boundLog = detachedLog.bind(logger);
boundLog('fixed');
// [ [LOG] fixed ]

// ============================================
// PART 20: FULL SCRIPT
// ============================================

const user40 = {
  name: 'Alice',
  greet() {
    console.log('Hello, ' + this.name);
  }
};

user40.greet();

function sayHi40() {
  console.log('Hi, ' + this.name);
}

const bob40 = { name: 'Bob' };
sayHi40.call(bob40);

const alice40 = { name: 'Alice' };
sayHi40.apply(alice40);

const boundSayHi = sayHi40.bind(bob40);
boundSayHi();

const arrow40 = () => {
  console.log('Arrow this:', this);
};
arrow40();

Quick Reference

The Four this Rules

Call stylethis value
obj.method()obj
fn()undefined (strict)
new Fn()New object
fn.call/apply/bind(ctx)ctx

call, apply, bind

MethodCalls fn?ArgsReturns
callYesIndividualReturn value
applyYesArrayReturn value
bindNoIndividualNew function

Function this

TypeOwn this?call/apply/bind
Regular functionYesWorks
Arrow functionNo (lexical)Ignored
Method shorthandYesWorks
Class methodYesWorks

call/apply/bind Syntax

MethodSignatureExample
callfn.call(ctx, a, b)greet.call(bob, 'Hi', '!')
applyfn.apply(ctx, [a, b])greet.apply(bob, ['Hi', '!'])
bindfn.bind(ctx, a)greet.bind(bob, 'Hi')

Common Patterns

PatternCode
Fix methodobj.method.bind(obj)
Partial applyfn.bind(null, x)
Borrow methodArray.prototype.slice.call(arrLike)
Arrow callbackarr.map(x => this.foo(x))
Event handlerelement.addEventListener('click', this.handler.bind(this))

Arrow vs Regular

AspectArrowRegular
thisLexicalDynamic
call/applyIgnoredWorks
new
arguments
Method syntax
Callback⚠️ needs bind

Strict Mode

ModePlain call fn()
Strict ('use strict')this = undefined
Sloppythis = globalThis

Best Practices

Do This:

// Use arrow functions for callbacks
arr.map(x => this.foo(x));                     // ✅

// Bind methods passed as callbacks
setInterval(this.tick.bind(this), 1000);       // ✅

// Use call/apply for borrowing
Array.prototype.slice.call(arrayLike);         // ✅

// Use bind for partial application
const double = multiply.bind(null, 2);         // ✅

// Use 'use strict' or ES modules
'use strict';                                  // ✅

// Use regular functions for object methods
const obj = {
  method() { return this.x; }
};                                              // ✅

// Use arrow for outer this
class Timer {
  start() {
    setTimeout(() => this.tick(), 1000);
  }
}                                              // ✅

// Pass null when this isn't needed
sum.call(null, 1, 2);                          // ✅

Don’t Do This:

// Don't use arrow functions as methods
const obj = {
  arrow: () => this.x                        // ❌ this is lexical
};

// Don't detach methods without binding
const g = user.greet;
g();                                           // ❌ this lost

// Don't rely on sloppy mode this
function fn() { return this; }                 // ❌ globalThis
fn();

// Don't forget bind in callbacks
setInterval(this.tick, 1000);                  // ❌ this lost
setInterval(() => this.tick(), 1000);          // ✅
setInterval(this.tick.bind(this), 1000);       // ✅

// Don't use call/apply on arrows
arrow.call(ctx);                               // ❌ ignored

// Don't use new with arrows
new (() => {})();                              // ❌ TypeError

// Don't rebind a bound function
const bound = fn.bind(a);
const rebound = bound.bind(b);                 // ⚠️  no effect

Common Pitfalls

PitfallProblemSolution
Detached methodthis lostbind
Arrow as methodthis is lexicalUse regular method
Callback without bindthis undefinedArrow or bind
call on arrowIgnoredUse regular function
new with arrowTypeErrorUse regular function
Sloppy modethis is global'use strict'
Rebinding boundNo effectBind once
Assuming this is the functionWrong modelthis is the caller

Real-World Examples

1. Method Call

const user = {
  name: 'Alice',
  greet() {
    console.log('Hello, ' + this.name);
  }
};

user.greet();
// [ Hello, Alice ]

2. Detached Method Loses this

const g = user.greet;
g();
// [ Hello, undefined ]

3. call

function greet(greeting, punctuation) {
  console.log(greeting + ', ' + this.name + punctuation);
}

const bob = { name: 'Bob' };
greet.call(bob, 'Hello', '!');
// [ Hello, Bob! ]

4. apply

greet.apply(bob, ['Hi', '?']);
// [ Hi, Bob? ]

5. bind

const boundGreet = greet.bind(bob);
boundGreet('Hey', '.');
// [ Hey, Bob. ]

6. Partial Application with bind

function multiply(a, b) {
  return a * b;
}

const double = multiply.bind(null, 2);
console.log(double(5));
// [ 10 ]

7. Borrow Array Method

const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
const arr = Array.prototype.slice.call(arrayLike);
console.log(arr);
// [ [ 'a', 'b', 'c' ] ]

8. Arrow Inside Method

const obj = {
  name: 'Alice',
  greet() {
    const inner = () => console.log(this.name);
    inner();
  }
};

obj.greet();
// [ Alice ]

9. Regular Function Inside Method Fails

const obj = {
  name: 'Alice',
  greet() {
    function inner() {
      console.log(this.name);
    }
    inner();
  }
};

try {
  obj.greet();
} catch (err) {
  console.log(err.message);
}
// [ Cannot read properties of undefined (reading 'name') ]

10. Arrow as Method Fails

const obj = {
  name: 'Alice',
  arrow: () => console.log(this.name)
};

obj.arrow();
// [ undefined ]

11. Arrows Ignore call/apply

const arrow = () => console.log(this);
const o = { name: 'Alice' };
arrow.call(o);
// [ undefined ]

12. Bind for Event Handler

class Button {
  constructor(label) {
    this.label = label;
  }
  click() {
    console.log('Clicked: ' + this.label);
  }
}

const btn = new Button('Save');
const handler = btn.click.bind(btn);
handler();
// [ Clicked: Save ]

13. Arrow for setInterval

class Counter {
  constructor() {
    this.count = 0;
  }
  start() {
    setTimeout(() => {
      this.count++;
      console.log(this.count);
    }, 100);
  }
}

new Counter().start();
// [ 1 ]

14. Constructor this

function Person(name) {
  this.name = name;
}

const p = new Person('Alice');
console.log(p.name);
// [ Alice ]

15. Class Method this

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  move(dx, dy) {
    this.x += dx;
    this.y += dy;
  }
}

const pt = new Point(0, 0);
pt.move(1, 2);
console.log(pt.x, pt.y);
// [ 1 2 ]

16. Bind Once

function fn() { return this.x; }
const a = { x: 1 };
const b = { x: 2 };

const boundA = fn.bind(a);
const rebound = boundA.bind(b);
console.log(rebound());
// [ 1 ]  ← still bound to a

17. Chaining with Method Return this

class Builder {
  constructor() {
    this.parts = [];
  }
  add(part) {
    this.parts.push(part);
    return this;
  }
  build() {
    return this.parts.join('-');
  }
}

const result = new Builder().add('a').add('b').add('c').build();
console.log(result);
// [ a-b-c ]

18. Borrow Math Method

const numbers = [5, 3, 9, 1];
const max = Math.max.apply(null, numbers);
console.log(max);
// [ 9 ]

const max2 = Math.max(...numbers);
console.log(max2);
// [ 9 ]

19. Strict vs Sloppy this

function sloppyThis() {
  return this;
}

function strictThis() {
  'use strict';
  return this;
}

console.log(typeof sloppyThis());
// [ object ]

console.log(typeof strictThis());
// [ undefined ]

20. Full Script

const user = {
  name: 'Alice',
  greet() {
    console.log('Hello, ' + this.name);
  }
};

user.greet();

function sayHi() {
  console.log('Hi, ' + this.name);
}

const bob = { name: 'Bob' };
sayHi.call(bob);

const alice = { name: 'Alice' };
sayHi.apply(alice);

const boundSayHi = sayHi.bind(bob);
boundSayHi();

const arrow = () => {
  console.log('Arrow this:', this);
};
arrow();

Visual: this Determination

┌──────────────────────────────────────────────┐
│  How was the function called?                │
│                                              │
│  obj.method()  ──► this = obj                │
│                                              │
│  fn()          ──► this = undefined (strict) │
│                    this = globalThis (sloppy)│
│                                              │
│  new Fn()      ──► this = new object         │
│                                              │
│  fn.call(x)    ──► this = x                  │
│  fn.apply(x)   ──► this = x                  │
│  fn.bind(x)()  ──► this = x                  │
│                                              │
│  Arrow function: this = enclosing scope      │
│                                              │
└──────────────────────────────────────────────┘

Visual: call vs apply vs bind

┌──────────────────────────────────────────────┐
│  fn.call(ctx, a, b)                          │
│    │                                         │
│    └──► calls fn immediately                 │
│         with this = ctx, args a, b           │
│         returns fn's return value            │
│                                              │
│  fn.apply(ctx, [a, b])                       │
│    │                                         │
│    └──► same as call, but args as array      │
│                                              │
│  fn.bind(ctx, a)                             │
│    │                                         │
│    └──► returns new function                 │
│         this = ctx, a pre-applied            │
│         call the new fn to run it            │
│                                              │
└──────────────────────────────────────────────┘

Visual: Arrow vs Regular this

┌──────────────────────────────────────────────┐
│  Regular function:                           │
│                                              │
│  obj = {                                     │
│    name: 'Alice',                            │
│    greet() {        ← function               │
│      // this = obj when called as obj.greet()│
│    }                                         │
│  }                                           │
│                                              │
│  obj.greet()  → this = obj ✅                │
│  greet()      → this = undefined ❌          │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  Arrow function:                             │
│                                              │
│  obj = {                                     │
│    name: 'Alice',                            │
│    arrow: () => {   ← arrow                  │
│      // this = whatever was outside obj      │
│    }                                         │
│  }                                           │
│                                              │
│  obj.arrow()  → this ≠ obj ❌                │
│                                              │
│  Use regular functions for methods!          │
│                                              │
└──────────────────────────────────────────────┘

Summary

ConceptSyntaxExample
Method thisobj.method()this = obj
Plain thisfn()this = undefined
Constructor thisnew Fn()this = new object
callfn.call(ctx, a, b)Invoke with ctx
applyfn.apply(ctx, [a, b])Same, array args
bindfn.bind(ctx)Return new fn
Partial applyfn.bind(null, x)Pre-fill args
BorrowArray.prototype.slice.call(x)Use method of another type
Arrow this() => thisLexical
Arrow + callarrow.call(x)Ignored
Strict mode'use strict'this = undefined

Key takeaways:

  • this depends on how a function is called, not where it’s defined
  • obj.method()this is obj
  • fn()this is undefined in strict mode, globalThis in sloppy
  • new Fn()this is the new object
  • call, apply, bind let you set this explicitly
  • call and apply invoke immediately; bind returns a new function
  • call takes individual args, apply takes an array
  • Arrow functions inherit this from the enclosing scope and ignore call/apply/bind
  • Use regular functions for object methods and constructors
  • Use arrow functions for callbacks where you want the outer this
  • Use bind when you need to pass a method as a callback and keep this
  • Partial application via bind is a powerful pattern for reusable functions

Remember: this is set by the call, not the definition. Method call → the object. Plain call → undefined. Constructor → the new object. call/apply/bind → whatever you pass. Arrow functions are the exception — they don’t have their own this. Use arrows for callbacks, regular functions for methods. When in doubt, log this and trace how the function was invoked. Master this, and the most confusing part of JavaScript becomes the most predictable.


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!