I read this, and I understand it, but, is there a way to attach a function to always execute a function alias on a specific scope, without the user of the function alias have to use the sort of clunky .apply stuff?
You can use the new ECMAScript 5 .bind() method. An alternative implementation for browsers that don't support it (taken from the MDC documentation I linked to):
// Function.prototype.bind polyfill
if ( !Function.prototype.bind ) {
Function.prototype.bind = function( obj ) {
if(typeof this !== 'function') // closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
var slice = [].slice,
args = slice.call(arguments, 1),
self = this,
nop = function () {},
bound = function () {
return self.apply( this instanceof nop ? this : ( obj || {} ),
args.concat( slice.call(arguments) ) );
};
bound.prototype = this.prototype;
return bound;
};
}
Related
I'm currently reading You don't know JS. In it, there is a section that talks about soft binding technique. Basically it's a variation of binding a function to a particular scope/context.
From the book:
It would be nice if there was a way to provide a different default for default binding (not global or undefined), while still leaving the function able to be manually this bound via implicit binding or explicit binding techniques.
if (!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this,
curried = [].slice.call( arguments, 1 ),
bound = function bound() {
return fn.apply(
(!this ||
(typeof window !== "undefined" &&
this === window) ||
(typeof global !== "undefined" &&
this === global)
) ? obj : this,
curried.concat.apply( curried, arguments )
);
};
bound.prototype = Object.create( fn.prototype );
return bound;
};
}
Generally I understand what the function does except this part:
bound.prototype = Object.create( fn.prototype );
Why do we have to setup a prototype when using this "soft bind" technique?
Why do we have to setup a prototype when using this "soft bind" technique?
It's more for completeness sake than anything else. If someone tries to create a new instance of the soft-bound function, they'd expect the resulting object to [[Prototype]] link to the same object as the original function's .prototype pointed. So, we make sure to set bound.prototype equal to reference that same object.
The chances of someone wanting to call new on a soft-bound function are low, I'd think, but just to be more safe, it's included nonetheless. The same, btw, is true of the polyfill for the built in bind(..) as listed here:
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind#Polyfill
From that page:
Why is new being able to override hard binding useful?
The primary reason for this behavior is to create a function (that can
be used with new for constructing objects) that essentially ignores
the this hard binding but which presets some or all of the function's
arguments. One of the capabilities of bind(..) is that any arguments
passed after the first this binding argument are defaulted as standard
arguments to the underlying function (technically called "partial
application", which is a subset of "currying").
I guess for the softbind to have the same benefits (to provide a constructor that has default arguments passed to it) you have to set the prototype as well or you're missing prototype functions when you use the bound function as a constructor:
//softBind code removed
function Test(arg1,arg2){
this.arg1=arg1;
this.arg2=arg2;
}
Test.prototype.sayArgs=function(){
console.log('args:',this.arg1,this.arg2);
}
//the binding part doesn't do anything so can pass null
var BoundTest = Test.softBind(null, 'arg1');
var bt = new BoundTest('arg2');
bt.sayArgs();//=args:arg1 arg2
If you are going to use it this way though you don't have to add softBind to the Function.prototype, you can use normal bind, either native or the MDN polyfill.
Note that with the polyfill you cannot pass a falsy argument as the first parameter to bind or it'll break.
//Using the MDN bind polyfill you can't pass any falsy
// value like null, undefined,"",0 and false
var BoundTest = Test.bind({}, 'arg1');
var bt = new BoundTest('arg2');
bt.sayArgs();//=args:arg1 arg2;
In the article, the example given is actually broken:
var bar = foo.bind( null, "p1" );
var baz = new bar( "p2" );
Should be:
var bar = foo.bind('anything non falsy' , "p1" );
[update]
//removed if, so it'll set softbind every time
Function.prototype.softBind = function(obj) {
var fn = this,
curried = [].slice.call( arguments, 1 ),
bound = function bound() {
return fn.apply(
(!this ||
(typeof window !== "undefined" &&
this === window) ||
(typeof global !== "undefined" &&
this === global)
) ? obj : this,
curried.concat.apply( curried, arguments )
);
};
bound.prototype = Object.create( fn.prototype );
return bound;
};
function Test(){
//this has the right prototype and has
// the default arguments
console.log('this is:', this,'arguments',arguments);
this.name='test';
}
Test.prototype.something=22
var BoundTest = Test.softBind(null,'arg1','arg2');
var bt = new BoundTest();
//bt is same as new Test('arg1','arg2') would be
console.log('bt is:',bt);
//this.name did not affect window
console.log(window.name);//not test
console.log(bt instanceof Test);//true
when i try to call a function inside the object using "this" from a callback function, an error occur saying that the method is undefined.
How can I solve this issue!.
var object_log = {
user: "",
pass: "",
error_message: "an error occured while connecting",
init: function(user, pass) {
this.user = user;
this.pass = pass;
},
login: function() {
remote_submit(identify, this.success, this.error);
},
error: function() {
alert(this.error_message);
},
success: function() {
alert("success");
}
};
You need to use the .call() or .apply() methods on the callback to specify the context which the method is called upon.
The callback method remote_submit does not know what this will be anymore and thus when it calls the callback methods they're executed like normal functions not on an object.
You can "Bind" your functions by wrapping them on the way out:
var self = this;
remote_submit(
identify,
function() { return self.success.apply(self, arguments); },
function() { return self.error.apply(self, arguments); }
);
This allows you to pass the context in the closure of the anonymous function and execute the callbacks with an exclusive this context.
It appears that in EMCAScript5+ you can use bind on the function to bind it for use in a callback:
remote_submit(identify, this.success.bind(), this.error.bind())
However from the MDN Documentation:
The bind function is a recent addition to ECMA-262, 5th edition; as such it may not be present in all browsers. You can partially work around this by inserting the following code at the beginning of your scripts, allowing use of much of the functionality of bind() in implementations that do not natively support it.
The shim/polyfill is here:
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal IsCallable 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();
return fBound;
};
}
Update:
To answer your additional question, let's first look at the call and apply documentation and break down how they work:
Fundamentally they work the same, the only difference is how they take their arguments:
myfunc.call(target, param1, param2, param3);
Will call myfunc(param1, param2, param3) with target as this.
var args = [param1, param2, param3];
myfunc.apply(target, args);
Will call myfunc(param1, param2, param3) with target as this.
Basically the difference is that .apply() takes an array of arguments, where the call function requires you to write in the arguments in the code.
Next, if we look at the example i gave you:
function() { return self.success.apply(self, arguments); }
This returns a function that will call your callback by passing all the arguments (arguments variable) that were passed into the anonymous function, onto the apply function. So:
var a = function() { return self.success.apply(self, arguments); };
a(1,2,3,4);
This will call self.success(1,2,3,4) with self as this. If you'd like to augment the arguments with something specific for example if you wanted a(1,2,3,4) to call self.success(self.test, 1, 2, 3, 4) then you'll have to provide an augmented array to the apply function:
var a = function() {
var args = [self.test];
for(var i = 0; i < arguments.length; i++) args[] = arguments[i];
return self.success.apply(self, args);
}
When you pass the function as a callback, do it like this:
whatever( object_log.login.bind( object_log ) );
That call to the .bind method will return a function that'll make sure your "login" function will be called such that this references the "object_log" object.
There's a good shim for .bind for older browsers at the MDN documentation site.
Reference : http://ejohn.org/blog/simple-class-instantiation/
// makeClass - By John Resig (MIT Licensed)
function makeClass(){
return function(args){
if ( this instanceof arguments.callee ) {
if ( typeof this.init == "function" )
this.init.apply( this, args.callee ? args : arguments );
} else
return new arguments.callee( arguments );
};
}
I was wondering, if there are any ECMAScript 5 compliant way to implement the same functionality. The problem is, accessing arguments.callee is deprecated in strict mode.
As I understand it arguments.callee isn't deprecated in strict mode, in which case you could continue to use it; rather, it has been removed and attempted use will (or is supposed to) throw an exception.
The workaround is to use named anonymous functions, if you'll forgive the oxymoron. Really I should say "named function expressions". An example:
function someFunc(){
return function funcExpressionName(args){
if (this instanceof funcExpressionName) {
// do something
} else
return new funcExpressionName( arguments );
};
}
The name you provide, in my example funcExpressionName is not supposed to be accessible from anywhere except inside the function it applies to, but unfortunately IE has other ideas (as you can see if you Google it).
For the example in your question I'm not sure how to handle the args.callee since I don't know how that is set by the calling function, but the use of arguments.callee would be replaced as per my example.
The above idea given by nnnnnn is quite good. And in order to avoid IE issues I suggest the following solution.
function makeClassStrict() {
var isInternal, instance;
var constructor = function(args) {
// Find out whether constructor was called with 'new' operator.
if (this instanceof constructor) {
// When an 'init' method exists, apply it to the context object.
if (typeof this.init == "function") {
// Ask private flag whether we did the calling ourselves.
this.init.apply( this, isInternal ? args : arguments );
}
} else {
// We have an ordinary function call.
// Set private flag to signal internal instance creation.
isInternal = true;
instance = new constructor(arguments);
isInternal = false;
return instance;
}
};
return constructor;
}
Note how we avoid reference to args.callee in the // do something part by using an internal flag.
John Resig's original code fails with a parameterless constructor.
var Timestamp = makeClass();
Timestamp.prototype.init = function() {
this.value = new Date();
};
// ok
var timestamp = Timestamp();
alert( timestamp.value );
// TypeError: args is undefined
var timestamp = new Timestamp();
alert( timestamp.value );
But it can be repaired using the following line
this.init.apply( this, args && args.callee ? args : arguments );
I'm defining a 'class' in JavaScript by means of prototype.
The first time func() runs, it works, but when it's called the second time, through a setTimeout, it fails because this time it has lost the object context, I.E. this doesn't reference the object anymore but now references window.
Is there a way I can overcome this while still using prototype? or do I need instead to use closures to define a 'class'?
function klass(){}
klass.prototype = {
a: function() {
console.log( "Hi" );
},
func: function(){
this.a();
setTimeout( this.func, 100 );
}
};
var x = new klass();
x.func();
Use Function.prototype.bind:
setTimeout( this.func.bind(this), 100 );
From Mozilla Developer Network:
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal IsCallable 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
? this
: oThis || window,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
You can wrap it in a function:
var self = this; // reference the value of "this"
setTimeout( function() {self.func();}, 100 );
Use Function.prototype.bind, or wrap your setTimeout callback in its own closure:
func: function() {
var self = this;
self.a();
setTimeout( function() {
self.func();
}, 100);
}
Function.prototype.bind = function() {
var $this = arguments[0];
return this.apply($this, Array.prototype.slice.call(arguments, 1));
};
Is it good enough to use in real-world application?
No. There are a few things I don't like about this code, and a few reasons why it won't work.
First, most people don't assign arguments that way. It takes up extra space for no extra effect. Only use the arguments variable if the variable names should change depending on the number of arguments/type of arguments. To assign $this you should do..
Function.prototype.bind = function($this) {
Second, bind should return a function. Your's returns whatever this returns. Your function acts more like a Function:call then a Function:bind.
What you need to do to fix it, is make it return a function that when run will return whatever the function returns.
Try this:
Function.prototype.bind = function($this) {
// `this` changes inside the function, so we have to give it a safe name.
var self = this;
return function () {
return self.apply($this, Array.prototype.slice.call(arguments, 1));
}
};
Also, more modern browsers have the ECMAScript 5 standard of this function built in. The function is written in plain JavaScript, so for older browsers, just include this code suggested by Mozilla:
if ( !Function.prototype.bind ) {
Function.prototype.bind = function( obj ) {
var slice = [].slice,
args = slice.call(arguments, 1),
self = this,
nop = function () {},
bound = function () {
return self.apply( this instanceof nop ? this : ( obj || {} ), args.concat( slice.call(arguments) ) );
};
nop.prototype = self.prototype;
bound.prototype = new nop();
return bound;
};
}