Why does JS report 'this.function' as not being a function? - javascript

I wonder if somebody could please help me understand something that seems odd with JS?
The below code works. The function inlineEditEvent.init() is called, and then t.copy() is called correctly (where var t = this;).
However, if I was to replace that with this.copy(), I get the error this.copy is not a function.
What's the difference here? Why does the below work, but not the way as described in the last paragraph? Thanks.
jQuery(function($){
$(document).ready(function(){inlineEditEvent.init();});
inlineEditEvent = {
init : function(){
var t = this;
/** Copy the row on click */
$('#the-list').on('click', '.row-actions a.single-copy', function(){
return t.copy();
});
}, // init
copy : function(){
// Do stuff here
}
} // inlineEditEvent
});

You're setting t as a context variable of this (of your init function). Once inside your click handler, this is now referring to the click handler, no longer the init function. Therefore, this.copy() is not a function.

this refers to this within the functions scope. That's why you need to set a self variable, so it's accessible within the scope of the function. Considering you're using jQuery, you could use $.proxy:
$.proxy(function(){
return this.copy();
},this)

t.copy(); appears in a different function to var t = this;. The value of this changes inside each function.

When you say var t= this; it refers to what this meant in that context. Later on when you are trying to refer to this, it is referring to a.single-copy instead since that is the new context it is in.

Related

Type error: "Not a function" using "this" keyword in a function

