js-dojo

1 the four invocation forms

GOAL

Burn in the rule that `this` has nothing to do with WHERE a function was written — it is chosen anew at every CALL SITE. One function, four ways to call it, four different `this`.

CONCEPT

1. plain call fn() → undefined (strict mode) 2. method call obj.fn() → obj (receiver left of dot) 3. explicit fn.call(x) → x (also .apply / .bind) 4. constructor new Fn() → a brand-new object, whose hidden [[Prototype]] is wired to Fn.prototype (module 1!).

HINT

Look at each quiz line, work out what the CALL SITE dictates, then type the exact string/boolean that call hands back.

MIRRORS

A game engine you don't own runs YOUR callbacks with a `this` it picks for you: an onHit hook runs with this = the player that was struck, an input-mapping callback with this = the input handle, a level hook with this = the game — every one is form 3 (.call) underneath.

Run

node 01-four-invocation-forms.cjs

Source

'use strict';
// ---------------------------------------------------------------------
// PROTO-DOJO · Module 2 / Exercise 1 — the four invocation forms
//
// GOAL:    Burn in the rule that `this` has nothing to do with WHERE a
//          function was written — it is chosen anew at every CALL SITE.
//          One function, four ways to call it, four different `this`.
// CONCEPT: 1. plain call        fn()            → undefined (strict
//          mode) 2. method call       obj.fn()        → obj (receiver
//          left of dot) 3. explicit          fn.call(x)      → x  (also
//          .apply / .bind) 4. constructor       new Fn()        → a
//          brand-new object, whose hidden [[Prototype]] is wired to
//          Fn.prototype (module 1!).
// HINT:    Look at each quiz line, work out what the CALL SITE
//          dictates, then type the exact string/boolean that call hands
//          back.
// MIRRORS: A game engine you don't own runs YOUR callbacks with a
//          `this` it picks for you: an onHit hook runs with this = the
//          player that was struck, an input-mapping callback with this
//          = the input handle, a level hook with this = the game —
//          every one is form 3 (.call) underneath.
//
// Run: node 01-four-invocation-forms.cjs
// ---------------------------------------------------------------------
const assert = require('node:assert');

function reportOwner() {
  if (this === undefined) return 'no-owner';
  return 'owner:' + this.id;
}

const knightScreen = { id: 'screen-knight', reportOwner: reportOwner };

function InputHandle(id) {
  this.id = id;
}
InputHandle.prototype.reportOwner = reportOwner;

// -- TODO 1: replace every '???' with the exact string / boolean produced ---
const quiz = {
  // FORM 1 — plain call: reportOwner()
  // ('use strict' at the top of this file means NO fallback to the global object)
  plainCall: '???',

  // FORM 2 — method call: knightScreen.reportOwner()
  // the receiver left of the dot becomes `this`
  methodCall: '???',

  // FORM 3 — explicit: reportOwner.call({ id: 'screen-rogue' })
  explicitCall: '???',

  // FORM 4 — constructor: new InputHandle('gamepad-1').reportOwner()
  // `new` builds a fresh object, wires its hidden link to InputHandle.prototype,
  // runs the constructor with `this` = that fresh object… and then the
  // .reportOwner() part is an ordinary FORM 2 call on the result.
  constructorCall: '???',

  // and the wiring `new` performed, true or false:
  // Object.getPrototypeOf(new InputHandle('x')) === InputHandle.prototype
  newLinksPrototype: '???',
};

// -- checks -----------------------------------------------------------
assert.strictEqual(quiz.plainCall, reportOwner(),
  'FORM 1: strict-mode plain call → this is undefined, no global fallback');
assert.strictEqual(quiz.methodCall, knightScreen.reportOwner(),
  'FORM 2: the receiver left of the dot becomes this');
assert.strictEqual(quiz.explicitCall, reportOwner.call({ id: 'screen-rogue' }),
  'FORM 3: .call aims this explicitly — the engine-invokes-your-hook form');
assert.strictEqual(quiz.constructorCall, new InputHandle('gamepad-1').reportOwner(),
  'FORM 4: this = the freshly created object, then a method call on it');
assert.strictEqual(
  quiz.newLinksPrototype,
  Object.getPrototypeOf(new InputHandle('x')) === InputHandle.prototype,
  '`new` wires the hidden link to Fn.prototype — module 1 says hello'
);

console.log('PASS — 01-four-invocation-forms: the call site decides, every time');

Solution