I'm building a jQuery app using OOP principles and I'm trying to implement a externally added callback function which invokes a method from inside of my object.
function testObject() {
var self = this;
var functions = new Array();
this.updateObject = function() {
console.log('updated')
}
this.addFunction = function(func) {
functions.push(func)
}
this.callFunctions = function() {
$.each(functions, function(key, value) {
functions[key]()
})
}
}
var myobject = new testObject();
myobject.addFunction(
function() {
$(':text').on('change', function() {
return self.updateObject();
})
}
)
This is an overly simplified version of the plugin I'm building. The callback works fine, but I cannot use the self.updateObject(); inside of it, since it outputs Illegal Invocation.
How can I call a method from inside the callback properly?
The problem is self is out of scope of the callback function, because the function only has variables in the scope of where it was defined. The callback is defined outside of the testObject.
A solution is to bind the this context in the callback function to self using Function.prototype.call(self), when you call it in callFunctions(). Then in the callback, you can use this to refer to the testObject instance. In your callback example it contains a jQuery event so you will lose the this context. To rectify that you can create a local self that equals this before the jQuery change event.
function testObject() {
var self = this;
var functions = new Array();
this.updateObject = function() {
console.log('updated')
}
this.addFunction = function(func) {
functions.push(func)
}
this.callFunctions = function() {
$.each(functions, function(key, value) {
functions[key].call(self); // call it and bind the context to self
})
}
}
var myobject = new testObject();
myobject.addFunction(
function() {
var self = this; // needed because the change event will overwrite 'this'
$(':text').on('change', function() {
return self.updateObject(); // use self to access testObject
})
}
)
myobject.callFunctions();
Alternatively you can pass self as an argument to the callback. To do that, change the .call() line to:
functions[key].call(null, self);
and change the callback to accept an argument like so:
myobject.addFunction(
function(self) { // self as an argument
$(':text').on('change', function() {
return self.updateObject(); // use self to refer to testObject
})
}
)
function testObject() {
var self = this;
var functions = new Array();
this.updateObject = function() {
console.log('updated')
}
this.addFunction = function(func) {
functions.push(func.bind(self)) // Bind the context
}
this.callFunctions = function() {
$.each(functions, function(key, value) {
functions[key]()
})
}
}
var myobject = new testObject();
myobject.addFunction(
function() {
var self = this;
$(':text').on('change', function() {
return self.updateObject();
})
}
)
Or you can use this as well:
myobject.addFunction(
function() {
$(':text').on('change', this.updateObject);
}
)
Related
Let's assume we have the following code:
var MyClass = (function(){
var _this;
function MyClass(inputVal){
_this = this;
this.value = inputVal;
}
MyClass.prototype.getValue = function(){
return this.value;
}
MyClass.prototype.getValue2 = function(){
return _this.value;
}
return MyClass;
})();
Let's make two instances of the class:
var instance1 = new MyClass(10);
var instance2 = new MyClass(20);
Now if we console.log() the values we see that:
instance1.getValue(); // 10
instance1.getValue2(); // 20
var MyClass = (function(){
var _this;
function MyClass(inputVal){
_this = this;
this.value = inputVal;
}
MyClass.prototype.getValue = function(){
return this.value;
}
MyClass.prototype.getValue2 = function(){
return _this.value;
}
return MyClass;
})();
var instance1 = new MyClass(10);
var instance2 = new MyClass(20);
console.log(instance1.getValue());
console.log(instance1.getValue2());
Why is that happening? It looks obviously that the _this variable gets the latest created instance properties. How to fix that? I need to keep a copy of this. Thanks!
Edit:
Here's the real situation
var HoverEffects = (function(){
var _this;
function HoverEffects($nav){
_this = this;
this._$activeNav = $nav.siblings('.active_nav');
this._$hoverableLis = $nav.find('>li');
this._$activeLi = $nav.find('>li.active');
if(!$nav.length || !this._$hoverableLis.length || !this._$activeNav.length || !this._$activeLi.length) return;
if(this._$activeNav.hasClass('bottom')){
this._$activeNav.align = 'bottom';
this._$activeLi.cssDefault = {
left: this._$activeLi.position().left,
width: this._$activeLi.width()
};
}
else if(this._$activeNav.hasClass('left')){
this._$activeNav.align = 'left';
this._$activeLi.cssDefault = {
top: this._$activeLi.position().top,
height: this._$activeLi.height()
};
}
else{
return;
}
this._$hoverableLis.hover(
function(){
// How to set the correct this inside this function?
if(this._$activeNav.align === 'bottom'){
this._$activeNav.css({
left: $(this).position().left,
width: $(this).width()
});
}
else if(this._$activeNav.align === 'left'){
this._$activeNav.css({
top: $(this).position().top,
height: $(this).height()
});
}
},
function(){
// Same here, wrong this
this._$activeNav.css(this._$activeLi.cssDefault);
}
);
}
return HoverEffects;
})();
var sideNavHoverMagic = new HoverEffects($('#side-navigation'));
var primaryNavHoverMagic = new HoverEffects($('#primary-navigation'));
Why is that happening?
Every time you call new MyClass, _this = this gets run. The second time overrides the first time.
So _this refers to new MyClass(20), which means that when you call getValue2 from any MyClass instance, 20 will be returned because all MyClass instances are referring to the same _this value.
Based on commentary on the Question:
If you're attempting to pass a function bound to the appropriate context there are a variety of ways to make sure that this refers to the right object. Before continuing, please read "How does the 'this' keyword work?", because there's no reason for me to repeat all of it here.
If you're binding event callbacks such as in a constructor:
function Example(something) {
something.addEventListener(..event.., this.callback, false);
}
Example.prototype.callback = function () {
this.doStuff();
this.doMoreStuff();
};
The callback will have the wrong this value because it's not being called as this.callback, it's just being called as:
fn = this.callback;
fn(); //no reference to this
You can get around this in a number of ways.
Function.prototype.bind
You can bind the callback for every instance on their respective instance. This is very concise:
function Example(something) {
//generate a new callback function for each instance that will
//always use its respective instance
this.callback = this.callback.bind(this);
something.addEventListener(..event.., this.callback, false);
}
Example.prototype.callback = function () {
this.doStuff();
this.doMoreStuff();
};
that = this
You can create the callback (closure) within the constructor and reference a variable inside the constructor.
function Example(something) {
//every Example object has its own internal "that" object
var that = this;
this.callback = function () {
//this function closes over "that"
//every instance will have its own function rather than
//a shared prototype function.
that.doStuff();
that.doMoreStuff();
}
something.addEventListener(..event.., this.callback, false);
}
() => {} (Fat Arrow Syntax)
If you're using ES2015 you can use "fat arrow" syntax for creating lambdas that don't create a new context:
function Example(something) {
this.callback = () => {
//the callback function doesn't create a new "this" context
//so it referes to the "this" value from "Example"
//every instance will have its own function rather than
//a shared prototype function.
that.doStuff();
that.doMoreStuff();
}
something.addEventListener(..event.., this.callback, false);
}
If you have to use var self=this for methods on the prototype object. Will you write var self=this inside each method?
Is there any way to avoid writing var self =this inside all methods?
function Test () {
}
Test.prototype.method1 = function () {
var self = this;
};
Test.prototype.method2 = function () {
var self = this;
};
You only need to create an alias for the this context if you are going to be introducing new contexts with function literals that still need to access the class's this context.
function Test () {}
Test.prototype.method1 = function () {
// This is fine.
this.someProperty = 123;
this.doSomething();
};
Test.prototype.method2 = function () {
var self = this;
var callback = function() {
// The `this` here is no longer the same `this` as outside the function.
self.someProperty = 456;
};
var anotherCallback = function() {
// No reference to the class's `this` needed here.
alert('Boo');
};
this.doAsyncThing(callback);
};
Alternatively, you can use Function.prototype.bind to force a this context:
var callback = function() {
this.someProperty = 456;
}.bind(this);
In JavaScript I am looking for a way to use bind() on an anonymous and async function.
Example:
exports.foo = function () {};
exports.foo.prototype = {
load : function(id) {
var query = new Parse.Query("SomeObject");
query.get(id).then(function(object) {
this.object = object; // this is the wrong this
});
}
};
I got this working by making the functions non-anonymous, but I think it makes my code look ugly. Especially after having 4 different anonymous functions in a row.
exports.foo = function () {};
exports.foo.prototype = {
load : function(id) {
function _load(object) {
this.object = object;
}
var _loadThis = _load.bind(this);
var query = new Parse.Query("SomeObject");
query.get(id).then(_loadThis);
}
};
Is there a better way?
Well it's not necessarily "better", but you can call .bind() directly after the closing brace of your function instantiation expression:
query.get(id).then(function(object) {
this.object = object; // this is the wrong this
}.bind(this));
A function instantiation expression gives you a function object reference, so putting a . after it and calling bind makes sense. What gets passed to the .then function, therefore, is the return value from the call to .bind.
This syntax is not correct:
exports.foo.prototype = {
load = function(id) {
var query = new Parse.Query("SomeObject");
query.get(id).then(function(object) {
this.object = object; // this is the wrong this
});
}
};
The prototype is an object who's properties are defined as load: function() {}, not load = function() {}.
It should be:
exports.foo.prototype = {
load: function(id) {
var query = new Parse.Query("SomeObject");
query.get(id).then(function(object) {
this.object = object; // this is the wrong this
});
}
};
A simple way to do it is to declare a variable to the correct 'this' and use closuers to keep a reference to it.
exports.foo = function () {};
exports.foo.prototype = {
load : function(id) {
var self = this;
var query = new Parse.Query("SomeObject");
query.get(id).then(function(object) {
self.object = object; // this is the wrong this
});
}
};
I have the following code
var PROMO = PROMO || {};
PROMO.Base = (function () {
var _self = this;
var Init = function () {
WireEvents();
};
var WireEvents = function () {
//wire up events
};
} ());
In the same file I have the code to call the above function
I am trying to get to an end point where I can use the following code
$(document).ready(function () {
PROMO.Base.Init();
});
this gives the error
Cannot call method 'Init' of undefined
Now I know there are many ways to write javascript, but in this case I want to be able to call my functions, or least the Init method in the way shown above.
var PROMO = PROMO || {};
PROMO.Base = (function () {
var _self = this;
var Init = function () {
WireEvents();
};
var WireEvents = function () {
//wire up events
};
var reveal = {
Init: Init
};
return reveal;
} ());
You need to return the public facing functions. See updated code.
Working fiddle with both patterns, using IIFE and direct attribution.
Using var makes the definition private and your function is returning nothing. Use this:
PROMO.Base = {
Init: function() {
},
WireEvents: function() {
};
};
You are wrapping the definition with an IIFE(Immediately Executed Function Expression). So your PROMO.Base object will be assigned the value of that (function(){//blabla})(); returns. But your function doesn't have a return statement. By default it will return undefined.
Which is way your PROMO.Base will be undefined and you get this:
Cannot call method 'Init' of undefined
If you really want that IIFE:
var PROMO = PROMO || {};
// NEVER use _self = this inside static functions, it's very dangerous.
// Can also be very misleading, since the this object doesn't point to the same reference.
// It can be easily changed with Function.prototype.call and Function.prototype.apply
PROMO.Base = (function () {
_PROMO = {
Init : function () {
document.body.innerHTML += "itworks";
},
WireEvents : function () {
//wire up events
}
}
return _PROMO;
} ());
PROMO.Base.Init();
Update
The better and easier pattern is to simply assign the functions to PROMO.Base. Dully note you should not capitalize static functions, but only constructors. So if something is not meant to be instantiated, don't call it Init, it should be init. That is the convention.
var PROMO = {};
PROMO.Base = {};
PROMO.Base.init = function() {
console.log("this works");
};
PROMO.Base.wireEvents = function() {
console.log("this is a static function too");
};
You can attach it to the window object like ...
window.PROMO = (function($, _){
// this will access PROMO.Base
PROMO.Base = {
// inner functions here
Init:{}
};
})(jQuery, _);
Then load it as you do.
Or if you depend from jQuery
(function($){
var PROMO = {
// inner functions
Init: function(){},
WireEvents: function(){}
};
$.PROMO = PROMO;
})(jQuery);
On DOM ready
jQuery(function ($) {
var promo = $.PROMO || undefined;
promo.Base.Init();
});
In the SomeObj object, the onkeydown event handler this.doSomething is called in the wrong context (that of the textbox element) but it needs to be called in the context of this. How can this be done?
function SomeObj(elem1, elem2) {
this.textboxElem = elem1;
this.someElem = elem2;
this.registerEvent();
}
SomeObj.prototype = {
registerEvent: function() {
this.textboxElem.onkeydown = this.doSomething;
},
doSomething: function() {
// this must not be textboxElem
alert(this);
this.someElem.innerHTML = "123";
}
};
Copy the reference to a local variable, so that you can use it in a closure:
registerEvent: function() {
var t = this;
this.textboxElem.onkeydown = function() {
t.doSomething();
};
},