I have created the following function.
function showAllSelectOpts(select)
{
selectLength = select.children().length;
select.attr('size',selectLength);
select.css('height','auto');
select.focusout(function(){
select.attr('size','1');
});
}
When it is called directly on a select element like this showAllSelectOpts(mySelect); it works fine, but when called within another function, as below using the keyword "this", it returns the error. Type error: select.children not a function
$('select').on('focus',function(){
showAllSelectOpts(this);
})
Is this a scope issue or what, and how can I resolve it?
In an event handler, this is a reference to the DOM element, not a jQuery object. But your showAllSelectOpts expects its argument to be a jQuery object.
Either change the call to wrap the DOM element with $():
showAllSelectOpts($(this));
...or update showAllSelectOpts to do so itself:
function showAllSelectOpts(select)
{
select = $(select); // ***
selectLength = select.children().length;
select.attr('size',selectLength);
select.css('height','auto');
select.focusout(function(){
select.attr('size','1');
});
}
Side note: As A.Wolff points out, your function attaches a new focusout handler to the select every time it's called. You only want one.
I'd remove that part of the handler entirely, and replace it with a single focusout:
function showAllSelectOpts(select)
{
var selectLength = select.children().length;
select.attr('size',selectLength);
select.css('height','auto');
}
$('select')
.on('focus',function(){
showAllSelectOpts($(this));
})
.on('focusout', function(){
$(this).attr('size', '1');
});
Also note that I added a var for selectLength in showAllSelectOpts (although actually, you could just remove the variable entirely); without one, the code is falling prey to The Horror of Implicit Globals (that's a post on my anemic little blog). Be sure to declare your variables.
jQuery event listener callbacks set this as the HTMLElement that the event was fired on.
In your callback you are expecting a jQuery object, but you have the HTMLElement.
You can pass the HTMLElement to a jQuery constructor and pass it into the showAllSelectOpts function
$('select').on('focus',function(){
showAllSelectOpts($(this));
})
Try this one -
$('select').on('focus',function() {
showAllSelectOpts($(this)); })
Try this:
var myselect = $('select');
$('select').on('focus',function(){
showAllSelectOpts(myselect);
})
A better way could be:
$('select').on('focus',function(event){
showAllSelectOpts($(event.target));
})
Why your code not working?
$('select').on('focus',function(){
//Here `this` is bound with the dom html element, not the $('select') object.
showAllSelectOpts(this);
})
These previous answers fix it. I'd just add here to create it as an extension since $(this) refers to a prototype of one method call.
$.fn.showAllSelectOpts=function() {
$(this).on('focus',()=>{
$(this)
.css('height','auto')
.attr('size',$(this).children().length)
.focusout(()=>{
$(this).attr('size','1');
});
});
};
$('select').showAllSelectOpts();

Javascript function assigned to object cannot access inherited method

I'm no doubt doing something dumb here, but the following code results in an
error "this.getString is not a function."
This occurs because when unrelatedInstance calls stringGetter, "this" in showCombinedStrings() has the value of Unrelated....which actually seems fair enough, but how could this be set up so that it would work?
function BaseStringGetter() {
this.getString = function () {
return 'this is from BaseStringGetter';
}
}
function DerivedStringGetter() {
this.showCombinedStrings = function () {
console.log( 'this is from DerivedStringGetter and... ' + this.getString() );
}
}
DerivedStringGetter.prototype = new BaseStringGetter();
var stringGetterInstance = new DerivedStringGetter();
function Unrelated() {};
var unrelatedInstance = new Unrelated();
unrelatedInstance.stringGetter = stringGetterInstance.showCombinedStrings;
unrelatedInstance.stringGetter();
One option is this:
unrelatedInstance.stringGetter =
stringGetterInstance.showCombinedStrings.bind(stringGetterInstance);
unrelatedInstance.stringGetter();
Here, you're using Function.prototype.bind() to make this inside of unrelatedInstance.stringGetter() always refer back to stringGetterInstance.
The problem is how you are calling unrelatedInstance.stringGetter();
Even though stringGetter refers to the showCombinedStrings function, this inside showCombinedStrings now refers to the unrelatedInstance instance which does not have the toString() property that is why the error.
Demo: Fiddle
Here the value of this is printed as Unrelated {stringGetter: function} which is not an DerivedStringGetter instance
One easy solution is to use .bind() to give a custom execution context to unrelatedInstance.stringGetter like
unrelatedInstance.stringGetter = stringGetterInstance.showCombinedStrings.bind(stringGetterInstance);
Demo: Fiddle
Now even if you call unrelatedInstance.stringGetter(), this inside showCombinedStrings will refer to the stringGetterInstance instance.
When you call a function on an object, this will refer to the object the function is invoked on even if the function was originally defined elsewhere.
When you call unrelatedInstance.stringGetter();, this inside the function will now refer to unrelatedInstance which doesn't have getString(). The MDN this reference page has more info.
You could do something like this to preserve the original context:
unrelatedInstance.stringGetter = function() {
return stringGetterInstance.showCombinedStrings();
}
Edit: I left out bind that the other answers have now mentioned since it doesn't exist in IE8 but you'd be fine using that if you have a shim or don't care about old browsers.

Maintaining Scope

I have a general question about maintaining the scope of this in an object. Here's a simplified snippet of code. Take note of the var that = this line and inside the event handler where I call that.showMenu().
var MyObj = {
init : function(target){
this.$Target = $(target);
this.$Menu = $(target).find('.menu');
this.eventBindings();
},
eventBindings : function(){
var that = this;
this.$Target.on('click', '.anchor', function(e){
e.preventDefault();
that.showMenu();
//some other code
});
},
showMenu : function(){
this.$Menu.show();
}
};
MyObj.init('.myTarget')
Code should be somewhat self explanatory. Typically I try to create reusable methods outside of my eventBindings(). The problem I continually run into is passing through this which would refer to MyObj into the event handler so I can call this.showMenu().
To overcome the obstacle I always assign this to a variable called that so when I'm further down the scope I have a reference. But I feel like this can't be the best method... can someone suggest a better alternative?
What you are doing is the best method. In Javascript this is scoped dynamically (binding depends on stack, that is on the place from where the function was called), all the other variables - are scoped statically (binding depends on static placement of variable in your code).
And since Javascript treats this in a special way, don't feel bad about treating it in a special way too.
You could encapsulate what you are doing now in a function - many libraries provide such a facility, it is usually called "bind". But this will not change what really happens, only hide it (and use up some resources while doing this). Such function could look a bit like:
function whatever (functionToProcess, thisToFreeze) {
return function() {
functionToProcess.apply(thisToFreeze, arguments); // apply is built in
}
}
Further to #Pointy's comment, using Function.prototype.bind:
onClick: function(e){
e.preventDefault();
this.showMenu();
//some other code
},
eventBindings : function(){
this.$Target.on('click', '.anchor', this.onClick.bind(this));
},
You could pass it in as event data:
this.$Target.on('click', '.anchor', {obj:this}, function(e){
e.preventDefault();
e.data.obj.showMenu();
//some other code
});
or store it on the element, but neither are really any different that declaring another variable outside like you are.

Why does "this.myFunction" not work when calling a function inside an object?

Here are two samples of code. The first one does not work and the second one does, though I'm completely at a loss as to why. Can someone explain this?
[I'm writing a simple game using a bit of jQuery to be played in a webkit browser (packaged with Titanium later).]
In the first example, Firebug tells me that "this.checkCloud" is not a function.
function Cloud(){
this.checkCloud = function(){
alert('test');
}
$("#"+this.cloudName).click(function(){
this.checkCloud();
});
}
...but then this works:
function Cloud(){
this.checkCloud = function(){
alert('test');
}
var _this = this;
$("#"+this.cloudName).click(function(){
_this.checkCloud();
});
}
This one works perfect.
Why does the first one not work? Is it because "this.checkCloud" is inside of the anonymous function?
in this example:
$("#"+this.cloudName).click(function(){
this.checkCloud();
});
this referrers to the element selected(jquery object).
what you can do is use private functions
var checkCloud = function(){
alert('test');
}
this way you can simply call it inside your anonymous function
$("#"+this.cloudName).click(function(){
checkCloud();
});
That is because the meaning of this can potentially change each time you create a new scope via a function. The meaning of this depends on how the function is invoked (and the rules can be insanely complicated). As you discovered, the easy solution is to create a second variable to which you save this in the scope where this has the expected/desired value, and then reuse the variable rather than this to refer to the same object in new function scopes where this could be different.
Try this:
function Cloud(){
this.checkCloud = function(){
alert('test');
}
var func = this.checkCloud;
$("#" + this.cloudName).click(function(){
func();
});
}
When you assign an even listener to an element, jQuery makes sure that this will refer to the element. But when you create the _this variable, you're creating a closure that jQuery couldn't mess with, even if it wanted to.

Javascript: Access the right scope "under" apply(...)

This is a very old problem, but I cannot seem to get my head around the other solutions presented here.
I have an object
function ObjA() {
var a = 1;
this.methodA = function() {
alert(a);
}
}
which is instantiated like
var myObjA = new ObjA();
Later on, I assign my methodA as a handler function in an external Javascript Framework, which invokes it using the apply(...) method.
When the external framework executes my methodA, this belongs to the framework function invoking my method.
Since I cannot change how my method is called, how do I regain access to the private variable a?
My research tells me, that closures might be what I'm looking for.
You already have a closure. When methodA is called the access to a will work fine.
Object properties are a different thing to scopes. You're using scopes to implement something that behaves a bit like ‘private members’ in other languages, but a is a local variable in the parent scope, and not a member of myObjA (private or otherwise). Having a function like methodA retain access to the variables in its parent scope is what a ‘closure’ means.
Which scopes you can access is fixed: you can always access variables in your parent scopes however you're called back, and you can't call a function with different scopes to those it had when it was defined.
Since a is not a property of this, it doesn't matter that this is not preserved when calling you back. If you do need to get the correct this then yes, you will need some more work, either using another closure over myObjA itself:
onclick= function() { myObjA.methodA(); };
or using Function#bind:
onclick= myObjA.methodA.bind(myObjA);
yes, you're right. Instead of a method reference
var myObjA = new ObjA();
libraryCallback = myObjA.methodA
pass a closure
libraryCallback = function() { myObjA.methodA() }
If you are using jQuery javascript framework, easiest way is to use proxy:
$('a').click($.proxy(myObjA, 'methodA'));
I'd do this:
function ObjA() {
this.a = 1;
this.methodA = function() {
alert(this.a);
}
}
function bindMethod(f, o) {
return function(){
return f.apply(o, arguments);
}
}
var myObjA = new ObjA();
myObjA.methodA = bindMethod(myObjA.methodA, myObjA);
...
Where bindMethod binds the methodA method to always be a method of myObjA while still passing on any arguments which function() {myObjA.methodA()} doesn't do.

Categories