Keep babel class as `this` when using a member function in `setTimeout` - javascript

I have an ES2015 class, call it Foo, which has at least two member functions, bar and baz. In bar there is a call to setTimeout whose first parameter is this.baz. Works fine up to here, I inspected it in the debugger, and this does refer to the instance of my class. (Actually since I'm using babel, I end up with a _this = this substitution beforehand but anyway the right thing is being passed into the setTimeout, confirmed.)
The problem is when the setTimeout callback fires, it calls the right function baz, but the this inside of baz the value of this refers to Window instead. Babel tries to do a _this2 = this at the beginning of baz but it seems to be too late already.
So my problem appears, somewhere in between the function baz being passed and the time it is called, it loses its this scoping. My question is, am I doing something wrong with ES2015 or babel here? I feel like this is a common enough use case that it shouldn't require too much strain. Personally I'd like to do this all with Promise but due to business requirements I can't add too much new JS stuff at once.
Alternatively, is there a standard idiom for passing the scope of this as I need here? It seems really messy and counter-intuitive to have to pass the calling object of a member function in as one of its parameters.
Here's a minimal working example for reference:
class Foo{
bar(){
setTimeout(this.baz, 1000);
}
baz(){
console.log("this message should repeat roughly once per second");
this.bar();
}
}
And here's a screenshot of me using it on a very simple page, along with the error message:
Edit: I have to object to my question being marked as a duplicate. Of course I had searched seen the setTimeout questions before asking this one. However the ES2015 and class-based aspect of my question is relevant and important, since the ES2015 syntax transformation of classes in babel changes the apparent behavior of this. My question was about whether there is another ES2015 design pattern to handle this, and why the intuitive class abstraction/encapsulation was being broken by passing a member function as a first-class value to be called externally. The most significant insight I gained was gleamed from a comment below by #FelixKing which I'll repeat here for posterity (in case anyone else is wondering):
Whether or not autobind class methods (like in Python) was discussed
but ultimately decided against, likely to keep consistency with the
rest of the language. There is also the question whether it would be
possible to autobind methods without memory/performance impact.

My question is, am I doing something wrong with ES2015 or babel here?
Actually, it's a expected JavaScript behavior and is related to how this is assigned in the language.
Consider the code below (no ES6, no babel...):
var obj = {
key1: 'value1',
key2: function() {
console.log(this);
}
}
obj.key2(); //will print obj
var callback = obj.key2; //assigned the function reference to some random variable
callback(); //will print Window/global object
As you can see, this is defined when the function is invoked, not when it's declared, and depends how it's being called.
That's exactly what's happening inside setTimeout, or in any function that receives a function as a parameter:
/* fake */
function setTimeout(fnCallback, time) {
/* wait and when the time comes, call your callback like this: */
fnCallback(); //'this' will be Window/global
}
"Workarounds":
In order to pass the desired context (in the example above), we can force the context:
using .bind:
var callback = obj.key2.bind(obj);
callback(); //will print obj
or using .call:
var callback = obj.key2;
callback.call(obj); //will print obj
Or we can pass an anymous function an call our object from inside:
setTimeout(function() {
//here, 'this' is Window/global, because the anonymous function is being called from a callback assignment
obj.key2(); //will print obj
}, 3000);
In your example
So, in your example, in order to properly set the setTimeout callback and ensure that baz() will receive the class context, you can:
bind the function when setting it as a callback:
setTimeout(this.baz.bind(this), 1000);
in your class constructor, bind the baz method once; so, everytime it's called, will be assigned the class context. Like this:
class Foo{
constructor() {
this.baz = this.baz.bind(this)
}
bar(){
setTimeout(this.baz, 1000);
}
baz(){
console.log("this message should repeat roughly once per second");
this.bar();
}
}
Use arrow functions. Another way of specifying the this context is using arrow functions, that, actually, ensure the this assignment is done through lexical scope (not anymore in the function invocation, but in the function declaration).
setTimeout(() => this.baz(), 1000);
// ^^^^
// 'this' here is your class, will pass your class as 'this'
// to the baz() method, due to the dot before
Different from:
setTimeout(function() { this.baz(); }, 1000);
// ^^^^
// 'this' here is Window/global, will thrown undefined method

Related

Javascript: Create a sub-class within a class [duplicate]

I thought I had a reasonable understanding of the this object in JavaScript. When dealing with objects, callbacks, and both events and handlers, I haven't had a problem with it since time immemorial. Now, however, all has changed.
I've fallen head over heels in love with JavaScript. Pure JS, that is, not jQuery, prototype.js, dojo... So naturally, I've taken to using closures. In some cases, though, this is catching me off guard here. Take this snippet for one:
function anyFunc(par)
{
//console.log(par);
console.log(this);
}
function makeClosure(func)
{
return function(par)
{
return func(par);
}
}
var close = makeClosure(anyFunc);
close('Foo');
var objWithClosure = {cls:makeClosure(anyFunc),prop:'foobar'};
objWithClosure.cls(objWithClosure.prop);
var scndObj = {prop:'Foobar2'};
scndObj.cls = makeClosure;
scndObj.cls = scndObj.cls(anyFunc);
scndObj.cls(scndObj.prop);
In all three cases, this logs as the window object. It's an easy fix, of course:
function makeClosure(func)
{
return function(par)
{
return func.call(this,par);
}
}
This fix works, I put it here to avoid people answering this, without explaining what I need to know: why is this behaving the way it does here?
ensures the caller is effectively the object that the closure belongs to. What I fail to understand is this:
Sure enough, this points to the window object in the first case, but in other cases, it shouldn't. I tried logging this in the makeClosure function just before returning, and it did log the object itself, not the window object. But when the actual closure is used, this is back to pointing to the window object. Why?
The only thing I can think of is that, by passing the anyFunc function as an argument, I'm actually passing window.anyFunc. So I tried this quick fix:
function makeClosure(func)
{
var theFunc = func;
return function(par)
{
theFunc(par);
}
}
With the expected results, this now points to the objects, but again: Why? I have a few idea's (theFunc is a reference to the function in the local scope [this > private: theFunc]?), but I'm sure there are people here with a lot more know-how when it comes to JS, so I was hoping to get some more explanation or links to articles worth reading from them...
Thanks
Update
Here's a fiddle, may be I left something out, but here this logs all sorts of things ;)
Edit/Update 2
The case that confuses me is here.
Final Edit
Ok, This is getting a rather messy post. So to clarify: What I was expecting was a behaviour similar to this:
function makeClosure()
{
function fromThisFunc()
{
console.log(this);
}
return fromThisFunc;
}
var windowContext = makeClosure();
windowContext();
var objectContext = {cls:makeClosure()};
objectContext.cls();
What caught me, was that the function anyFunc wasn't declared within the correct scope, and therefore, this pointed to the window object. I found this out by reading an ancient scroll I found somewhere on the web.
But something a little more complicated has happened because the function object now referred to by globalVar was created with a [[scope]] property referring to a scope chain containing the Activation/Variable object belonging to the execution context in which it was created (and the global object). Now the Activation/Variable object cannot be garbage collected either as the execution of the function object referred to by globalVar will need to add the whole scope chain from its [[scope]] property to the scope of the execution context created for each call to it.
So what I needed to do, was simplify rather then complicate things:
function fromThisFunc()
{
console.log(this);
}
function makeClosure(funcRef)
{
//some code here
return funcRef;
}
That should work, right?
PS: I'll except Alnitak's answer, but special thanks goes to Felix Kling for all the patience and info.
As soon as you call:
return func(par);
You're creating a new scope (with its own this) and in this case because you haven't specified an object, this === window usually or undefined in strict mode. The called function does not inherit whatever this was in the calling scope.
Ways to set a value for this are:
myobj.func(par); // this === myobj
or
func.call(myobj, ...) // this === myobj
There are also:
apply
bind
arrow functions, where this is set to the same value as the outer execution context (also see MDN:Arrow functions )
The value of this depends only on whether you call the function as a method or as a function.
If you call it as a method, this will be the object that the method belongs to:
obj.myFunction();
If you call it as a function, this will be the window object:
myFunction();
Note that even if you are in a method that belongs to an object, you still have to call other methods in the object using the method syntax, otherwise they will be called as functions:
this.myOtherFunction();
If you put a method reference in a variable, you will detach it from the object, and it will be called as a function:
var f = obj.myFunction;
f();
The call and apply methods are used to call a function as a method even if it's not a method in the object (or if it's a method in a different object):
myFunction.call(obj);

