37

In some Javascript code (node.js specifically), I need to call a function with an unknown set of arguments without changing the context. For example:

function fn() {
    var args = Array.prototype.slice.call(arguments);
    otherFn.apply(this, args);
}

The problem in the above is that when I call apply, I'm change the context by passing this as the first argument. I'd like to pass args to the function being called without changing the context of the function being called. I essentially want to do this:

function fn() {
    var args = Array.prototype.slice.call(arguments);
    otherFn.apply(<otherFn's original context>, args);
}

Edit: Adding more detail regarding my specific question. I am creating a Client class that contains a socket (socket.io) object among other info pertaining to a connection. I am exposing the socket's event listeners via the client object itself.

class Client
  constructor: (socket) ->
    @socket    = socket
    @avatar    = socket.handshake.avatar
    @listeners = {}

  addListener: (name, handler) ->
    @listeners[name] ||= {}
    @listeners[name][handler.clientListenerId] = wrapper = =>
      # append client object as the first argument before passing to handler
      args = Array.prototype.slice.call(arguments)
      args.unshift(this)
      handler.apply(this, args)  # <---- HANDLER'S CONTEXT IS CHANGING HERE :(

    @socket.addListener(name, wrapper)

  removeListener: (name, handler) ->
    try
      obj = @listeners[name]
      @socket.removeListener(obj[handler.clientListenerId])
      delete obj[handler.clientListenerId]

Note that clientListenerId is a custom unique identifier property that is essentially the same as the answer found here.

Community
  • 1
  • 1
Matt Huggins
  • 81,398
  • 36
  • 149
  • 218
  • Are you asking how to get a reference to the global context? – SLaks Sep 09 '12 at 02:24
  • Have you tried leaving the first argument empty? as long as it's not a required argument, that should work. – Sylvester Sep 09 '12 at 02:26
  • @SLaks - no, because `otherFn` will belong to another object, but that object will vary depending on when `fn` is being called. – Matt Huggins Sep 09 '12 at 02:31
  • @Sylvester - I have not tried that, as both https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/apply and https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/call show that the `thisArg` is required. – Matt Huggins Sep 09 '12 at 02:32
  • 1
    Sounds to me like you need to use `Function.prototype.bind` before applying arguments to the bound function. – zzzzBov Sep 09 '12 at 02:32
  • @Sylvester AFAIK when you're using the second argument (the argument list in an array) you need to input some value for the first argument. Setting it to **null** will bypass "this" object. I'm not sure but there's also a chance that doing this might make it global. Who knows :) – inhan Sep 09 '12 at 02:33
  • @MattHuggins Did you try setting the first argument to `null`? – inhan Sep 09 '12 at 02:40
  • @inhan - Just tried passing `null`, and it seems to be treating it the same as if I passed `this`. In other words, it's changing `otherFn`'s context to `fn`'s context. – Matt Huggins Sep 09 '12 at 02:47
  • 2
    @Matt: A function does never belong to an object. If you only have a function reference, then calling the function will set `this` to the global object, *unless* it was bound to another value with `.bind`. If it was, then passing any value as `this` won't change it. If the function is "a method of an object" you need to have a reference to the object as well. – Felix Kling Sep 09 '12 at 02:51
  • 2
    @Matt I think you will not be able to do this from your Client class. By the time `addListener` is called, the reference to the context for the handler has already been lost. The code that is _calling_ `addListener` can do a `bind` of the function to the appropriate object before calling. That should work fine. But once the context is lost, it's completely gone. Sorry. – Scott Sauyet Sep 09 '12 at 03:03
  • That's unfortunate. Thanks for the info, Scott. – Matt Huggins Sep 09 '12 at 03:12
  • The question here fundamentally misunderstands how context works in JavaScript. The value of `this` within a function, unlike, say, the value of closure variables, is defined at *call* time, not at the time the function is created. The function doesn't *have* a context, it is *called* with a context, which is why if you write `function foo () {console.log(this)}; obj1 = {someProp: foo}; foo(); obj1.someProp();` you see two completely different objects (the `window`, and `obj1`) getting logged. Asking how to call a function without 'changing' its context is thus meaningless. – Mark Amery Feb 02 '14 at 20:10

10 Answers10

9

If I understand you correctly:

                          changes context
                   |    n     |      y       |
accepts array    n |  func()  | func.call()  |
of arguments     y | ???????? | func.apply() |

PHP has a function for this, call_user_func_array. Unfortunately, JavaScript is lacking in this regard. It looks like you simulate this behavior using eval().

Function.prototype.invoke = function(args) {
    var i, code = 'this(';
    for (i=0; i<args.length; i++) {
        if (i) { code += ',' }
        code += 'args[' + i + ']';
    }
    eval(code + ');');
}

Yes, I know. Nobody likes eval(). It's slow and dangerous. However, in this situation you probably don't have to worry about cross-site scripting, at least, as all variables are contained within the function. Really, it's too bad that JavaScript doesn't have a native function for this, but I suppose that it's for situations like this that we have eval.

Proof that it works:

function showArgs() {
    for (x in arguments) {console.log(arguments[x]);}
}

showArgs.invoke(['foo',/bar/g]);
showArgs.invoke([window,[1,2,3]]);

Firefox console output:

--
[12:31:05.778] "foo"
[12:31:05.778] [object RegExp]
[12:31:05.778] [object Window]
[12:31:05.778] [object Array]
Brian McCutchon
  • 8,354
  • 3
  • 33
  • 45
  • 3
    This is the only response that actually answers the question. Everyone else thinks the OP either wants to get _access_ to the context (which they don't), or that it doesn't have a context, or that they just don't know about `Function.apply` (which they clearly do). Thank you for being the only one to understand the question and say no, there is nothing like PHP's `call_user_func_array`. – JMTyler Oct 07 '13 at 03:17
7

Simply put, just assign the this to what you want it to be, which is otherFn:

function fn() {
    var args = Array.prototype.slice.call(arguments);
    otherFn.apply(otherFn, args);
}
Dave
  • 2,126
  • 1
  • 15
  • 18
  • I think that's exactly what the author of the question needed. – SergeyA Feb 02 '19 at 19:36
  • 1
    You may not know what the context of otherFn is if [bind](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind) has been used. – Code Commander Sep 22 '20 at 20:55
6

'this' is a reference to your function's context. That's really the point.

If you mean to call it in the context of a different object like this:

otherObj.otherFn(args)

then simply substitute that object in for the context:

otherObj.otherFn.apply(otherObj, args);

That should be it.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • 5
    The problem is that at the point I have access to `otherFn`, I don't know what object it belongs to. Is there a way to determine its current binding if I only have a reference to the function? – Matt Huggins Sep 09 '12 at 02:33
  • Perhaps I am looking for [Function.constructor](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/constructor)... – Matt Huggins Sep 09 '12 at 02:35
  • 2
    No. If you have a raw function, you cannot know from whence it came. It could have been bound to anything, or to nothing at all. – Scott Sauyet Sep 09 '12 at 02:35
  • I don't think the Function constructor will do you any good. It might help create a new function, but still won't help you recover a missing context. – Scott Sauyet Sep 09 '12 at 02:37
  • 1
    If you've lost the context, it's really gone. Could you edit the post to provide some more background about where the function is coming from? – Scott Sauyet Sep 09 '12 at 02:37
5

If you bind the function to an object and you use everywhere the bound function, you can call apply with null, but still get the correct context

var Person = function(name){
    this.name = name;
}
Person.prototype.printName = function(){
    console.log("Name: " + this.name);
}

var bob = new Person("Bob");

bob.printName.apply(null); //window.name
bob.printName.bind(bob).apply(null); //"Bob"
Blackfire
  • 73
  • 2
  • 7
1

One way that you can work around the change of context that can happen in JavaScript when functions are called, is to use methods that are part of the object's constructor if you need them to be able to operate in a context where this is not going to mean the parent object, by effectively creating a local private variable to store the original this identifier.

I concede that - like most discussions of scope in JavaScript - this is not entirely clear, so here is an example of how I have done this:

function CounterType()
{
    var counter=1;
    var self=this; // 'self' will now be visible to all

    var incrementCount = function()
    {
        // it doesn't matter that 'this' has changed because 'self' now points to CounterType()
        self.counter++;
    };

}

function SecondaryType()
{
    var myCounter = new CounterType();
    console.log("First Counter : "+myCounter.counter); // 0
    myCounter.incrementCount.apply(this); 
    console.log("Second Counter: "+myCounter.counter); // 1
}
glenatron
  • 11,018
  • 13
  • 64
  • 112
1

These days you can use rest parameters:

function fn(...args) {
    otherFn(...args);
}

The only downside is, if you want to use some specific params in fn, you have to extract it from args:

function fn(...args) {
    let importantParam = args[2]; //third param
    // ...
    otherFn(...args);
}

Here's an example to try (ES next version to keep it short):

// a one-line "sum any number of arguments" function
const sum = (...args) => args.reduce((sum, value) => sum + value);

// a "proxy" function to test:
var pass = (...args) => sum(...args);
console.log(pass(1, 2, 15));
YakovL
  • 7,557
  • 12
  • 62
  • 102
0

I'm not going to accept this as an answer, as I'm still hoping for something more suitable. But here's the approach I'm using right now based upon the feedback on this question so far.

For any class that will be calling Client.prototype.addListener or Client.prototype.removeListener, I did added the following code to their constructor:

class ExampleClass
  constructor: ->
    # ...
    for name, fn of this
      this[name] = fn.bind(this) if typeof(fn) == 'function'

   message: (recipient, body) ->
     # ...

   broadcast: (body) ->
     # ...

In the above example, message and broadcast will always be bound to the new ExampleClass prototype object when it's instantiated, allowing the addListener code in my original question to work.

I'm sure some of you are wondering why I didn't just do something like the following:

example = new ExampleClass
client.addListener('message', example.bind(example))
# ...
client.removeListener('message', example.bind(example))

The problem is that every time .bind( ) is called, it's a new object. So that means that the following is true:

example.bind(example) != example.bind(example)

As such, the removeListener would never work successfully, thus my binding the method once when the object is instantiated.

Matt Huggins
  • 81,398
  • 36
  • 149
  • 218
  • Downvoter: Why the downvote? I just shared what I did to solve my own question, it'd be nice to know your reasoning. – Matt Huggins Aug 18 '13 at 04:16
  • I would, too. While your question is based on a misunderstanding of how `this` works, your answer is completely correct. Using `.bind` (or a wrapper for it like underscore's `_.bindAll`) is the right thing to do in your particular use case of wanting to pass around a function on its own but have all calls to it be treated as method calls of a particular 'parent' object. – Mark Amery Feb 02 '14 at 20:16
0

Since you seem to want to be using the bind function as it is defined in Javascript 1.8.5, and be able to retrieve the original this object you pass the bind function, I recommend redefining the Function.prototype.bind function:

Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") {
        throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var aArgs = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP = function () {},
        fBound = function () {
            return fToBind.apply(this instanceof fNOP && oThis
            ? this
            : oThis,
            aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    /** here's the additional code **/
    fBound.getContext = function() {
        return oThis;
    };
    /**/

    return fBound;
};

Now you can retrieve the original context that you called the bind function with:

function A() {
    return this.foo+' '+this.bar;
}

var HelloWorld = A.bind({
    foo: 'hello',
    bar: 'world',
});

HelloWorld(); // returns "hello world";
HelloWorld.getContext(); // returns {foo:"hello", bar:"world"};
Blake Regalia
  • 2,677
  • 2
  • 20
  • 29
  • Not being able to use .apply() without specifying context is indeed major design flaw of Javascript (knowing how easy and frequent *args / **kwargs are used in Python). Nice idea to pass function arguments to .bind(). Thank you. – Dmitriy Sintsov Jun 10 '16 at 09:24
  • 1
    Mucking with native functions seems like a really bad idea. – linuxdan Nov 03 '16 at 21:33
0

I was just reminded of this question after a long time. Looking back now, I think what I was really trying to accomplish here was something similar to how the React library works with its automatic binding.

Essentially, each function is a wrapped bound function being called:

function SomeClass() {
};

SomeClass.prototype.whoami = function () {
  return this;
};

SomeClass.createInstance = function () {
  var obj = new SomeClass();

  for (var fn in obj) {
    if (typeof obj[fn] == 'function') {
      var original = obj[fn];

      obj[fn] = function () {
        return original.apply(obj, arguments);
      };
    }
  }

  return obj;
};

var instance = SomeClass.createInstance();
instance.whoami() == instance;            // true
instance.whoami.apply(null) == instance;  // true
Matt Huggins
  • 81,398
  • 36
  • 149
  • 218
-1

Just push properties directly to the function's object and call it with it's own "context".

function otherFn() {
    console.log(this.foo+' '+this.bar); // prints: "hello world" when called from rootFn()
}

otherFn.foo = 'hello';
otherFn.bar = 'world';

function rootFn() {
    // by the way, unless you are removing or adding elements to 'arguments',
    // just pass the arguments object directly instead of casting it to Array
    otherFn.apply(otherFn, arguments);
}
Blake Regalia
  • 2,677
  • 2
  • 20
  • 29
  • It still seems to be a problem if the function is a prototype function for an object. The properties then are on the object, not the function itself. Passing `otherFn` as the context doesn't allow me to access the properties of the object. (I tried this with no success.) – Matt Huggins Sep 09 '12 at 12:32
  • Not quite. More like this: http://jsfiddle.net/K79pp/2/ You can see that references to `foo()` and `bar()` can't be found because the function is being used as the context, when in fact we want `otherClass` to be used as the context. – Matt Huggins Sep 09 '12 at 17:53
  • http://jsfiddle.net/K79pp/3/ <-- there you go, just have to apply the function object itself to the handler function – Blake Regalia Sep 09 '12 at 18:31
  • I would do that if I knew what the object is to be able to supply it as the context. However, if you look at my original question, I don't know what the original parent object is by the time I have access to the `handler` function. – Matt Huggins Sep 09 '12 at 19:05
  • I think you are misunderstanding the whole concept of javascript contexts. Every function is an independent object that **only** stores a permanent reference to its scope chain. A _context_ is simply an object: if you want to apply a function with a certain context you use either `context.fn(args)`, or `fn.apply(context, args)`. Since contexts are just objects, if you do not maintain an explicit reference to one **then the garbage collector will dispose of it** – Blake Regalia Sep 10 '12 at 20:56
  • The function's context is still being referenced elsewhere in the app, so there is no concern about it being garbage collected. I'm just trying to figure out how to call that function with an array of args without modifying the existing context on the function. – Matt Huggins Sep 10 '12 at 22:02
  • I think it comes down to Javascript not offering what I'm looking for. If I call `ftn.bind(context)`, there's no method I can call on `ftn` that will return `context`, which is ultimately what I'm looking for. – Matt Huggins Sep 10 '12 at 22:03
  • Why are you using `bind` then? – Blake Regalia Sep 10 '12 at 22:16
  • I'm not necessarily using bind. I'm simply trying to maintain the original context on whatever function I'm calling. For example, if I define `User.prototype.onSave = function() { ... }` and later do `user = new User`, then `user.onSave` will have the context of `user`. With that in mind, in my original question I only have access to `onSave` but not `user`. I want to call `onSave` with an array of arguments, but I don't want to change the context to something new in the process. – Matt Huggins Sep 10 '12 at 22:20