Pattern to deal with 'this' in JavaScript event handlers - javascript

I'm trying to encapsulate some code to grab and release the onLoad event for a tab in a Firefox Extension such that as necessary, I call:
var onLoad = new MyHandler_onLoad();
And then when I'm done, I call:
onLoad.unregister();
In principle, this code implements the above fine until you delve in to the grittier details.
function bind(scope, fn) {
return function(){
fn.apply(scope, arguments);
};
function MyHandler_onLoad()
{
this.scan = function() {
do_scan(this.browser); // this.browser == undefined
};
this.register = function() {
this.tab.addEventListener("load", this.scan, false);
};
this.unregister = function() {
this.tab.removeEventListener("load", this.scan, false);
};
this.tab = gBrowser.selectedTab;
this.browser = gBrowser.selectedBrowser;
this.register();
window.addEventListener("unload", bind(this, this.unregister), false);
};
Due to the behaviour of JavaScript's this, I'm struggling. I want to be able to access this.browser from my scan function, but can't.
I've used bind to ensure that unregister gains the appropriate context on unload. But, I can't do this with the call to scan as I'll not be able to remove it later if I don't have a name.
Is there a good pattern for doing this sort of thing in JavaScript?
I've tried storing the result of bind(this, this.scan) as a variable in the constructor, but it doesn't help and am now struggling for options.

this, in JavaScript, always points to the current object. If there is no current object, this points to window, which is always the top-level scope (in a browser anyway)
By example:
function(){
...
this.foo = function(){
this;
// There is no object here, so `this` points to `window`
}
}
function foo(){
this;
// again, no object so `this` points to window`
}
foo();
function foo(){
this;
// because initialized with `new`, `this` points to an instance of `foo`
this.bar = function(){
this;
// no object, `this` points to `window`
}
}
var foobar = new foo();
// above is roughly equivalent to: foo.apply({}, arguments); Note the new object
foobar.bar();
var foo = {
bar : function(){
this;
// `this` points at `foo` -- note the object literal
}
};
function foo(){
}
foo.prototype.bar = function(){
this;
// `this` points to the instance of `foo`
}
var foobar = new foo();
foobar.bar();
The concept of binding allows you to lock the this variable to whatever you want since the final call to the function is via .apply(scope, params), so going back to your original question, my last example above will work, so will this:
function foo(){
this.scan = bind(this, function(){
this;
// `this` points to instance of `foo` IF `foo` is instantiated
// with `new` keyword.
});
}
new foo()
If you want to understand all of this more, I have two articles I wrote ages back that should help:
http://www.htmlgoodies.com/primers/jsp/article.php/3600451/Javascript-Basics-Part-8.htm
http://www.htmlgoodies.com/primers/jsp/article.php/3606701/Javascript-Basics-Part-9.htm

function MyHandler_onLoad()
{
var self = this;
Having done this, self will always point to the correct object in your handlers.

Solution: Don't use this.
Here is an alternative way to define MyHandler_onLoad
function MyHandler_onLoad() {
var onload_handler = {
scan: function() {
do_scan(onload_handler.browser); // onload_handler.browser == undefined
},
register = function() {
onload_handler.tab.addEventListener("load", onload_handler.scan, false);
},
unregister = function() {
onload_handler.tab.removeEventListener("load", onload_handler.scan, false);
}
};
onload_handler.tab = gBrowser.selectedTab;
onload_handler.browser = gBrowser.selectedBrowser;
onload_handler.register();
window.addEventListener("unload", bind(onload_handler, onload_handler.unregister), false);
return onload_handler;
}
Even better? Move global dependencies up and no access to tab and browser properties (ie making them 'private')
You could even choose to hide register and unregister functions as I'm not sure you even need them, since it seems to attach itself already.
var handler = MyHandler_onLoad(gBrowser.selectedTab, gBrowser.selectedBrowser);
function MyHandler_onLoad(tab, browser) {
var onload_handler = {
scan: function() {
do_scan(browser); // browser == undefined
},
register = function() {
tab.addEventListener("load", onload_handler.scan, false);
},
unregister = function() {
tab.removeEventListener("load", onload_handler.scan, false);
}
};
onload_handler.register();
window.addEventListener("unload", bind(onload_handler, onload_handler.unregister), false);
return onload_handler;
}
Specifically your problem with this is that it points to the scan function, not your handler object. If you don't use this at all then you will never run into these kinds of bugs.
Oh, and you don't need to use new either.

Related

How Object.prototype and bind() method work together

I'm reading about ngInfiniteScroll, and i'm a newbie about JS.
As I've read the demo of nfInfiniteScroll, it's hard for me to understand why Reddit.nextPage have been transformed into Reddit.prototype.nextPage and it has been used the bind() method to wrap a part of Reddit.prototype.nextPage body.
Here is the code.
myApp.controller('DemoController', function($scope, Reddit) {
$scope.reddit = new Reddit();
});
// Reddit constructor function to encapsulate HTTP and pagination logic
myApp.factory('Reddit', function($http) {
var Reddit = function() {
this.items = [];
this.busy = false;
this.after = '';
};
Reddit.prototype.nextPage = function() {
if (this.busy) return;
this.busy = true;
var url = "https://api.reddit.com/hot?after=" + this.after + "&jsonp=JSON_CALLBACK";
$http.jsonp(url).success(function(data) {
var items = data.data.children;
for (var i = 0; i < items.length; i++) {
this.items.push(items[i].data);
}
this.after = "t3_" + this.items[this.items.length - 1].id;
this.busy = false;
}.bind(this));
};
return Reddit;
});
I've just understood: by using this I can have access to properties in Reddit object.
Is it only because var Reddit is assigned an anonymous function and I need to bind this of the anonymous function to this of Reddit.nextPage, so they refer to the same properties?
But I can crearly see it is possible to have access to those properties even without the bind() method. See:
if (this.busy) return;
this.busy = true;
I've read some articles about the topic, but none exaplains it in depth: I'm really confused about.
Lets look at these functions:
Reddit.prototype.nextPage = function() {
// outer function
...
$http.jsonp(url).success(function(data) {
// inner function
}.bind(this));
};
Without binding, this in inner function would have different properties, since it's in another context. But if we call bind(this) we tell inner function to use this from outer function's context.
For more information I recommend this article.
I haven't visited the blog post, but I'm guessing that the reason it has been moved to be declared on the prototype is to have it automatically included in each instance of your "Reddit" service. Every time your service is created it will include this method, as all prototype methods are automatically inherited.
Regarding the bind, whenever you are passing a function as an argument, when the function is to get executed it will lose the main context, meaning it will not be bound to your Reddit service any more, because it will have a new scope of execution. Therefore calls to this.items, this.busy and this.after would all be undefined and would cause errors.
Here's some more info on the bind(), call() and apply().
this is context dependent. An example:
var foo = {
bar: function() {
console.log(this.baz);
},
baz: 3
};
foo.bar(); // logs 3
But in an asynchronous callback the context is gone, an example using setTimeout:
var foo = {
bar: function() {
setTimeout(function() { console.log(this.baz); }, 0);
},
baz: 3
};
foo.bar(); // logs undefined or throws an error in strict mode
'this' is no longer in the context of foo. We can get around that limitation using bind:
var foo = {
bar: function() {
setTimeout((function() { console.log(this.baz); }).bind(this), 0);
},
baz: 3
};
foo.bar(); // logs 3
We've now bound the context to foo (the value of this at the call site) which is what's going on in your example, the binding of this in the callback passed to the success handler of the promise returned by $http.jsonp.

`this.some_property` becomes undefined inside anonymous callback function

So I can't quite figure out why the variable this.tasks becomes undefined inside of the add event listener I have inside of my goal object. I have a feeling it might have something to do with asynchronous programming(which I still don't fully understand). Sorry I'm a bit of a JS noob, but if you guys could explain to me what I'm doing wrong and what might be a better solution that would be awesome! Thanks.
function Goal(name) {
this.gDiv = document.createElement('div');
this.name = name || "goal";
this.tasks = document.createElement('ul');
//Sets the styling and content and adds it to the parent element
this.initialize = function() {
this.gDiv.className = "default";
this.gDiv.setAttribute("id", this.name);
this.gDiv.innerHTML = this.name;
elem.appendChild(this.gDiv);
this.gDiv.parentNode.insertBefore(this.tasks, this.gDiv.nextSibling);
this.tasks.style.display = "none";
};
//Creates a list underneath the a dive associated with the Goal object
this.addTask = function(task) {
var newLi = document.createElement('li');
newLi.innerHTML = task;
this.tasks.appendChild(newLi);
};
this.gDiv.addEventListener('click', function(){
alert(this.tasks);
});
}
Thank you guys! You all answered my question! I'd been scratching my head at this for a while. Kudos to you all!
The scope changes when you enter that anonymous closure and 'this' changes. You can hack around it by doing
var self = this;
And then using self in place of this (eg):
function Goal(name) {
var self = this;
/* ... */
this.gDiv.addEventListener('click', function(){
alert(self.tasks);
});
If you're using jQuery you could do something nicer:
this.gDiv.addEventListener('click', $.proxy(function() {
alert(this.tasks);
}, this));
Either way works just fine.
EDIT: In ES6, arrow functions can be used instead as they don't bind their own "this", so it becomes even simpler:
this.gDiv.addEventListener('click', () => {
alert(this.tasks);
});
Here is a comparison of some methods (including your problem), to give you a taster, and to try and explain things a little.
// This is the problem that you have,
// where `this` inside the anonymous function
// is a different scope to it's parent
function Test1(something) {
// `this` here refers to Test1's scope
this.something = something;
setTimeout(function() {
// `this` here refers to the anonymous function's scope
// `this.something` is `undefined` here
console.log(this.something);
}, 1000);
};
new Test1('Hello');
// This solution captures the parent `this` as `test2This`,
// which can then be used inside the anonymous function
function Test2(something) {
var test2This = this;
this.something = something;
setTimeout(function() {
console.log(test2This.something);
}, 1000);
}
new Test2('World');
// This solution captures `this` as `test3This` in an `IIFE closure`
// which can then be used in the anonymous function
// but is not available outside of the `IIFE closure` scope
function Test3(something) {
this.something = something;
(function(test3This) {
setTimeout(function() {
console.log(test3This.something);
}, 1000);
}(this));
}
new Test3('Goodbye');
// This method requires that you load an external library: jQuery
// and then use it's `$.proxy` method to achieve the basics of
// Test3 but instead of being referred to as `test3This` the
// outer scope `this` becomes the inner scope `this`
// Ahh, that's much clearer?
function Test4(something) {
this.something = something;
setTimeout($.proxy(function() {
console.log(this.something);
}, this), 1000);
}
new Test4('Mum');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
// This is approximately what jQuery's `$.proxy` does
// but without having to load the whole library
function Test5(something) {
this.something = something;
setTimeout((function(func, context) {
return function() {
func.call(context);
};
}(function() {
console.log(this.something);
}, this)), 1000);
}
new Test5('Dad');
// Lets create the proxy method as a reuseable
function proxy(func, context) {
var args = Array.prototype.slice.call(arguments, 2);
return function() {
return func.apply(
context,
args.concat(Array.prototype.slice.call(arguments))
);
};
}
// and now using it
function Test6(something) {
this.something = something;
setTimeout(proxy(function() {
console.log(this.something);
}, this), 1000);
}
new Test6('Me want cookies');
Then we have Function#bind
function Test7(something) {
this.something = something;
setTimeout(function() {
// `this` was bound to the parent's `this` using bind
console.log(this.something);
}.bind(this), 1000);
};
new Test7('Num num');
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.9/es5-shim.min.js"></script>
And most recently ES2015 Arrow functions
function Test8(something) {
this.something = something;
setTimeout(() => console.log(this.something), 1000);
};
new Test8('Whoop');
In ES6, arrow functions were introduced, which do not bind their own this.
MDN for reference.
So creating an anonymous function using the arrow syntax is probably the easiest way to overcome this issue nowadays. It is supported by all major browsers currently, except IE.
the keyword 'this' changes in it's meaning for an event handler against a constructor
please refer to the MDN
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#As_a_DOM_event_handler

Recursive timeout loses visibility to itself

I have defined an API object:
function API() {
var self = this;
return {
getRandomArticle: function() {
$.getJSON("http://en.wikipedia.org/w/api.php?action=query&generator=random&grnnamespace=0&prop=extracts&exchars=50000&format=json&callback=?", function (data) {
for(var id in data.query.pages) {
console.log(data.query.pages[id].extract);
}
});
},
repeatAPICall: function() {
self.getRandomArticle();
console.log(self);
setTimeout(self.repeatAPICall, 5000);
}
}
}
And then I instantiated the API object with window.test = new API();.
When I head over to Chrome Dev tools and call window.test.repeatAPICall(), it works once, then it fails and says TypeError: Object #<API> has no method 'getRandomArticle'
I suspect that somehow the recursive call is behaving differently than I intended, what am I doing wrong?
Working code:
function API() {
var self = this;
self.getRandomArticle = function() {
$.getJSON("http://en.wikipedia.org/w/api.php?action=query&generator=random&grnnamespace=0&prop=extracts&exchars=50000&format=json&callback=?", function (data) {
for(var id in data.query.pages) {
console.log(data.query.pages[id].extract);
}
});
},
self.repeatAPICall = function() {
self.getRandomArticle();
console.log(self);
setTimeout(self.repeatAPICall, 5000);
}
return this;
}
window.test = new API();
Now you've fixed "self" vs. "this" the next change is to use
self.getRandomArticle= ...
self.repeatAPICall=...
and then just return self/this. That should work. Right now, you have two objects - this and the one you return.
Your main issue is the passing of this.repeatAPICall into setTimeout. When you call a method in JavaScript, the this keyword points to the object that called it:
var something = {
foo : function(){
return this;
}
};
something.foo(); //=> something
However, if you assign the function to a different variable, the context changes (to the global window object):
var something = {
foo : function(){
return this;
}
};
something.foo(); //=> something
var bar = something.foo;
bar(); //=> window
This is what's happening above; you're passing a reference to the function to setTimeout, which is then losing the correct context.
Instead, you need to pass in a function which keeps the context; you could use the self = this statement like so:
repeatAPICall: function() {
self = this;
self.getRandomArticle();
setTimeout(function(){
self.repeatAPICall();
}, 5000);
This creates anonymous function which remembers the state of the self object (this is how JavaScript variable scope works). When that function gets called, it can then call repeatAPICall as a method on that object, rather than as a function with no context.
The accepted answer avoids having to do this (each method can access self), but hopefully this explains why it wasn't working.

Is it possible to modify a function itself when its property function is called?

Basically I want to do this:
someFunction() // do something
someFunction.somePropertyFunction()
someFunction() // Now someFunction is modified; it should now exhibit a different behaviour
Is this possible?
EDIT:
I'm not looking for what #Kolink was suggesting. Basically I want to augment a function's functionality by calling one of it's property function.
Specifically, I need to: 1. have access to the original function inside my property function (which is entirely doable using this), and 2. bind a new function to the original function's name (which I'm not sure if it's possible).
Just to be clear, I don't have access to the internal definition of the function that I want to augment. I want to attach a function to Function.prototype (so that it will be available as a property of the function that I want to augment), and then I will call func.augmentThis(), and then func should be augmented. But I'm not sure how, hence the question :P
Easily. Here's an example:
var derp = 123;
someFunction = function() {alert(derp);};
someFunction.somePropertyFunction = function() {derp = 456;};
someFunction(); // alerts 123
someFunction.somePropertyFunction();
someFunction(); // alerts 456
Okay, that's an oversimplified example, but yeah, it's entirely possible.
If your question is whether a function attached as a property to another function has a way to access the function to which it is attached, the answer is no. After all, the same function could be attached to any number of functions of objects.
So one alternative is to explicitly refer to the "mother" function within the function that is attached to it and intended to change its behavior:
function f (n) { alert (n + f.offset); }
f.offset = 0;
f.change_offset = function (i) { f.offset = i; };
f (1); //1
f.change_offset (100);
f (1); //101
Here, f is hard-wired into the definition of change_offset. If this bothers you, or you want something slightly more general, write a little routine to set a function as a property on another function, while binding its this to the function being attached to:
function set_func_as_func_prop ( propname, func_to_set, func_to_set_on ) {
func_to_set_on[propname] = func_to_set.bind(func_to_set_on);
}
Now you can write the function more generally
function change_offset (i) {
this.offset = i;
}
and set it on f or any other function.
set_func_as_func_prop ("change_offset", change_offset, f);
set_func_as_func_prop ("change_offset", change_offset, g);
Sort of:
function someFunction() {
return realFunction.apply(this, arguments);
}
function someFunctionA(name) {
return 'Hello, ' + name + '!';
}
function someFunctionB(name) {
return 'Goodbye, ' + name + '...';
}
var realFunction = someFunctionA;
someFunction.somePropertyFunction = function () {
realFunction = someFunctionB;
};
Sure it's possible. It's not recommended, but it's possible. For example:
function a() {
alert("a");
}
function b() {
alert("b");
}
function c() {
return c.f.apply(this, arguments);
}
c.f = a;
c.toggle = function () {
c.f = c.f === a ? b : a;
};
Now let's test it:
c(); // alerts "a"
c.toggle();
c(); // alerts "b"
See the demo: http://jsfiddle.net/LwKM3/
I want to attach a function to Function.prototype. Then I need to bind a new function to the original function's name (which I'm not sure if it's possible).
That indeed is impossible, you don't know what refers to the function. And you cannot change the internal representation of a function, which is immutable.
The only thing you can do is to create a new function and return that, to let the caller of your method use it somehow - specifically assigning it to the original variable:
somefunction = somefunction.augmentSomehow();
Your method for that will look like this:
Function.prototype.augmentSomehow = function() {
var origFn = this;
return function() {
// in here, do something special
// which might include invoking origFn() in a different way
};
};
Not sure if this helps, but I would implement described problem in following way:
// defined by somebody else - unknown to developer
var someFunction = function() {
alert("this is initial behavior");
}
someFunction(); // returns "this is initial behavior"
// defines parent object on which someFunction() is called
var parentObject = this; // returns window object (as called direclty in the
// browser)
// if you are calling someFunction from some object (object.someFunction())
// it would be:
// var parentObject = object;
// augumentThis definition
someFunction.augumentThis = function() {
var newFunction = function() {
alert("this is changed behavior");
};
parentObject.someFunction.somePropertyFunction = function() {
parentObject.someFunction = newFunction;
parentObject.someFunction();
};
};
someFunction.augumentThis(); // change function behavior
someFunction(); // "this is initial behavior"
someFunction.somePropertyFunction(); // "this is changed behavior"
someFunction(); // "this is changed behavior"

Unable to access the object using `this`. `this` points to `window` object

I have this Javascript constructor-
function TestEngine() {
this.id='Foo';
}
TestEngine.prototype.fooBar = function() {
this.id='bar';
return true;
}
TestEngine.prototype.start = function() {
this.fooBar();
}
TestEngine.prototype.startMethod = function() {
inter = setInterval(this.start, 200);
}
var test = new TestEngine();
test.startMethod();
Gives me this error -
Uncaught TypeError: Object [object global] has no method 'fooBar'
I tried console.log and found out that when I call this.start from within setInterval, this points to the window object. Why is this so?
The this pointer can point to one of many things depending upon the context:
In constructor functions (function calls preceded by new) this points to the newly created instance of the constructor.
When a function is called as a method of an object (e.g. obj.funct()) then the this pointer inside the function points to the object.
You can explicitly set what this points to by using call, apply or bind.
If none of the above then the this pointer points to the global object by default. In browsers this is the window object.
In your case you're calling this.start inside setInterval. Now consider this dummy implementation of setInterval:
function setInterval(funct, delay) {
// native code
}
It's important to understand that start is not being called as this.start. It's being called as funct. It's like doing something like this:
var funct = this.start;
funct();
Now both these functions would normally execute the same, but there's one tiny problem - the this pointer points to the global object in the second case while it points to the current this in the first.
An important distinction to make is that we're talking about the this pointer inside start. Consider:
this.start(); // this inside start points to this
var funct = this.start;
funct(); // this inside funct (start) point to window
This is not a bug. This is the way JavaScript works. When you call a function as a method of an object (see my second point above) the this pointer inside the function points to that object.
In the second case since funct is not being called as a method of an object the fourth rule is applied by default. Hence this points to window.
You can solve this problem by binding start to the current this pointer and then passing it to setInterval as follows:
setInterval(this.start.bind(this), 200);
That's it. Hope this explanation helped you understand a little bit more about the awesomeness of JavaScript.
Here is a neat way to do OOP with javascript:
//Global Namespace:
var MyNamespace = MyNamespace || {};
//Classes:
MyNamespace.MyObject = function () {
this.PublicVar = 'public'; //Public variable
var _privatVar = 'private'; //Private variable
//Public methods:
this.PublicMethod = function () {
}
//Private methods:
function PrivateMethod() {
}
}
//USAGE EXAMPLE:
var myObj = new MyNamespace.MyObject();
myObj.PublicMethod();
This way you encapsulate your methods and variables into a namespace/class to make it much easier use and maintain.
Therefore you could write your code like this:
var MyNamespace = MyNamespace || {};
//Class: TestEngine
MyNamespace.TestEngine = function () {
this.ID = null;
var _inter = null;
//Public methods:
this.StartMethod = function (id) {
this.ID = id;
_inter = setInterval(Start, 1000);
}
//Private methods:
function Start() {
FooBar();
console.log(this.ID);
}
function FooBar() {
this.ID = 'bar';
return true;
}
}
//USAGE EXAMPLE:
var testEngine = new MyNamespace.TestEngine();
testEngine.StartMethod('Foo');
console.log(testEngine.ID);
Initially, the ID is set to 'Foo'
After 1 second the ID is set to 'bar'
Notice all variables and methods are encapsulated inside the TestEngine class.
Try this:
function TestEngine() {
this.id='Foo';
}
TestEngine.prototype.fooBar = function() {
this.id='bar';
return true;
}
TestEngine.prototype.start = function() {
this.fooBar();
}
TestEngine.prototype.startMethod = function() {
var self = this;
var inter = setInterval(function() {
self.start();
}, 200);
}
var test = new TestEngine();
test.startMethod();
setInterval calls start function with window context. It means when start gets executed, this inside start function points to window object. And window object don't have any method called fooBar & you get the error.
Anonymous function approach:
It is a good practice to pass anonymous function to setInterval and call your function from it. This will be useful if your function makes use of this.
What I did is, created a temp variable self & assigned this to it when it is pointing your TestEngine instance & calling self.start() function with it.
Now inside start function, this will be pointing to your testInstance & everything will work as expected.
Bind approach:
Bind will make your life easier & also increase readability of your code.
TestEngine.prototype.startMethod = function() {
setInterval(this.start.bind(this), 200);
}

Categories

Resources