How to bind function arguments without binding this?

Javascript

Javascript Problem Overview


In Javascript, how can I bind arguments to a function without binding the this parameter?

For example:

//Example function.
var c = function(a, b, c, callback) {};

//Bind values 1, 2, and 3 to a, b, and c, leave callback unbound.
var b = c.bind(null, 1, 2, 3); //How can I do this without binding scope?

How can I avoid the side-effect of having to bind the function's scope (e.g. setting this = null) as well?

Edit:

Sorry for the confusion. I want to bind arguments, then be able to call the bound function later and have it behave exactly as if I called the original function and passed it the bound arguments:

var x = 'outside object';

var obj = {
  x: 'inside object',
  c: function(a, b, c, callback) {
    console.log(this.x);
  }
};

var b = obj.c.bind(null, 1, 2, 3);
  
//These should both have exact same output.
obj.c(1, 2, 3, function(){});
b(function(){});

//The following works, but I was hoping there was a better way:
var b = obj.c.bind(obj, 1, 2, 3); //Anyway to make it work without typing obj twice?

I'm still new at this, sorry for the confusion.

Thanks!

Javascript Solutions


Solution 1 - Javascript

You can do this, but best to avoid thinking of it as "binding" since that is the term used for setting the "this" value. Perhaps think of it as "wrapping" the arguments into a function?

What you do is create a function that has the desired arguments built into it via closures:

var withWrappedArguments = function(arg1, arg2)
    {
        return function() { ... do your stuff with arg1 and arg2 ... };
    }(actualArg1Value, actualArg2Value);

Hope I got the syntax right there. What it does is create a function called withWrappedArguments() (to be pedantic it is an anonymous function assigned to the variable) that you can call any time any where and will always act with actualArg1Value and actualArg2Value, and anything else you want to put in there. You can also have it accept further arguments at the time of the call if you want. The secret is the parentheses after the final closing brace. These cause the outer function to be immediately executed, with the passed values, and to generate the inner function that can be called later. The passed values are then frozen at the time the function is generated.

This is effectively what bind does, but this way it is explicit that the wrapped arguments are simply closures on local variables, and there is no need to change the behaviour of this.

Solution 2 - Javascript

In ES6, this is easily done using rest parameters in conjunction with the spread operator.

So we can define a function bindArgs that works like bind, except that only arguments are bound, but not the context (this).

Function.prototype.bindArgs =
    function (...boundArgs)
    {
        const targetFunction = this;
        return function (...args) { return targetFunction.call(this, ...boundArgs, ...args); };
    };

Then, for a specified function foo and an object obj, the statement

return foo.call(obj, 1, 2, 3, 4);

is equivalent to

let bar = foo.bindArgs(1, 2);
return bar.call(obj, 3, 4);

where only the first and second arguments are bound to bar, while the context obj specified in the invocation is used and extra arguments are appended after the bound arguments. The return value is simply forwarded.

Solution 3 - Javascript

In the native bind method the this value in the result function is lost. However, you can easily recode the common shim not to use an argument for the context:

Function.prototype.arg = function() {
    if (typeof this !== "function")
        throw new TypeError("Function.prototype.arg needs to be called on a function");
    var slice = Array.prototype.slice,
        args = slice.call(arguments), 
        fn = this, 
        partial = function() {
            return fn.apply(this, args.concat(slice.call(arguments)));
//                          ^^^^
        };
    partial.prototype = Object.create(this.prototype);
    return partial;
};

Solution 4 - Javascript

var b = function() {
    return c(1,2,3);
};

Solution 5 - Javascript

One more tiny implementation just for fun:

function bindWithoutThis(cb) {
    var bindArgs = Array.prototype.slice.call(arguments, 1);

    return function () {
        var internalArgs = Array.prototype.slice.call(arguments, 0);
        var args = Array.prototype.concat(bindArgs, internalArgs);
        return cb.apply(this, args);
    };
}

How to use:

function onWriteEnd(evt) {}
var myPersonalWriteEnd = bindWithoutThis(onWriteEnd, "some", "data");

Solution 6 - Javascript

It's a bit hard to tell exactly what you ultimately want to do because the example is sort of arbitrary, but you may want to look into partials (or currying): http://jsbin.com/ifoqoj/1/edit

Function.prototype.partial = function(){
  var fn = this, args = Array.prototype.slice.call(arguments);
  return function(){
    var arg = 0;
    for ( var i = 0; i < args.length && arg < arguments.length; i++ )
      if ( args[i] === undefined )
        args[i] = arguments[arg++];
    return fn.apply(this, args);
  };
};

var c = function(a, b, c, callback) {
  console.log( a, b, c, callback )
};

var b = c.partial(1, 2, 3, undefined);
  
b(function(){})

Link to John Resig's article: http://ejohn.org/blog/partial-functions-in-javascript/

Solution 7 - Javascript

Using LoDash you can use the _.partial function.

const f  = function (a, b, c, callback) {}

const pf = _.partial(f, 1, 2, 3)  // f has first 3 arguments bound.

pf(function () {})                // callback.

Solution 8 - Javascript

May be you want to bind reference of this in last but your code:-

var c = function(a, b, c, callback) {};
var b = c.bind(null, 1, 2, 3); 

Already applied binding for instance this and later you can not change it. What I will suggest that use reference also as a parameter like this:-

var c = function(a, b, c, callback, ref) {  
    var self = this ? this : ref; 
    // Now you can use self just like this in your code 
};
var b = c.bind(null, 1, 2, 3),
    newRef = this, // or ref whatever you want to apply inside function c()
    d = c.bind(callback, newRef);