'this' concept in the object in javascript

I am sorry for that the simple question first. But I just want to know what happen in the code below.
var indexobj = {
test : 1,
testFunction : function() {
document.getElementById('AA').innerHTML = indexobj.test++;
//this is i wanted
document.getElementById('BB').innerHTML = this.test++;
//NaN
setTimeout(indexobj.testFunction,1000);
},
}
setTimeout(indexobj.testFunction,1);
Here is the sample link below
http://jsfiddle.net/apss/JR5Xk/28/
Why in this example the ‘this’ inside the ‘testFunction’ function doesn’t mean ‘indexobj’ this object?
Thanks for your help.
Because setTimeout runs callback function in global object context, meaning that indexobj.testFunction is invoked with this being a window object.
So you can either do
setTimeout(function() {
indexobj.testFunction();
},1000);
or using Function.prototype.bind to create wrapper function with correct context:
setTimeout(indexobj.testFunction.bind(indexobj), 1000);
Another modern option from ES2015 is to use arrow function that preserves lexical context:
setTimeout(() => indexobj.testFunction(), 1000);
A function's this set set by:
How the function is called
the use of bind.
I doesn't matter where it's called from.
In this case, you have:
setTimeout(indexobj.testFunction,1000);
which passes the function referenced by indexobj.testFunction to be called later by setTimeout. When called, the function is called directly, so its this isn't set by the call and it defaults to the global object (window in a browser) or remains undefined in strict mode.
You can fix that in a number of ways, one is to pass a function that sets indexobj to this in the call:
setTimeout(function(){indexobj.testFunction()}, 1000);
or you can use bind as suggested by dfsq.
Arrow functions in the ES6 draft change how this is set, but that's for the next edition…

