Skip to content

Instantly share code, notes, and snippets.

@getify
Last active August 23, 2022 15:24
Show Gist options
  • Save getify/9043478 to your computer and use it in GitHub Desktop.
Save getify/9043478 to your computer and use it in GitHub Desktop.
function soft-binding
// Modified based on: https://gist.github.com/getify/9043478/#comment-1210362 from @ZIJ
if (!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this;
// capture any curried parameters
var curried = [].slice.call( arguments, 1 );
var bound = function() {
return fn.apply(
(!this || this === (window || global)) ? obj : this,
curried.concat.apply( curried, arguments )
);
};
bound.prototype = Object.create( fn.prototype );
return bound;
};
}
function foo() {
console.log("name: " + this.name);
}
var obj = { name: "obj" },
obj2 = { name: "obj2" },
obj3 = { name: "obj3" };
var fooOBJ = foo.softBind(obj);
fooOBJ(); // name: obj
obj2.foo = foo.softBind(obj);
obj2.foo(); // name: obj2 <---- look!!!
fooOBJ.call(obj3); // name: obj3 <---- look!
setTimeout(obj2.foo,10); // name: obj <---- falls back to soft-binding
@ZIJ
Copy link

ZIJ commented Apr 12, 2014

Cool! Here's a slightly improved version:

if (!Function.prototype.softBind) {
    Function.prototype.softBind = function (context) {
        if (typeof this !== "function") {
            throw new TypeError("Not callable");
        }
        var func = this;
        var root = window || global;
        var args = Array.prototype.slice.call(arguments, 1);
        var nop = function() {};
        var bound = function() {
            var actualContext = (!this || this === root) ? context : this;
            var allArgs = args.concat(Array.prototype.slice.call(arguments));
            return func.apply(actualContext, allArgs);
        }        
        nop.prototype = func.prototype;
        bound.prototype = new nop();
        return bound;
  };
}

@getify
Copy link
Author

getify commented Apr 12, 2014

@ZIJ Nice, thanks!

@basherr
Copy link

basherr commented Nov 1, 2018

@ZIJ Why would you even need to extract the params while you are concatenating it back in the code?

var args = Array.prototype.slice.call(arguments, 1);

And then

var allArgs = args.concat(Array.prototype.slice.call(arguments));

@FlorianSchaetz
Copy link

@basherr : Because these are different "arguments". The first "arguments" (in the line with "var args = ...") are the arguments you give when calling the softBind method:

var fooOBJ = foo.softBind(obj);

The first "arguments" would here be [ obj ]. If you called...

var fooOBJ = foo.softBind(obj,1,2,3);

...the first "arguments" would be [ obj, 1, 2, 3]. If you do a slice( ..., 1) you get [1,2,3].

The second "arguments" (in the line with "var allArgs = ...") are the arguments given to the soft-bound function when it's actually called. This is what this concat is for: You can give some arguments to the softBind call to "fix" those - all calls to the soft-bound function will use these fixed arguments first - all arguments given to the soft-bound function would come after that. This is called "currying".

Example: If you did...

var fooOBJ = foo.softBind(obj, 1, 2, 3);  // first "arguments" = obj, 1, 2, 3
fooObj(4,5); // second "arguments" = 4,5

then this would call foo on obj with the parameters 1,2,3,4,5, because the first "arguments" (except the first element) are added in front of the second "arguments" for the actual call.

@basherr
Copy link

basherr commented Aug 21, 2020

@FlorianSchaetz Thank you very much for the detailed explanation and especially for the term Currying. I've been working with Javascript and quite learned a lot but yet this was totally new topic to me. Thanks once again

@getify
Copy link
Author

getify commented Aug 21, 2020

This is called "currying".

Actually, it's more appropriately called "partial application" (which is commonly confused/conflated with "currying"). Both partial application and currying are a way to preset argument(s) to a function, but they go about it differently (different usage mechanics). This usage more closely fits partial application.

@FlorianSchaetz
Copy link

Thanks for clearing that up. I got that from the "You don't know JS" book (the old one, here), where those arguments (the first ones) are called "curried".

@getify
Copy link
Author

getify commented Aug 21, 2020

Oops. I wrote that book 6 years ago, and I've since learned better what to call them. FTR, that mention of "currying" refers to an earlier section, where I say:

technically called "partial application", which is a subset of "currying"

THAT is also a bit of a mis-statement... one is not the subset of the other... they're more like cousins: related, but different.

@FlorianSchaetz
Copy link

JS is full of little, hard to understand, very confusing details. That's what I like about it. Ups, no, that's what I totally hate about it. ;-)

Anyway, looking forward for edition 2 of your book, unfortunately the corresponding part does not seem to be there yet, so I have (for now) to rely on edition 1, which is still more in-depth than anything else I found so far.

@basherr
Copy link

basherr commented Aug 21, 2020

@getify I would like to request one thing for the 2nd Edition book to keep the words as much simple as you can. This will help non-native speakers to understand at a much faster pace. However, I am really grateful to you for this work. Stay blessed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment