js-dojo

4 implement bind from scratch

GOAL

Build Function.prototype.bind yourself. Once you have written bind there is nothing left to fear about this-binding: it is only a closure holding (fn, thisArg, presetArgs) and one fn.apply.

CONCEPT

bind = manufacture a NEW function that, no matter when or how it is invoked, runs the original with a FIXED this and some leading args filled in ahead of time. The wrapper deliberately IGNORES its own call-time this — which is exactly why a native bound function can never be pointed somewhere else.

HINT

return function (...callArgs) { return fn.apply(?, [?, ...?]); }

MIRRORS

Profiling a method you don't own: the same wrapper-closing-over-the- original pattern you reach for when you wrap game.render with a timer yet keep its this intact — and what every engine's wrap() / monkey-patch helper is doing underneath.

Run

node 04-bind-from-scratch.cjs

Source

'use strict';
// ---------------------------------------------------------------------
// PROTO-DOJO · Module 2 / Exercise 4 — implement bind from scratch
//
// GOAL:    Build Function.prototype.bind yourself. Once you have
//          written bind there is nothing left to fear about
//          this-binding: it is only a closure holding (fn, thisArg,
//          presetArgs) and one fn.apply.
// CONCEPT: bind = manufacture a NEW function that, no matter when or
//          how it is invoked, runs the original with a FIXED this and
//          some leading args filled in ahead of time. The wrapper
//          deliberately IGNORES its own call-time this — which is
//          exactly why a native bound function can never be pointed
//          somewhere else.
// HINT: return function (...callArgs) { return fn.apply(?, [?, ...?]);
//       }
// MIRRORS: Profiling a method you don't own: the same
//          wrapper-closing-over-the- original pattern you reach for
//          when you wrap game.render with a timer yet keep its this
//          intact — and what every engine's wrap() / monkey-patch
//          helper is doing underneath.
//
// Run: node 04-bind-from-scratch.cjs
// ---------------------------------------------------------------------
const assert = require('node:assert');

// -- TODO 1 -----------------------------------------------------------
// Return a NEW function that, when called (in any form):
// 1. invokes `fn` with `this` = thisArg — no matter how the wrapper
// itself
//      was called,
//   2. passes presetArgs first, then whatever the caller adds (partial
//      application),
//   3. returns fn's return value.
// Do NOT use Function.prototype.bind anywhere — fn.apply / fn.call are
// the tools.
function myBind(fn, thisArg, ...presetArgs) {
  // ...your code here (it is 3 lines)...
  return fn;
}

// -- checks -----------------------------------------------------------
function describeAction(move, damage) {
  const actor = (this && this.name) || 'NO-PLAYER';
  return actor + ' ' + move + ' ' + damage;
}

const knight = { name: 'knight' };

const boundDescribe = myBind(describeAction, knight);
assert.strictEqual(boundDescribe('SLASH', '12'), 'knight SLASH 12',
  'TODO: even on a PLAIN call, the wrapper must aim this at knight — close over thisArg and fn.apply it');
assert.notStrictEqual(boundDescribe, describeAction,
  'TODO: myBind must manufacture a NEW function, not hand back fn');

const knightSlash = myBind(describeAction, knight, 'SLASH');
assert.strictEqual(knightSlash('40'), 'knight SLASH 40',
  'TODO: preset args go FIRST, call-time args append after them');
assert.strictEqual(knightSlash('7'), 'knight SLASH 7',
  'TODO: the bound function must be reusable — do not consume the presets');

// binding must not damage the original:
assert.strictEqual(describeAction.call({ name: 'rogue' }, 'STAB', '9'), 'rogue STAB 9',
  'the original function must stay freely aimable');
assert.strictEqual(describeAction('PARRY', '3'), 'NO-PLAYER PARRY 3',
  'the original must remain unbound');

// and like the native one, a bound function shrugs off later .call attempts:
assert.strictEqual(boundDescribe.call({ name: 'mage' }, 'CAST', '25'), 'knight CAST 25',
  'TODO: the wrapper must IGNORE its own call-time this — the closed-over thisArg always wins');

console.log('PASS — 04-bind-from-scratch: bind demystified — a closure and an apply');

Solution