Nested .bind not working as expected

Unfortunately .bind has been giving me grief when creating more complex closures.
I am quite interested in why .bind seems to work differently once you nest functions.
For example :
function t(){
t = t.bind({}); //correctly assigns *this* to t
function nested_t(){
nested_t = nested_t.bind({}); // fails to assign *this* to nested_t
return nested_t;
}
return nested_t();
}
//CASE ONE
alert(t());
// alerts the whole function t instead of nested_t
//CASE TWO
aleft(t.call(t));
// alerts the global object (window)
In both cases I was expecting a behavior like this:
function t(){
var nested_t = function nested_t(){
return this;
};
return nested_t.call(nested_t);
}
alert(t.call(t));
If someone could explain the behavior of .bind in the first (and/or) second case it would be much appreciated!
So, i'm not entirely reproducing your issue here (both cases return the global object), but i'll try and explain the code as i see it.
function t(){
t = t.bind({}); //correctly assigns *this* to t
function nested_t(){
nested_t = nested_t.bind({}); // fails to assign *this* to nested_t
return this;
}
return nested_t();
}
//CASE ONE
alert(t());
Let's take it step by step.
First, the function t() is defined. Then, upon call, it gets overwritten with a clean context. However, i don't see any usage of this context.
Now, a nested function (nested_t) is defined. Upon call, it is overwritten with a clean context. It then returns the context it had when it was called.
Back to t(). You then return the result of nested_t(), not nested_t itself. In the original function call, nested_t is still being called with global context.
Therefore, when you run t(), it returns the global object.
How your code works
It's very unclear, what your code is trying to do. You can find the documentation for .bind() here. It looks like you might be somehow misunderstanding what this is and how to use it. Anyway, what happens when you run your code is this:
A t function is created in global scope.
[case one] The t function is called.
Global scope t is replaced with a new value (original t bound to a specific context - anonymous empty object), which doesn't affect the current call in any way. Also, while the global t is overwritten, the local t is behaving as read-only. You can check it out by trying the following code: (function foo () { return (function bar () { bar = window.bar = 'baz'; return bar; })(); })() and comparing return value with window.bar.
The same thing happens with nested_t in the nested context (instead of global context).
Result of nested_t call is returned. nested_t returns the context it was called with, which was window, as no context was specified. Specifically, it was not called with an empty object context, because the .bind() inside didn't affect the call itself.
[case two] The exact same thing happens once again. Now you're just calling t with itself as context. Since t doesn't use its context (this) anywhere in its code, nothing really changes.
What your misconceptions might be
Basically, you're mixing up two things - function instance and function call context. A function is a "first-class citizen" in JavaScript - it's an object and you can assign values to its properties.
function foo () {
foo.property = 'value';
}
foo(); // foo.property is assigned a value
This has nothing to do with function call context. When you call a function a context is assigned to that call, which can be accessed using this (inside function body)
function foo () {
this.property = 'value';
}
var object = {};
foo.call(object); // object.property is assigned a value
When you use .bind(), you just create a new function with the same code, that is locked to a specific context.
function foo () {
this.property = 'value';
}
var fixedContext = {},
object = {};
bar = foo.bind(fixedContext);
foo.call(object); // fixedContext.property is set instead of object.property
But in this case, there are also function instances foo and bar, which can also be assigned properties, and which have nothing to do with contexts of calls of those functions.
Let's look at how bind works. First, one level of nesting:
var foo = function() { return this.x; };
alert(foo()); // undefined
alert(foo.bind({x: 42})()); // 42
Now we can add the next level of nesting:
var bar = function() { return foo.bind(this)(); };
alert(bar()); // undefined
alert(bar.bind({x: 42})());
We pass our this context to foo with - guess what? - bind. There is nothing different about the way bind works between scopes. The only difference is that we've already bound this within bar, and so the body of bar is free to re-bind this within foo.
As a couple of commenters have noted, functions that overwrite themselves are a huge code smell. There is no reason to do this; you can bind context to your functions when you call them.
I highly, highly recommend reading the documentation on bind, and trying to understand it to the point where you can write a basic version of Function.prototype.bind from scratch.

