Function.prototype.bind
function bind(context:Object[, arg1, ..., argN]):Function {
return a callback able to execute this function
passing context as reference via this
}
In few words, if we have a generic object, we don't necessary need to attach a function to create a method:
// a generic function
function name(name) {
if (name == null) return this.name;
this.name = name;
return this;
}
// a generic object
var wr = {name:"WebReflection"};
// a callback with a "trapped" object
var fn = name.bind(wr);
alert([
// wr has not been affected
wr.name, // WebReflection
// fn can always be called
fn(), // WebReflection
fn("test") === wr, // true
fn(), // test
wr.name // test
].join("\n"));
Designed to ensure the same context whatever way we decide to use the callback, included call, apply, setTimeout, DOM events, etc, Function.prototype.bind is one of the most useful/powerful JavaScript concept.
Unfortunately, this will be standard only with newer browser ...
Optimized bind for every browser
Update: as I have said I code what I need, I rarely google for simple tasks like this. Most of this code, in any case, has been readapted and tested to reach best performances, based on this proposal.
if (Function.prototype.bind == null) {
Function.prototype.bind = (function (slice){
// (C) WebReflection - Mit Style License
function bind(context) {
var self = this; // "trapped" function reference
// only if there is more than an argument
// we are interested into more complex operations
// this will speed up common bind creation
// avoiding useless slices over arguments
if (1 < arguments.length) {
// extra arguments to send by default
var $arguments = slice.call(arguments, 1);
return function () {
return self.apply(
context,
// thanks @kangax for this suggestion
arguments.length ?
// concat arguments with those received
$arguments.concat(slice.call(arguments)) :
// send just arguments, no concat, no slice
$arguments
);
};
}
// optimized callback
return function () {
// speed up when function is called without arguments
return arguments.length ? self.apply(context, arguments) : self.call(context);
};
}
// the named function
return bind;
}(Array.prototype.slice));
}
Why Bother
The concatenation optimization is something rarely present in whatever framework/library I have seen. While it makes the code a bit bigger than usual, performances will be the best for most common cases, those where optional arguments are not passed at all.A simple test case, one of my favorites, able to solve the classic IE problem with extra arguments passed to setInterval/Timeout:
(function(){
var callback = function(time){
if (1000 < new Date - time)
return alert("1 second with a default argument");
setTimeout(callback, 15);
}.bind(null, new Date);
setTimeout(callback, 15);
}());
Why not use this instead of manipulating the arguments object?
ReplyDeleteif(!Function.prototype.bind){
Function.prototype.bind = function(context){
if(!context)
return this;
var fun = this;
return function(){
return fun.apply(context, arguments);
};
};
}
Michael, that is not standard, bind accepts a thisArg plus, optionally, one or more arguments.
ReplyDeleteThe check !context is poor, if I want to pass the current global object I do believe null should be accepted.
If I want to pass a persistent condition, true or false, I don't see why I should not be able to bind this value.
Regards
The check !context is poor, if I want to pass the current global object I do believe null should be accepted.
ReplyDeleteerrata, null will result into null, undefined context, so I guess the check should be context == null for ES5.
Updated
What do you think about the implementation of Garrett Smith?
ReplyDelete<>
Function.prototype.bind = function(context){
var fn = this,
ap, concat, args,
isPartial = arguments.length > 1;
// Strategy 1: just bind, not a partialApply
if(!isPartial) {
return function() {
if(arguments.length !== 0) {
return fn.apply(context, arguments);
} else {
return fn.call(context); // faster in Firefox.
}
};
} else {
// Strategy 2: partialApply
ap = Array.prototype,
args = ap.slice.call(arguments, 1);
concat = ap.concat;
return function() {
return fn.apply(context,
arguments.length === 0 ? args :
concat.apply(args, arguments));
};
}
};
@joseanpg there are several bind implementation out there. My aim is to provide the fastest one avoiding every redundant operation and using all it's possible to speed up both creation and execution.
ReplyDeleteIn your example there are several useless steps.
isPartial is created and passed via "not", why? That variable is not useful, the if could simply contain: if (1 < arguments.length) ... same case, no need for a variable, netirher a "not" operation.
Same is for checks agains integer, 0 is falsy since C language and it's accepted in C as well as boolean.
if (0) { falsy }
There is no need to strictly compare via === or !==
An if/else statement is usually slower than a ternary operation.
Specially in JS where we have no bytecode, it means lots of redundant code as well to surround those 2 cases.
return arguments.length ? apply : call
This is all we need.
In Strategy 2 every time there is one or more arguments, we need to lookup for the global Array, and access its prototype.
This is both slower and not safe. Global Constructor prototypes should be always cached in advance so that it's easier to access the method we are looking for, and it's safer, since if another library will replace Array, its prototype, or one of its method we trust for our purpose, we could be in troubles.
Finally, since concat already accepts an array to concat, there is no need at all to cache concat and use apply, specially because if an argument is an array, this will become flat.
var a = [[1, 2], 3];
var b = [[4, 5], 6];
alert(a.concat(b).join("-"));
//1,2-3-4,5-6
alert(a.concat.apply(a, b).join("-"));
// 1,2-3-4-5-6
As you can see, that is simply an error, able to make arguments inconsistent (if I passed an array as first argument, I meant it!)
Very good advice:
ReplyDelete"...specially because if an argument is an array, this will become flat.
var a = [[1, 2], 3];
var b = [[4, 5], 6];
alert(a.concat(b).join("-"));//1,2-3-4,5-6
alert(a.concat.apply(a, b).join("-"));// 1,2-3-4-5-6
As you can see, that is simply an error, able to make arguments inconsistent"
Is
bind_researched
making that mistake?
Perhaps could be fixed using call instead of apply:
alert(a.concat.call(a, b).join("-"));//1,2-3-4,5-6
call is fine, but it wont recognize arguments as an array, so it won't concatenate arguments as expected ;)
ReplyDeleteUndoubtedly the best option is
ReplyDeleteboundArgs.concat(_slice.call(arguments))
But the following would be valid too
_concat.call(boundArgs,_slice.call(arguments))
Do you agree?
By the way, it seems that bind_even_newer also suffers the problem of flattening concat.apply, what do you think?
sure, that would be valid.
ReplyDeleteWhat do I think?
I think I have both the fastest and more stable version here ;)
I agree :D
ReplyDeleteI've found another part of the problem with Function.prototype.bind: http://www.angrycoding.com/2011/09/to-bind-or-not-to-bind-that-is-in.html
ReplyDelete