js-dojo

3 call / apply / bind

GOAL

Put all three explicit-this tools through their paces on one shared name-tag formatter: .call (args listed one by one), .apply (args packed into an array), .bind (build a NEW function with this — and optionally leading args — pre-filled), plus the rule that a bound this is PERMANENT.

CONCEPT

fn.call(thisArg, a, b) — run now, args listed fn.apply(thisArg, [a, b]) — run now, args as one array fn.bind(thisArg, a) — run LATER; hands back a new function with this welded and `a` pre-filled (partial application). Re-aiming a bound function with .call is silently ignored.

HINT

TODO 3 should hand you a name-tag formatter the hud can call later, not a finished name tag — bind manufactures, it never invokes.

MIRRORS

Any engine or UI library you don't own runs YOUR hud/label callback with .call(context) so `this` is the thing being drawn; your own wrapper code pre-binds those callbacks to a specific game before dropping them into a settings object.

Run

node 03-call-apply-bind.cjs

Source

'use strict';
// ---------------------------------------------------------------------
// PROTO-DOJO · Module 2 / Exercise 3 — call / apply / bind
//
// GOAL:    Put all three explicit-this tools through their paces on one
//          shared name-tag formatter: .call (args listed one by one),
//          .apply (args packed into an array), .bind (build a NEW
//          function with this — and optionally leading args —
//          pre-filled), plus the rule that a bound this is PERMANENT.
// CONCEPT: fn.call(thisArg, a, b)  — run now, args listed
//          fn.apply(thisArg, [a, b]) — run now, args as one array
//          fn.bind(thisArg, a)    — run LATER; hands back a new
//          function with this welded and `a` pre-filled (partial
//          application). Re-aiming a bound function with .call is
//          silently ignored.
// HINT:    TODO 3 should hand you a name-tag formatter the hud can call
//          later, not a finished name tag — bind manufactures, it never
//          invokes.
// MIRRORS: Any engine or UI library you don't own runs YOUR hud/label
//          callback with .call(context) so `this` is the thing being
//          drawn; your own wrapper code pre-binds those callbacks to a
//          specific game before dropping them into a settings object.
//
// Run: node 03-call-apply-bind.cjs
// ---------------------------------------------------------------------
const assert = require('node:assert');

// One name-tag formatter shared by every player on the roster — `this`
// decides
// which player it speaks for at each call.
function formatNameTag(decimals, suffix) {
  return this.name + ' ' + this.score.toFixed(decimals) + (suffix || '');
}

const knight = { name: 'knight', score: 1284.375 };
const rogue = { name: 'rogue', score: 97.5 };

// -- TODO 1 -- invoke formatNameTag ON knight with decimals=1 (no
// suffix), via .call
const viaCall = '???';

// -- TODO 2 -- invoke it ON rogue via .apply — the args arrive
// pre-packed as an array (say, out of a hud settings object):
// decimals=2, suffix=' pts'.
const viaApply = '???';

// -- TODO 3 -- manufacture a reusable hud name-tag formatter:
// this=knight AND decimals=3 pre-filled (partial application). Only
// `suffix` stays open.
const knightNameTag = '???';

// -- checks -----------------------------------------------------------
assert.strictEqual(viaCall, 'knight 1284.4',
  'TODO 1: fn.call(thisArg, arg1, arg2, ...) — this first, then args spread out');
assert.strictEqual(viaApply, 'rogue 97.50 pts',
  'TODO 2: fn.apply(thisArg, [args]) — the array IS the argument list');
assert.strictEqual(typeof knightNameTag, 'function',
  'TODO 3: bind does not call — it manufactures a new function for later');
assert.strictEqual(knightNameTag(' (hero)'), 'knight 1284.375 (hero)',
  'TODO 3: decimals pre-filled at bind time, suffix supplied at call time');
assert.strictEqual(knightNameTag(''), 'knight 1284.375',
  'TODO 3: reusable — call it as often as you like');

// -- TODO 4 -- a bound this is PERMANENT. Predict the exact name tag
// the hud shows:
const rebindAttempt = '???';

assert.strictEqual(
  rebindAttempt,
  knightNameTag.call(rogue, '!'),
  'TODO 4: .call on a BOUND function cannot re-aim this — the bound target wins and rogue is ignored; ' +
    "'!' still lands in the open suffix slot"
);

console.log('PASS — 03-call-apply-bind: explicit this, partial application, and the permanence of bind');

Solution