Solution 9 - Javascript

Use a protagonist!

var geoOpts = {...};

function geoSuccess(user){  // protagonizes for 'user'
  return function Success(pos){
    if(!pos || !pos.coords || !pos.coords.latitude || !pos.coords.longitude){ throw new Error('Geolocation Error: insufficient data.'); }

    var data = {pos.coords: pos.coords, ...};

    // now we have a callback we can turn into an object. implementation can use 'this' inside callback
    if(user){
      user.prototype = data;
      user.prototype.watch = watchUser;
      thus.User = (new user(data));
      console.log('thus.User', thus, thus.User);
    }
  }
}

function geoError(errorCallback){  // protagonizes for 'errorCallback'
  return function(err){
    console.log('@DECLINED', err);
    errorCallback && errorCallback(err);
  }
}

function getUserPos(user, error, opts){
  nav.geo.getPos(geoSuccess(user), geoError(error), opts || geoOpts);
}

Basically, the function you want to pass params to becomes a proxy which you can call to pass a variable, and it returns the function you actually want to do stuff.

Hope this helps!

Solution 10 - Javascript

An anonymous user posted this additional info:

> Building on what has already been provided in this post -- the most elegant solution I've seen is to Curry your arguments and context:

function Class(a, b, c, d){
    console.log('@Class #this', this, a, b, c, d);
}

function Context(name){
    console.log('@Context', this, name);
    this.name = name;
}

var context1 = new Context('One');
var context2 = new Context('Two');

function curryArguments(fn) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function bindContext() {
      var additional = Array.prototype.slice.call(arguments, 0);
      return fn.apply(this, args.concat(additional));
    };
}

var bindContext = curryArguments(Class, 'A', 'B');

bindContext.apply(context1, ['C', 'D']);
bindContext.apply(context2, ['Y', 'Z']);

Solution 11 - Javascript

Well for the exemple you gave, this will do

var b= function(callback){
        return obj.c(1,2,3, callback);
};

If you want to guarenty enclosure of the parameters :

var b= (function(p1,p2,p3, obj){
        var c=obj.c;
        return function(callback){
                return c.call(obj,p1,p2,p3, callback);
        }
})(1,2,3,obj)

But if so you should just stick to your solution:

var b = obj.c.bind(obj, 1, 2, 3);

It's the better way.

Solution 12 - Javascript

Simple like that?

var b = (cb) => obj.c(1,2,3, cb)
b(function(){}) // insidde object

More general solution:

function original(a, b, c) { console.log(a, b, c) }
let tied = (...args) => original(1, 2, ...args)

original(1,2,3) // 1 2 3
tied(5,6,7) // 1 2 5

Solution 13 - Javascript

I'm using this function:

function bindArgs(func, ...boundArgs) {
    return function (...args) {
        return func(...boundArgs, ...args);
    };
}
// use
const deleteGroup = bindArgs(this.props.deleteGroup, "gorupName1");

Solution 14 - Javascript

Why not use a wrapper around the function to save this as mythis ?

function mythis() {
  this.name = "mythis";
  mythis = this;
  function c(a, b) {
    this.name = "original";
    alert('a=' + a + ' b =' + b + 'this = ' + this.name + ' mythis = ' + mythis.name);
    return "ok";
  }    
  return {
    c: c
  }
};

var retval = mythis().c(0, 1);

Solution 15 - Javascript

jQuery 1.9 brought exactly that feature with the proxy function.

> As of jQuery 1.9, when the context is null or undefined the proxied function will be called with the same this object as the proxy was called with. This allows $.proxy() to be used to partially apply the arguments of a function without changing the context.

Example:

$.proxy(this.myFunction, 
        undefined /* leaving the context empty */, 
        [precededArg1, precededArg2]);

Solution 16 - Javascript

Jquery use case:

instead:

for(var i = 0;i<3;i++){
	$('<input>').appendTo('body').click(function(i){
		$(this).val(i); // wont work, because 'this' becomes 'i'
	}.bind(i));
}

use this:

for(var i = 0;i<3;i++){
	$('<input>').appendTo('body').click(function(e){
		var i = this;
		$(e.originalEvent.target).val(i);
	}.bind(i));
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionImbueView Question on Stackoverflow
Solution 1 - JavascriptIan NartowiczView Answer on Stackoverflow
Solution 2 - JavascriptGOTO 0View Answer on Stackoverflow
Solution 3 - JavascriptBergiView Answer on Stackoverflow
Solution 4 - JavascriptmikeView Answer on Stackoverflow
Solution 5 - JavascriptDmitrii SorinView Answer on Stackoverflow
Solution 6 - JavascriptKevin EnnisView Answer on Stackoverflow
Solution 7 - JavascriptDaniele OrlandoView Answer on Stackoverflow
Solution 8 - JavascriptDeepak DixitView Answer on Stackoverflow
Solution 9 - JavascriptCodyView Answer on Stackoverflow
Solution 10 - JavascriptBarettView Answer on Stackoverflow
Solution 11 - JavascriptJoseph GarroneView Answer on Stackoverflow
Solution 12 - JavascriptQwertyView Answer on Stackoverflow
Solution 13 - JavascriptIsraelView Answer on Stackoverflow
Solution 14 - JavascriptOlivier ArcherView Answer on Stackoverflow
Solution 15 - JavascriptgosuaView Answer on Stackoverflow
Solution 16 - Javascriptuser669677View Answer on Stackoverflow