bind method creates a new function that when called has its this keyword set to the provided value.
var obj = {
a: 0,
b() {
console.log(this.a);
}
}
obj.b() // -> 0
var functionBound = obj.b.bind(obj)
functionBound() // -> 0
functionBound.bind(null)() // -> 0 AND I expect an error here
Clearly, I cannot rebind a function has already been rebound. However, I could not find any documentation on this behavior.
Quote from "Bind more arguments of an already bound function in Javascript"
Once you bound an object to a function with bind, you cannot override it. It's clearly written in the specs, as you can see in MDN documentation:
The bind() function creates a new function (a bound function) with the same function body (internal call property in ECMAScript 5 terms) as the function it is being called on (the bound function's target function) with the this value bound to the first argument of bind(), which cannot be overridden.
I could not find these in MDN documentation. I did an exact full-text search on the quote above on Google and seems the SO answer above is the only source for this behavior. I also try to find an answer in the language spec with no luck.
My question is do you know this behavior and where can I find any official documentation on these?
This may not directly answer the question about getting a officially documented specification validating this behavior, but we can base our conclusions on the source code provided in MDN, specifically in the documentation for Function.prototype.bind(), under section Polyfill, where they provide an example of how a polyfill bind function would look like.
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,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
}
We can see that the oThis parameter is used in the closure fBound which is the one ultimately returned by the bind function.
This means that when you invoke the bind function, you get a closure function in return which, when invoked, accesses the oThis free variable provided as parameter in the original invocation of bind.
As such, it doesn't matter how many more times you rebind the bound fBound function, this function is already bound forever to the original context oThis within its closure.
The MDN documentation also points to Raynos bind implementation for further reference, which seems to correspond to this example as well.
The problem is that Function.prototype.bind returns a NEW function instead of the same. Calling a bound function with a different this-argument has no effect, because the bound function already knows which value to use as the this-argument.
You could use this for binding your functions:
Function.boundOriginProp = Symbol()
Function.prototype.bindDynamic = thisArg => {
let origin = this[Function.bindOriginProp] || this
let bound = (...args) => origin.call(thisArg, ...args)
bound[Function.bindOriginProp] = origin
return bound
}
So you can rebind functions that have already been bound like this:
let obj1 = { value: 1 }
let obj2 = { value: 2 }
function example() {
console.log(this.value)
}
let fn1 = example.bindDynamic(obj1)
fn1() // -> 1
let fn2 = fn1.bindDynamic(obj2)
fn2() // -> 2
let fn3 = fn1.bindDynamic(null)
fn3() // -> undefined
I hope this can help you ;)
The bind method wraps the original function and creates a new Bounded Function.
Actually, a function which wraps the original function keeping the same body of the original function.
This is the definition in the MDN website:
The bind() function creates a new bound function (BF). A BF is an
exotic function object (a term from ECMAScript 2015) that wraps the
original function object. Calling a BF generally results in the
execution of its wrapped function.
So, every time you call .bind, you create a new function passing the context as first parameter and the args as rest of parameters, but keeping the body of the first definition.
You can also override the initial body, and bind the function again.
At the same time, you can also take a previously bounded function and bind it again to a new function.
In the following example, you should see the expected behavior:
var printer = function(a) {
console.log(a);
};
var printer1 = printer.bind(null, 1);
var printer2 = printer.bind(null, 2);
var printer3 = printer.bind(null, 3);
printer1();
printer2();
printer3();
printer = function(a) {
console.log("I am a new " + a);
};
var printer4 = printer.bind(null, 4);
printer4();
var newPrinter = function() {
console.log('I am a new printer!');
}
printer4 = newPrinter.bind(null);
printer4();
Related
Is it possible to re-bind a function so that it is bound to the same object it was originally but accepts a different argument value?
For instance...
// Object definition
function OriginalOwner(prop) { this.prop = prop; }
OriginalOwner.prototype.aFunc = function(arg) { return this.prop + arg; }
// Object instance
var owner = new OriginalOwner("Returned ");
// Example function bindings
var boundFunc = owner.aFunc.bind(owner);
var reboundFunc = boundFunc.bind(boundFunc.owner, "myArgument");
// Calling rebound function
console.log(reboundFunc()); // outputs "Returned myArgument"
If you just want to add arguments, you can do that with bind. It doesn't matter what you supply for the first bind argument, because it will be ignored when you call bind on a bound function (so null is a reasonable choice for that first argument).
Because the first argument is ignored, your original code would work unchanged, but it would be misleading (boundFunc has no owner property, so boundFunc.owner yields undefined). But better to use null or something else to avoid misreading people reading the code later.
Only change is formatting and a missing ;, plus the *** line:
// Object definition
function OriginalOwner(prop) {
this.prop = prop;
}
OriginalOwner.prototype.aFunc = function(arg) {
return this.prop + arg;
};
// Object instance
var owner = new OriginalOwner("Returned ");
// Example function bindings
var boundFunc = owner.aFunc.bind(owner);
var reboundFunc = boundFunc.bind(null, "myArgument"); // ***
// Calling rebound function
console.log(reboundFunc()); // outputs "Returned myArgument"
The reason that works is that bind returns a new function that will get called with the this we supply — but the bound function we're calling it on completely ignores the this you call it with, using the one it has bound to it instead.
I'm calling a jQuery function in two ways, one works the other doesn't because this is bound incorrectly.
$('#my-div').fadeOut();
works as expected, the value of this inside the fadeOut function is the jQuery object.
var fadeOutFn = $('#my-div').fadeOut;
fadeOutFn();
doesn't work since value of this is now Window
here is the jsfiddle with both examples.
http://jsfiddle.net/XCAdP/
Edit: Adding some clarifications on why I posted the question, I don't really want to know how to fix this. that's not the issue. I want to understand why it's happening.
Yes it does not know the element it is applying fadeOut animation to and this context will be mostly the window and not the jquery object in this case. You can pass the context using function.call
Try this:
var fadeOutFn = $('#my-div').fadeOut;
fadeOutFn.call($('#my-div'));
Or just do this:
use function.bind to bind the context to the function reference, and invoke it.
var fadeOutFn = $().fadeOut.bind($('#my-div'));
fadeOutFn();
Read function.bind
For non supported browsers you can add this in your js file for support as mentioned in the document:
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;
};
}
Yes, when you get a method as a function reference, you disconnect it from the object. A function only works as a method when you call it in the context of an object, which is usually done with the . operator, e.g. obj.method().
If you call a function without the context of an object, it's called with the global scope as context, i.e. the window object. Example:
var obj = {
name: "obj",
method: function() { alert(this.name); }
};
obj.method(); // shows the name "obj"
var m = obj.method;
m(); // shows the name of window
m.call(obj); // shows the name "obj"
var obj2 = {
name: "obj2"
};
m.call(obj2); // shows the name "obj2" (using the method from obj)
If you want to make it work as a method, you have to call it with the object as context:
var obj = $('#my-div');
var fadeOutFn = obj.fadeOut;
fadeOutFn.call(obj);
You can use the proxy method to make a function that calls the function with the correct context:
var obj = $('#my-div');
var fadeOutFn = $.proxy(obj.fadeOut, obj);
fadeOutFn();
In the following code, the first function is not bound to obj, but the second function is, so f() returns fifi and g() returns Mark Twain as expected. But the 3rd attempt, it is by (obj.getCallBack) first, which is now a function, and then it is invoked, essentially it should be the same as the f case. But they do print out Mark Twain instead. Why are they not bound to obj using bind() but still got executed with this pointing to obj?
(the 4th attempt is just a usual invocation of a method, and this should bind to the object on which the method is invoked on).
(tested on the current Chrome, Firefox, and IE 9)
window.name = "fifi";
var obj = {
name: "Mark Twain",
getCallBack: function() {
return this.name;
}
}
var f = obj.getCallBack;
var g = f.bind(obj);
console.log(f);
console.log(f());
console.log(g);
console.log(g());
console.log((obj.getCallBack)());
console.log(obj.getCallBack());
You are forgetting that if a function is called as a property of some object, that object will be the this for the call. So:
obj.getCallBack() //The function referenced by `obj.getCallBack`
//is called as a property of `obj`, so obj will be `this`
//for the call
f() //The function referenced by f, is not called as a property of some object so
//`this` will depend on strict mode.
After these basic rules, the bound function will be invoked, which can be thought of as a proxy function (any shim does this) that uses .call/.apply to explicitly set the context for the target function. So the this value for the proxy function doesn't matter, but behind the scenes it was set by the basic rules.
Edit:
(obj.getCallBack) does not return the function as value, because getValue is not called.. So it is exactly the same as obj.getCallback and the first post applies.
So you can do this and not get an error:
(obj.getCallback) = 5;
As opposed to:
(function(){}) = 5; //invalid assignment
To complement Esailija's answer, the desired effect actually should be:
var obj = {
name: "Mark Twain",
getCallBack: function() {
return function() { return this.name; };
}
}
var f = obj.getCallBack();
var g = f.bind(obj);
console.log(f);
console.log(f());
console.log(g);
console.log(g());
console.log((obj.getCallBack())());
console.log(obj.getCallBack()());
console.log(obj.getCallBack().bind(obj)());
Then in this case, the third attempt will give fifi, and so will the 4th attempt. To get the name inside obj, the fifth attempt binds it and invoke it and will get Mark Twain.
But the method that returns the callBack function should bind it, so let's change the code to:
var obj = {
name: "Mark Twain",
getCallBack: function() {
return (function() { return this.name;}).bind(this); // <-- note here
}
}
and now all attempts, even f(), will return Mark Twain.
Is there any method that invokes a function but sets the context this to the "default" value that it has when I invoke the function by doing fn()?
This method should accept an array and pass the single elements as arguments to the function, much like apply() does:
emitter = new EventEmitter();
args = ['foo', 'bar'];
// This is the desired result:
emitter.emit('event', args[0], args[1], ...);
// I can do this using apply, but need to set the context right
emitter.emit.apply(emitter, 'event', args);
// How can I trim the context from this?
emitter.emit.???('event', args);
EDIT: To clarify this, I do care about the value that this will have inside the called function - it needs to be the "normal" context it has when doing emitter.emit(), not the global object or anything else. Otherwise, this will break things sometimes.
You can pass null or undefined if you don't care about the context. Inside the function, this will then refer to the global object when in non-strict mode and to null respectively undefined in strict-mode.
A "default" context for a function is hard to define
function f() { return this };
a = { b: f }
c = a.b;
console.log(f()); # window
console.log(a.b()); # a
console.log(c()); # window
Which one of these is the "right" context?
In your case you might consider a utility function
/* you might call it something else */
emitter.emit_all = function (event, args) {
return this.emit.apply(this, [event].concat(args));
}
Just set the first parameter to the global object (i.e. window in a browser)
In ES3 browsers you could pass null instead and it would be automatically be changed to the global object, but that behaviour has been removed in the ES5 specifications.
EDIT it sounds like you just need a new function:
EventEmitter.prototype.emitArgs = function(event, args) {
this.emit.apply(this, [event].concat(args));
}
at which point you can just call:
emitter.emitArgs('event', args);
(EDIT thanks to #Esalija for [].concat)
This is solved by the native Function "arguments" variable.
var EventEmitter = window.EventEmitter = function(){
//this.emit = this.emit.bind(this);//bind emit to "this"
return this;
};
EventEmitter.prototype.isMe = function(obj){
return obj === this;
};
EventEmitter.prototype.emit = function(eventName){
var input = Array.prototype.slice.call(arguments, 1);
console.log("this:%O, Emitting event:%s, with arguments:%O", this, eventName,input);
};
emitter = new EventEmitter();
emitter.emit('magicEvent', 'Zelda Con', 'Zork Meetup', 'etc');
To maintain the "this" context you could bind the emit method in the constructor, though this would create per instance "own" object properties increasing memory consumption and practically perform all prototype chain lookups (for bound methods) on object creation regardless if you needed them or not.
Consider the following example:
var funcToCall = function() {...}.bind(importantScope);
// some time later
var argsToUse = [...];
funcToCall.apply(someScope, argsToUse);
I want to preserve 'importantScope' of funcToCall. Yet, I need to use apply to apply an unknown number of arguments. 'apply' requires that I provide 'someScope'. I don't want to change the scope, I just want to apply the arguments to the function and preserve its scope. How would I do that?
You can pass any old object (including null) as the first argument to the apply() call and this will still be importantScope.
function f() {
alert(this.foo);
}
var g = f.bind( { foo: "bar"} );
g(); // Alerts "bar"
g.apply(null, []); // Alerts "bar"
The bind method creates a new function in which the this value is guaranteed to be the object you passed in as the parameter to the bind call. Regardless of how this new function is called, this will always be the same. A simple implementation would look like this (note the implementation specified ECMAScript 5 and that in Prototype does more than this but this should give you the idea):
Function.prototype.bind = function(thisValue) {
var f = this;
return function() {
return f.apply(thisValue, arguments);
};
};