dx_over_dt
dx_over_dt

Reputation: 14318

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

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

Upvotes: 3

Views: 255

Answers (2)

FZs
FZs

Reputation: 18609

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)
}

Upvotes: 1

Isidrok
Isidrok

Reputation: 2181

You can check if the original function prototype is the prototype of objects created from the bound function but you will have to execute it:

function foo(){}
var bar = foo.bind({},1,2,3);
function baz() {}
Object.setPrototypeOf(foo.prototype, baz.prototype);
foo.prototype === Object.getPrototypeOf(new bar()); // true
baz.prototype === Object.getPrototypeOf(new bar()); // false

Edit: check it is the same prototype instead of part of the prototype chain.

Upvotes: 0

Related Questions