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.cjsSource
'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');