why is this private method in a constructor?

I'm a bit puzzled by this coding pattern I've run into even though I've been studying up on 'this.' The following (simplified) code shows the pattern:
var MyConstructor = function MyConstructor() {
this._handlers = {
action: this.handleAction.bind(this)
};
};
MyConstructor.prototype.start = function(someObj) {
this.someObj.on(’some event’, this._handlers.action); //<--here
}
MyConstructor.prototype.handleAction = function() {
//do stuff
}
module.exports = MyConstructor;
My question is, why is the private method in the constructor required? Is this pattern avoid some common problem? Could the line commented //<--here simply be:
this.someObj.on(’some event’, this.handleAction);
No, they are different. The difference is in context, which means the value of this within the function.
this.handleAction passes the function to on without any context. There is no value of this specified. The value will be determined when the function is executed. It is very likely that the value will not be the a MyConstructor object, so this.start, for instance, will not refer to the right object, or indeed perhaps any object at all.
The solution is to bind context. This sets the context forever, so this will always refer to the right value. You see this line of code:
action: this.handleAction.bind(this)
This means that, when the code later refers to this._handlers.action, it will be sending the function to on with the appropriate context, so this will always point to the correct value.
The difference between the following lines
this.someObj.on(’some event’, this.handleAction.bind(this));
this.someObj.on(’some event’, this.handleAction);
... is that the first handleAction will run with this being the instance of MyConstructor, while the second will run in whatever context the event handling mechanism decides. If it is something like this, it will run with this being the global object:
function on (a, callback) {
callback(); // callback is run with this as the global object
// UNLESS it was bound to something else
}
The 'private' _handlers property is just an object that holds references to callbacks bound to the instance. If you were to call bind twice, two functions would be created. The _handlers property makes it so that a single bound function is created which can be used as a handler for any number of events.

JavaScript closure and the this object

