I have problem with this code recently:
function doSth() {
console.log(this);
}
const fWithMeteorEnv = Meteor.bindEnvironment(doSth);
fWithMeteorEnv.call({}); // expect to see a plain object in console
What I expect is to see a plain object in console but not, it is something else. It seems that Meteor.bindEnvironment prevents the returned function to be called with another context. Are there any way to get around this?
I think that what you're trying to achieve is not possible, i.e. you will need to bind your context at the moment you're calling Meteor.bindEnvironment. You can do it with .bind(), e.g.
const fWithMeteorEnv = Meteor.bindEnvironment(doSth.bind(context));
or you can pass the context as the third argument to Meteor.bindEnvironemnt(), e.g.
const fWithMeteorEnv = Meteor.bindEnvironment(doSth, null, context);
The second argument is an exception callback.
Meteor.bindEnvironment(func, onException, _this) accepts 3 arguments and the function it returns is bound to the third argument. You need to bind it at the time it is created and using apply or call on it will pass the arguments, but the this reference will be overridden.
function doSth() {
console.log(this.foo);
}
const fWithMeteorEnv = Meteor.bindEnvironment(doSth, null, {foo: 'foo'});
fWithMeteorEnv.call({foo: 'bar'}); // will print 'foo'
This is rather similar to what you should expect with Function.prototype.bind. You should not expect to call a bound function and have its this context to be your argument.
let f = function() {
console.log(this);
}.bind({foo:'foo'});
f.call({foo: 'bar'}); // will log `{foo: 'foo'}`.
If you really need to set the this context for some function, you can wrap it and pass it as a parameter to the wrapper function instead (e.g, use the first argument to the wrapper as the this context of the original function).
If you need to have both the call semantics of the returned function, this can be done in a rather convoluted way.
/**
* Wraps the function.
* When the returned function is called, it sets the wrapped function's `this` to its first
* argument and passes it the rest of its arguments.
*/
function _wrapToMakeCallable(fn) {
return function() {
var _this = Array.prototype.shift.apply(arguments);
fn.apply(_this, arguments);
}
}
/**
* This function wraps the boundWithEnvironment function and maps the arguments such
* that it can be `call`ed or `apply`-ed as normal, using `wrapper`.
*/
function callableWrapAsync(fn) {
const bound = Meteor.bindEnvironment(_wrapToMakeCallable(fn));
return function() {
Array.prototype.splice.call(arguments, 0, 0, this);
bound.apply(this, arguments);
}
}
function doSth() {
console.log(this.foo);
}
Meteor.startup(function() {
const fWithMeteorEnv = Meteor.bindEnvironment(doSth, null, {foo: 'foo'});
fWithMeteorEnv.call({foo:'bar'}); // will print 'foo'
const callable = callableWrapAsync(doSth);
callable.call({foo:'bar'}); // will print 'bar'
});
Related
I've been reading quite a bit about the method bind() and I am beginning to understand that it sets this to a specific object. That almost always means that there is this somewhere within the function definition that is pointing to a certain object. However, I've seen in instances where bind() is used without this in the definition of the functions. More specifically, this is used as an argument, which confuses me. For example,
const eventLoader = new DataLoader((eventIds) => {
return events(eventIds);
});
const events = async eventIds => {
try {
const events = await Event.find({ _id: { $in: eventIds } });
return events.map(event => {
return transformEvent(event);
});
} catch (err) {
throw err;
}
};
const user = async userId => {
try {
const user = await userLoader.load(userId.toString());
return {
...user._doc,
_id: user.id,
createdEvents: eventLoader.load.bind(this, user._doc.createdEvents)
};
} catch (err) {
throw err;
}
};
In this example, eventLoader.load.bind(this, user._doc.createdEvents) uses this as an argument for bind() even though the eventLoader function nor the events function have this in their function definition. Isn't the first argument of bind() where you want the pre-existing this to point towards?
Isn't the first argument of bind() where you want the pre-existing this to point towards?
Yes, exactly.
In this example, eventLoader.load.bind(this, user._doc.createdEvents) uses this as an argument for bind() even though the eventLoader function nor the events function have this in their function definition.
To be more precise, DataLoader.load needs this to be the DataLoader to work correctly. Therefore it would make sense to .bind the eventLoader to it eventLoader.bind(eventLoader, ...).¹
Binding this makes no sense, as that is window (as an arrow function takes the context of the parent function ², and as you have no parent function [from the code shown] the parent is the global scope).
¹ Read on
² Even more to read
The first argument of the bind function sets the this value when the function is called.
Example:
// You have an object
var obj = { test: "Yeah, test!" };
// We check the object properties
console.log(obj.test);
// We define a function to work with an argument
function check(myobj) {
console.log("Is the same object?", obj === myobj);
console.log("Is the same object?", myobj.test);
}
// Now we define a function that will work with an object as "this"
function checkthis() {
// Notice how I use the "this" keyword, which will be the one that inherits or the one that you set
console.log("Is the same object?", obj === this);
console.log("Is the same object?", this.test);
}
// We call the first check
check(obj);
// Now we call the second checkthis but with another "this" context
// The first argument of call() will set the "this" context of the function and then call it instantly.
checkthis.call(obj);
// And now we are going to save another function with the this context changed.
// The first argument of bind() will also set the "this" context, but will save a reference of the function for later use, instead of calling it.
var newcheckthis = checkthis.bind(obj);
// Now we call this new function.
newcheckthis();
Note that newcheckthis is the checkthis function but with the this context changed. checkthis will keep its context.
Also, with bind you can force to create a function reference with the this. context changed AND with default arguments set.
Example:
// We create a function that will sum the some this object value to an argument
function sum(sumvalue) {
return this.value + sumvalue;
}
// We create the main object
var obj = { value: 10 };
// Then we call the function setting the "this" context and passing an argument
// Note how I pass obj as the first argument and 5 as the second
// Actually it will set as the first argument (sumvalue) the second argument in the call, as the first argument in the call will be set to the "this" context.
var result = sum.call(obj, 5);
// And we can use the same trick with bind()
var newsum = sum.bind(obj, 5);
// Now we have a newsum function that has the this context set to "obj" and the first argument (sumvalue) set to 5 by default, which cannot be replaced.
// If we cann the function, it will di the same as the call() function above
result = newsum();
// result will still be 15.
So, call() and bind() (there's another one: apply(), but works a bit different) will have this signature:
.bind/.call(THISCONTEXT, THIS_2ND_ARGUMENT_WILL_BE_THE_FIRST_ONE, 3RD_AS_SECOND, AND_SO_ON...);
[This is related to Bound function instead of closure to inject extra arguments, but that was neither clearly asked nor answered.]
I'm calling a function that expects a function as its argument. I want to pass a method from my class, bound to an instance of my class. To make it clear, assume my class looks like:
var MyClass = function() {}
MyClass.prototype.myMethod = function() { ... }
var my_instance = new MyClass();
Is there any substantive difference between using bind:
doSomething(my_instance.myMethod.bind(my_instance))
and wrapping the call in an anonymous function:
doSomething(function() { my_instance.myMethod(); })
?
If a prototype within your class needs to generate a callback, it may not know its instance's name. As a result, you'll need to use this, but the value of this depends on where the callback was executed.
Consider the following example:
var MyClass = function (x) { this.something = x; };
MyClass.prototype.makeCall = function () {
var myBadCallback = function() { console.log(this.something); };
var myGoodCallback = function() { console.log(this.something); }.bind(this);
// When called, the value of "this" points to... we don't know
callMeBack( myBadCallback );
// When called, the value of "this" points to this instance
callMeBack( myGoodCallback );
};
function callMeBack( callback ) { callback(); };
var foo = new MyClass('Hello World!');
var bar = new MyClass('Goodbye!');
// Probably prints "undefined", then prints "Hello World!"
foo.makeCall();
// Probably prints "undefined", then prints "Goodbye!"
bar.makeCall();
In the above example, the first output probably prints undefined because the context (what this refers to) has changed by the time the callback has executed.
This example may seem contrived, but these sort of situations do arise, a common case being AJAX callbacks.
Can anyone explain me on how hard binding works in javascript?.
function foo() {
console.log(this.a);
}
var obj = {
a: 2
};
var bar = function() {
foo.call(obj);
};
bar(); // 2
setTimeout(bar, 100); //
I am more interested in this function.
var bar = function() {
foo.call(obj);
};
Why do we wrap foo.call(obj) inside another function?. We can use it directly right?.
setTimeout(foo.call(obj), 100); // still results in 2.
The .call (and .apply) methods simply let you manually set the value of this in the function being invoked.
So when you do foo.call(obj), the value of this in foo will be obj.
As to the setTimeout, what you're doing is calling it immediately instead of waiting for 100ms. Change it to 10000, and you'll see more clearly it doesn't wait.
So that's why the function is needed. You need to pass a function to setTimeout, and it will get invoked after the duration you provide.
There's also the .bind() method that creates a new function with its this value permanently bound to the first argument you provide. So that would actually be a hard binding example
setTimeout(foo.bind(obj), 100);
So in that example, a function is returned that will always have obj set as the this value. So now that setTimeout is being passed a function, it will be invoked after the given duration.
You can also bind arguments to the function. All arguments passed to .bind() after the first argument will be permanently bound to the returned function so that any arguments passed to that function will be placed after the bound ones.
You don't need setTimeout to achieve hard binding.
function foo() {
console.log(this.bar);
}
var obj1 = {bar:10};
var obj2 = {bar:5};
var originalFoo = foo;
OriginalFoo now has the reference to foo
Now override foo function and use originalFoo.call to set the this context to always be that of obj1
foo = function() {
originalFoo.call(obj1);
}
foo(); // returns 10
foo.call(obj2); //returns 10 even when obj2 passed as arg
I have target object
function Foo() {
this.someVar = 'some var';
};
Foo.prototype.callback() {
console.log(this);
};
And object, that will call this callback
function Bar(callback) {
this.callback = callback;
};
Bar.prototype.onSomeAction = function() {
this.callback();
};
And initial code
foo = new Foo();
bar = new Bar();
bar.callback = foo.callback;
bar.onSomeAction();
Result: i have logged to console Bar()'s context instead of Foo().
How can i get context of Foo() in the Foo() callback?
PS: I tried closures
Foo.prototype.callback() {
var foo = this;
return function(foo) {
console.log(foo);
};
};
but it does nothing. I have not fully understanding of the closures :(
The reason your original code didn't work is that the value of this inside of a method call is the value of the object it's being called on. That means when you say:
bar.callback = foo.callback;
And then you call:
bar.callback();
The code defined here:
Foo.prototype.callback = function () {
console.log(this);
};
gets called with this being a reference to bar because bar is to the left of the . on the method call. So whenever you assign a function as an object property, calling it on that object will call it with the object as this.
You could also have written:
function callback() {
console.log(this);
}
bar.callback = callback;
bar.callback();
And you would find that this still references bar.
In fact, if you call the plain function callback(); as defined above, you'll find that this is a reference to the global object, usually window in web browsers. That's because all global variables and functions are properties of window, so callback(); is implicitly window.callback();
The fact that the value of this depends on what object is calling a function can be a problem when passing callbacks around, since sometimes you want this to reference the original object the function was a property of. The bind method was design to solve this problem, and Yuri Sulyma gave the right answer:
bar.callback = foo.callback.bind(foo);
However, the way you would do this using closures is to capture an instance of Foo within an anonymous function that calls the correct method on the correct object:
foo = new Foo();
bar = new Bar();
bar.callback = function () {
foo.callback();
};
bar.onSomeAction();
Which is essentially what bind does. In fact, we call write our own naive version of bind using a closure:
Function.prototype.bind = function (obj) {
var fn = this;
return function () {
fn.call(obj);
};
};
call let's you call a function with the value of this explicitly defined. This allows you to "set the context" the function is called in so that it's the same as calling obj.fn() when you call bar.callback(). Since when we call foo.callback.bind(foo);, obj is foo and fn is foo.callback, the result is that calling bar.callback() becomes the same as calling foo.callback().
That's where Dalorzo's answer comes from. He uses call to explicitly set the context.
There's also another function for setting the context called apply that also takes an array representing the arguments for the function as its second argument. This allows us to write a more complete version of bind by taking advantage of the special arguments variable:
Function.prototype.bind = function (obj) {
var fn = this;
return function () {
fn.apply(obj, arguments);
};
};
bar.callback = foo.callback.bind(foo);
You can polyfill Function.prototype.bind() if necessary: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
Try using these changes:
Use call to set context:
bar.onSomeAction.call(foo);
And I think your callback function needs to change to:
Foo.prototype.callback=function() {
console.log(this);
};
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);
};
};