Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determine if bound JavaScript function is a bound version of a function

Tags:

javascript

Suppose I have a function, foo, and a bound version of it, bar. Is there a built-in method I can test if bar is a bound version of foo more definitively than checking the name?

function isBoundTo(bound, fn) {
  return /bound (.+)$/.exec(bound.name)[1] === fn.name;
}

function foo() {
  return this && this.x !== undefined ? this.x : 'foo';
}

function notfoo() { 
  return 'notfoo'; 
}

const bar = foo.bind({ x: 'bar' });
const boundbar = bar.bind({ x: 'boundbar' });

const baz = {
  foo() { 
    return 'baz.foo';
  }
};

const results = [
  ['foo()'],
  ['notfoo()'],
  ['bar()'],
  ['boundbar()'],
  ['baz.foo()'],
  ['isBoundTo(bar, foo)'],
  ['isBoundTo(bar, notfoo)'],
  ['isBoundTo(boundbar, bar)'],
  ['isBoundTo(boundbar, foo)'],
  ['isBoundTo(bar, baz.foo)', 'false positive'],
].reduce((obj, [exp, comment]) => {
  const val = eval(exp);
  return {
    ...obj,
    [exp]: {
      result: typeof val === 'string' ? JSON.stringify(val) : val,
      comment: comment || ''
    }
  };
}, {});

console.table(results);
document.querySelector('.as-console-table thead th').innerText = 'expression';
/*<ignore>*/console.config({maximize:true,timeStamps:false,autoScroll:false});/*</ignore>*/
<script src="https://gh-canon.github.io/stack-snippet-console/console.min.js"></script>

I know I could overwrite Function.prototype.bind with a proxy. I'd just rather not (though the more I've worked on this question, the less I'm opposed to using a proxy).

Function.prototype.bind = new Proxy(Function.prototype.bind, {
  apply: function (target, thisArg, args) {
    let fn = Reflect.apply(target, thisArg, args);

    Object.defineProperty(fn, 'targetFunction', {
      value: thisArg.targetFunction || thisArg,
      configurable: false,
      writable: false
    });

    return fn;
  }
});

function foo() {
  return this && this.x !== undefined ? this.x : 'foo';
}

function notfoo() {
  return 'notfoo';
}

const bar = foo.bind({ x: 'bar' });
const boundbar = bar.bind({ x: 'boundbar' });

const baz = {
  foo() {
    return 'baz.foo';
  }
};

const results = [
  'foo()',
  'notfoo()',
  'bar()',
  'boundbar()',
  'baz.foo()',
  'foo.targetFunction',
  'bar.targetFunction === foo',
  'bar.targetFunction === notfoo',
  'boundbar.targetFunction === bar',
  'boundbar.targetFunction === foo',
  'bar.targetFunction === baz.foo'
].reduce((obj, exp) => {
  const val = eval(exp);
  return {
    ...obj,
    [exp]: {
      result: typeof val === 'string' ? JSON.stringify(val) : val
    }
  };
}, {});

console.table(results, ['expression', 'result']);
document.querySelector('.as-console-table thead th').innerText = 'expression';
/*<ignore>*/console.config({maximize:true,timeStamps:false,autoScroll:false});/*</ignore>*/
<script src="https://gh-canon.github.io/stack-snippet-console/console.min.js"></script>

When I debugged this and inspected fn, there was a property listed as [[TargetFunction]], but I have no idea how to reference it. It doesn't appear to be a symbol (which are denoted [Symbol('name')]). I thought maybe it was specific to Node.JS, but it also appears in Chrome. It doesn't appear in Firefox, but since I'm only using this for testing in Node, I don't really care. I imagine it must be accessible somehow if the debugger knows it exists and can retrieve the value of it.

Edit

'Cause some of ya'll want me to justify the need for this, I wrote up a simplified version of what I'm working on. (I didn't choose the pattern for opening a dialog, but I do need to test it.) Note: The code doesn't actually run because repl.it wouldn't let me install the dependencies needed for Enzyme. https://repl.it/repls/BitesizedWeirdElectricity

like image 727
dx_over_dt Avatar asked Feb 20 '26 20:02

dx_over_dt


1 Answers

There's no way to do that. [[TargetFunction]] is an internal property, that isn't exposed.

However, you might implement a custom bind function, that keeps track of that:

const boundFns = new WeakMap()
function bind(fn, thisArg, ...args){
  const newFn = function (...moreArgs) {
    const allArgs = args.concat(moreArgs)
    return new.target 
      ? Reflect.construct(fn, allArgs, new.target)
      : Reflect.apply(fn, thisArg, allArgs)
  }
  Object.defineProperty(newFn, 'name', {value: 'bound ' + fn.name})
  Object.defineProperty(newFn, 'length', {value: fn.length})
  Object.defineProperty(newFn, 'toString', {
    value: function toString(){
      return fn.toString()
    },
    configurable: true
  })
  boundFns.set(newFn, fn)
  return newFn
}

function getTargetFunction(fn){
  return boundFns.get(fn)
}

Or even simpler (as @Ry- pointed out), that uses native bind:

const boundFns = new WeakMap()
function bind(fn, ...args){
  const newFn = fn.bind(...args)
  boundFns.set(newFn, fn)
  return newFn
}

function getTargetFunction(fn){
  return boundFns.get(fn)
}
like image 95
FZs Avatar answered Feb 22 '26 10:02

FZs



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!