I thought I had a reasonable understanding of the this object in JavaScript. When dealing with objects, callbacks, and both events and handlers, I haven't had a problem with it since time immemorial. Now, however, all has changed.
I've fallen head over heels in love with JavaScript. Pure JS, that is, not jQuery, prototype.js, dojo... So naturally, I've taken to using closures. In some cases, though, this is catching me off guard here. Take this snippet for one:
function anyFunc(par)
{
//console.log(par);
console.log(this);
}
function makeClosure(func)
{
return function(par)
{
return func(par);
}
}
var close = makeClosure(anyFunc);
close('Foo');
var objWithClosure = {cls:makeClosure(anyFunc),prop:'foobar'};
objWithClosure.cls(objWithClosure.prop);
var scndObj = {prop:'Foobar2'};
scndObj.cls = makeClosure;
scndObj.cls = scndObj.cls(anyFunc);
scndObj.cls(scndObj.prop);
In all three cases, this logs as the window object. It's an easy fix, of course:
function makeClosure(func)
{
return function(par)
{
return func.call(this,par);
}
}
This fix works, I put it here to avoid people answering this, without explaining what I need to know: why is this behaving the way it does here?
ensures the caller is effectively the object that the closure belongs to. What I fail to understand is this:
Sure enough, this points to the window object in the first case, but in other cases, it shouldn't. I tried logging this in the makeClosure function just before returning, and it did log the object itself, not the window object. But when the actual closure is used, this is back to pointing to the window object. Why?
The only thing I can think of is that, by passing the anyFunc function as an argument, I'm actually passing window.anyFunc. So I tried this quick fix:
function makeClosure(func)
{
var theFunc = func;
return function(par)
{
theFunc(par);
}
}
With the expected results, this now points to the objects, but again: Why? I have a few idea's (theFunc is a reference to the function in the local scope [this > private: theFunc]?), but I'm sure there are people here with a lot more know-how when it comes to JS, so I was hoping to get some more explanation or links to articles worth reading from them...
Thanks
Update
Here's a fiddle, may be I left something out, but here this logs all sorts of things ;)
Edit/Update 2
The case that confuses me is here.
Final Edit
Ok, This is getting a rather messy post. So to clarify: What I was expecting was a behaviour similar to this:
function makeClosure()
{
function fromThisFunc()
{
console.log(this);
}
return fromThisFunc;
}
var windowContext = makeClosure();
windowContext();
var objectContext = {cls:makeClosure()};
objectContext.cls();
What caught me, was that the function anyFunc wasn't declared within the correct scope, and therefore, this pointed to the window object. I found this out by reading an ancient scroll I found somewhere on the web.
But something a little more complicated has happened because the function object now referred to by globalVar was created with a [[scope]] property referring to a scope chain containing the Activation/Variable object belonging to the execution context in which it was created (and the global object). Now the Activation/Variable object cannot be garbage collected either as the execution of the function object referred to by globalVar will need to add the whole scope chain from its [[scope]] property to the scope of the execution context created for each call to it.
So what I needed to do, was simplify rather then complicate things:
function fromThisFunc()
{
console.log(this);
}
function makeClosure(funcRef)
{
//some code here
return funcRef;
}
That should work, right?
PS: I'll except Alnitak's answer, but special thanks goes to Felix Kling for all the patience and info.
As soon as you call:
return func(par);
You're creating a new scope (with its own this) and in this case because you haven't specified an object, this === window usually or undefined in strict mode. The called function does not inherit whatever this was in the calling scope.
Ways to set a value for this are:
myobj.func(par); // this === myobj
or
func.call(myobj, ...) // this === myobj
There are also:
apply
bind
arrow functions, where this is set to the same value as the outer execution context (also see MDN:Arrow functions )
The value of this depends only on whether you call the function as a method or as a function.
If you call it as a method, this will be the object that the method belongs to:
obj.myFunction();
If you call it as a function, this will be the window object:
myFunction();
Note that even if you are in a method that belongs to an object, you still have to call other methods in the object using the method syntax, otherwise they will be called as functions:
this.myOtherFunction();
If you put a method reference in a variable, you will detach it from the object, and it will be called as a function:
var f = obj.myFunction;
f();
The call and apply methods are used to call a function as a method even if it's not a method in the object (or if it's a method in a different object):
myFunction.call(obj);

Categories

Resources