I am working on a project that uses some JS prototyping and jQuery.
My issue, I believe, is how 'this' is being used.
In my code, I have some jQuery in a function that I will be prototyping. The jQuery looks like this:(in this code, 'target' is a jQuery object passed when IMAGE_UPLOADER is first created.)
document.getElementById(target.find('.file_selector').prop('id')).addEventListener("change", this.FileSelectHandler, false);
In this event listener, there is a function called FileSelectHandler. This function is being called just fine. However, within this function, there is a call to a second function. Here is a short version of the function:
FILE_UPLOADER.prototype.FileSelectHandler = function(e) {
this.FileDragHover(e);
}
This is where the error comes up. JS is complaining that the function 'FileDragHover' does not exist. It, of course DOES exist and is defined as follows:
FILE_UPLOADER.prototype.FileDragHover = function(e) {}
I hope this is enough info to understand the problem. If not, please let me know and I can add more.
You can use $.proxy() to pass the custom execution handler to the event handler, this inside the event handler refers to the dom element where the listener is attached - it is the same as Function.bind() but jQuery version supports IE < 9.
document.getElementById(target.find('.file_selector').prop('id')).addEventListener("change", $.proxy(this.FileSelectHandler, this), false);
The handler registration can be simplified using jQuery to
target.find('.file_selector').change($.proxy(this.FileSelectHandler, this))
If an object obj has a function func and you do obj.func() the function is called in context of obj where this in that function then referes to obj.
If you however do something like this:
var callback = obj.func;
callback();
Then callback is not called in the context of obj anymore. For browsers the context is then window. This is what is happening if you pass the function as callback to the addEventListener.
Depending how your callback is used it can be called with another context, for event listeners it is the DOM element, so this in your code refers to the DOM element.
To solve this problem you can used Function.prototype.bind (you need to check the browser support if it is usable for you or use a polyfill for it, that can be found on the mdn page), you can use jQuery.proxy or create a closure and maintain the context yourself.
The this variable refers to the invoking object not the object that the method is declared on. So if I have someObject.someFunction(); then someObject is the invoking object and the value of this in the function.
To demonstrate:
var obj1 = {
name:"obj1",
say:function(){
console.log(this.name);
}
};
var obj2 = {name:"obj2"};
obj2.say = obj1.say;
obj2.say()//logs obj2
In the above code say was declared on obj1 but invoked from obj2.
More on the this value, constructor functions and prototype here.
Related
I read some other answer about this topic but I'm not sure I understand how this keyword works inside addEventListener.
const button = document.querySelector('button');
function foo() { console.log(this) }
button.addEventListener('click', foo);
foo is a regular function inside addEventListener, it's not a method on button object. When foo is called should be executed in the context of the global object, therefore this should be equal to window and not to button.
Looks like a situation similar to this example:
const obj = {
method: function (cb) {
console.log('method', this); // `this` === `obj`
return cb();
}
};
obj.method(function() {
console.log('cb', this); // `this` === `window`
});
Where obj could be considered as button, method could be addEventListener and cb the callback inside addEventListener.
I know I can use bind to change the context of this but I want to understand more in depth why it works like that.
Why this inside addEventListener callback is invoked on the context of the current element instead of the global object?
If we are using functions which have been defined using function keyword as an event handler, then that event handler function executes in the context of the element on which event was binded
button.addEventListener('click', foo);
so, in this case, this value inside foo will be button element.
If we use arrow functions instead of them then this value will be the window object
The reason is this in an arrow function has the same value as the context in which the arrow function was created
button.addEventListener('click', () => { console.log(this) // window } );
More about lexical this
What is lexical 'this'?
While we know that event listeners are executed with 'this' set to the event target, the below lines of code inside the EventTarget.prototype.dispatchEvent method in the EventTarget link that you found will answer your question as to how it is implemented.
for (var i = 0, l = stack.length; i < l; i++) {
stack[i].call(this, event);
}
The 'stack' array has the callback functions and they are invoked using .call by passing in the event target instance (this) and event as arguments.
As event handler is a type of callback, they are passed as a parameter to the function. Let's create a simple function and passed one callback as a parameter to it and see how it actually works.
function testCallBack(fn){
console.log('inside testCallBack');
fn('Hello I am a callBack')
}
testCallBack(foo);
function foo(param){
console.log(param);
}
// Outputs:
inside testCallBack
Hello I am a callBack
Every scope in JavaScript has a this object that represents the calling object for the function.
That's the reason why this inside addEventListener callback is invoked on the context of the current element instead of the global object.
Refer below code for more clear understanding:
function sayNameForAll() {
console.log(this.name);
}
var person1 = {
name: "Rajat",
sayName: sayNameForAll
};
var person2 = {
name: "pldg",
sayName: sayNameForAll
};
var name = "Sidd";
person1.sayName(); // outputs "Rajat" here calling object is person1, so this represents person 1
person2.sayName(); // outputs "pldg"
sayNameForAll(); // outputs "Sidd"
So when you call button.addEventListner('click',foo), your calling object is button.
Event listeners are executed with this set to the object that triggered the event, as one listener can listen to events of many objects.
A regular function invocation however does not set this if the invocation expression does not contain a member access via .. In those cases, without "use strict" active, this will become the global context, which is window in the browser.
If you want this for cb to be obj, you could replace cb() with cb.apply(this), which would set cb's this to that of the enclosing function.
A final warning: these this mechanics only work for functions defined with the function keyword (and similar mechanics). The this inside of an arrow function becomes locked to that of the enclosing scope at the time of definition.
Just like you can use bind or call to set this to whatever you want, the browser APIs can also call your functions with any value set to this. It’s used in a bunch of weird ways and isn’t very consistent. Outside of classes and methods, this is more like a secret extra argument to a function. In this case you could avoid needing it by getting the button element from event.target.
In a codebase I see bind used to make bound copies of functions on the prototype, used as callbacks for DOM events.
Why might this idiom be used, rather than, for example, using the methods on the prototype directly?
Does this offer some benefits in terms of memory consumption/the ability to free memory use when events are unbound from DOM events?
function F() {
var onFoo = this._onFoo.bind(this); // Why?
document.getElementById('foo').onClick(onFoo);
}
F.prototype._onFoo = function () { /*...*/ }
The issue is that event handlers set their own value for this when they call the callback. That value will typically be related to the event handler, not to the object that the method is bound to. For example, in your example:
document.getElementById('foo').onClick(myObj.myFunc);
The this pointer in myFunc will be set to the DOM element that had the event handler attached (in this case, the foo element). But that isn't myObj so myFunc in that case could not access any of it's own instance variables via the this pointer (the normal way that methods access their instance data).
So, if you have a method that wants to access it's own instance data when it is called directly by an event handler, you have to do something other than just pass the method to the event handler. There are a couple ways to work around this issue.
One way of doing so it so use .bind() which returns a new stub function who's function is to set this before calling your function like this:
document.getElementById('foo').addEventListener('click', myObj.myFunc.bind(myObj));
In this case .bind() actually returns a new stub function who's function is to set the value of this to myObj before it calls myFunc.
You could also do that manually yourself like this:
document.getElementById('foo').addEventListener('click', function(e) {
myObj.myFunc();
});
But, as you can see, .bind() provides a shortcut that takes less code (which is why it was invented).
A potential disadvantage to using .bind() in some cases is that you may no longer have access to the value of this that the caller of your callback would have set itself because .bind() threw that value away and replaced it with your own. In the event handler example above, this is not an issue because the original source of the event can be accesses via the e argument that is passed to the event handler so it is not lost if you need it.
I am aware of no meaningful difference in memory consumption or garbage collection between the two methods above. Both create a new function that is used to call the original and control the value of this when calling the original function. Both will have the same garbage collection lifetime.
It appears that one thing that is confusing you is that objects in Javascript are assigned or passed by pointer (some call it by reference, but that has some connotations that don't apply here so I'll use the phrase by pointer).
var x = {};
x.myFunc = function() {console.log("hello");};
x.myFunc(); // generates "hello" in the console
var t = x.myFunc; // save reference to the function that x.myFunc currently points to
delete x.myFunc; // remove property myfunc from the x object
t(); // generates "hello" in the console
t() still works event after x.myFunc has been removed because both t and x.myFunc had a reference (or pointer) to the same function. Doing a delete x.myFunc simply removed the myFunc property from the x object. The function that x.myFunc points to will only be "freed" by the GC when there are no other references to it. But, there is another reference to that function in t, so it is not freed and t() can use it for as long as t exists.
I am trying to set up an on-click callback for an HTML that causes another node to become visible. Along the way, I was surprised to find out that the following two statements are not equivalent:
$("#title").click($("#content").toggle);
$("#title").click(function() {
$("#content").toggle();
}
The first statement ultimately results in a TypeError when the element is finally clicked, with a message of "undefined is not a function," which I surmised to indicate that whatever I was assigned to the onclick callback ended up being undefined and somehow doesn't persist in memory.
The workaround is simple (just use the statement of the second form), but what I really want to understand is why passing the toggle function as an object doesn't work when it finally gets called. I can see that the two are semantically different: the first executes the $("#content") call when binding the event and the other executes it when the event occurs, but I don't understand why that should matter.
In case it is relevant to the answer, the code in question is located inside of a function (that has presumably returned by the time the user can click anything).
In the first example, you're passing the toggle function for jQuery to execute as the event handler.
However, when jQuery executes the event handler, it sets the value of this to be the DOM element the event fired on.
toggle, however, expects it to be the jQuery object (which it would be if called normally), as it uses this.animate() in its implementation.
This is why you see "undefined is not a function", as this.animate is "undefined", and you're trying to call it as a function.
It's important to appreciate the resolution of this inside a function is deferred until the function is executed. This means a single function can see a different this value between invocations. The value of this can be altered using bind(), new, call(), apply() or by referencing the object differently; for more info see here, or How does the "this" keyword work?
The jQuery function, as in this -> $(), is just a function, think of it as
var $ = function(selector, context) {
// do stuff with selector etc
}
That's really simplified, but when you're calling the jQuery function (as in $()) with a valid selector, it gets the DOM node and returns something like this.
[
0 : <div id="title"></div>,
context : document,
selector : "#title",
jquery : "1.11.0",
.....
etc
]
this is the array-like object jQuery returns, and as you can see 0 is the native DOM node, and it's the reason we can do $('#title')[0] to get the native DOM node.
There is however something that one really can't see from a simple console.log, and that's the methods that are prototyped onto that array-like object, we could however use a for..in loop to see them in the console.
var title = $('#title');
for (var key in title)
console.log(key)
FIDDLE
This would return a long list of all the prototyped and non-prototyped methods available on this object
get
each
map
first
last
eq
extend
find
filter
not
is
has
closest
....
etc
Notice that these are all the jQuery methods added to the $() function with $.prototype, but jQuery uses a shorter name, $.fn, but it does the same thing.
So all the jQuery functions we know are added to the main $() function as properties, and the new keyword is used internally to return a new instance of the $() function with those prototyped properties, and that's why we can use dot notation, or for that matter bracket notation and chain on methods to the $() function, like this
$().find()
// or
$()[find]()
When objects are extended with prototyped properties like this, the value of this is also set inside the methods, so now that we understand a little bit about how it works, we can recreate a really simple jQuery version
var $ = function(selector, context) {
if (this instanceof $) {
this.context = context || document;
this[0] = this.context.querySelector(selector);
return this;
}else{
return new $(selector, context);
}
}
This is simplified a lot from how jQuery works, but in principle it's the same, when $() is called, it checks if it's an instance of itself, otherwise it creates a new instance with the new keyword and calls itself again as a new instance.
When it is a new instance, it gets the element and the other properties it needs, and returns those.
If we were to prototype on a method to that instance, we could chain it like jQuery does, so lets try that
$.prototype.css = function(style, value) {
this[0].style[style] = value;
}
and now we can do this
$('#title').css('color', 'red');
we've almost created jQuery, only 10000 lines of code to go.
FIDDLE
Notice how we have to use this[0] to get the element, we don't have to do that in jQuery when we use something like click, we can just use this, so how does that work ?
Lets simplify that as well, as it's crucial to understand why the code in the question doesn't work
$.prototype.click = function(callback) {
var element = this[0]; // we still need [0] to get the element
element.addEventListener('click', callback.bind(element), false);
return this;
}
What we did there was use bind() to set the value of this inside the callback function so we don't have to use this[0], we can simply use this.
FIDDLE
Now that's cool, but now we can no longer use any of the other methods we've created and prototyped to the object, as this is no longer the object, it's the DOM node, so this fails
$('#element').click(function() {
this.css('color', 'red'); // error, <div id="element".. has no css()
// however this would work, as we now have the DOM node
this.style.color = 'red';
});
The reason it fails is because we now have the native DOM node, and not the jQuery object.
So finally to answer the question asked.
The reason this works ...
$("#title").click(function() {
$("#content").toggle();
});
... is because you're calling the toggle() function, and the correct value of this is set, in this case it would be the jQuery object containing #content as toggle() has no callback that uses bind(), it simply passes the jQuery object, an object similar to what we can see at the top of this answer
Internally toggle() does
$.prototype.toggle = function() {
this.animate();
}
see how it uses this directly whithout doing anything other than calling another jQuery function, it requires that this is a jQuery object, not a native DOM element.
Lets repeat that, toggle() requires that this inside the function is a jQuery object, it can not be anything other than a jQuery object.
-
Now lets move on back to click again, when you do
$("#title").click(function() {
console.log(this)
});
the console would show the native DOM element, something like <div id="title"></div>
Now we can reference a named function instead
$("#title").click(myClickHandler);
function myClickHandler() {
console.log(this)
});
and the result would be exactly the same, we would get the native DOM element in the console -> <div id="title"></div>, which is not suprising as this is exactly the same as the one above using an anonymous function.
What you're doing is referencing the toggle() function like this
$("#title").click($("#content").toggle);
It's exactly the same as the example above, but now you're referencing toggle(), and when called it will be called with the value of this set to the native DOM element in the click function, it would go like this
$("#title").click($("#content").toggle);
$.prototype.toggle = function() {
console.log(this); // would still be <div id="title"></div>
this.animate(); // fails as <div id="title"></div> has no animate()
}
This is what is happening, toggle() is expecting this to be a jQuery object, but instead it gets the native DOM node for the element in the click handler.
Read that again, this inside the toggle() function would be the native #title element, which isn't even the correct element, as that's how javascript and jQuery works, see the long explanation above for how this is set in the prototyped methods etc.
When you use this in a JS function, it refers to whatever object the function is currently being called on, not where it was defined. For instance, you can define a function and copy it onto another object, like this:
foo = {'name': 'foo'}; bar = {'name': 'bar'};
foo.test= function() { console.log(this.name); }
bar.test= foo.test;
foo.test(); // logs 'foo'
bar.test(); // logs 'bar'
When you run foo.test(), this is set to point at foo; but when you run the same function as bar.test(), this is set to bar instead. There is nothing in the function that knows it was originally part of foo, so you basically have two separate but identical functions, like this:
foo.test = function() { console.log(this.name); }
bar.test = function() { console.log(this.name); }
When you run $("#title").click($("#content").toggle);, a similar thing happens - you get a reference to the toggle function, and copy that function into jQuery's list of event handlers. When the callback runs, the $("#content") part is forgotten, just like the foo was above, so when the implementation in jQuery looks at this to see what you want to toggle, it will find the wrong thing.
Exactly what it finds instead has an extra little quirk: jQuery sets this on click handlers to be the DOM element that was clicked on (there are various ways in JS of explicitly telling a function what it should use as this). The exact error comes about because the implementation of toggle is expecting a jQuery object, not a native DOM object, but even if a jQuery object was set as this, it would be the wrong node: you clicked on $('#title'), but want to toggle $('#content'), and jQuery has no way of knowing that.
For completeness, to explain why $("#title").click(function() { $("#content").toggle(); } does work: here the function being saved in jQuery is an anonymous function, which doesn't make any use of this, so doesn't care what it gets set to when the callback finally fires. When the event runs (when you click) it calls toggle with an explicit context (the object returned by the $('#content') lookup), which is exactly what it's expecting.
I have a function:
myObject.myFunction = function(callback){
callback();
}
and a callback
randomObject.callBack = function(){
console.log(this);
}
if I call randomObject.callBack() directly, I get the parent object in the console. However, if I call myObject.myFunction(randomObject.callBack), it logs a DOM Element.
How can I access the parent object?
Note
I do not know the name of the callbacks parent object ahead of runtime.
The context (i.e. this value) of an object is determined at the time the function is run, not at the time it is defined. Passing randomObject.callBack to another function does not send the context (the randomObject bit); it just sends the function.
Presumably the context is set when myFunction calls it. Since you aren't explicitly providing a context (e.g. with call or apply), the context will be the window object.
You can change this by explicitly saying what the context of the function should be before it is run. You can do this with the bind method:
myObject.myFunction(randomObject.callBack.bind(randomObject))
Now when you call callback inside myFunction, randomObject will be logged.
Note that bind is relatively new; not all browsers support it. The MDN page I linked to above has a bit of code that will make it work in all browsers.
This happens because when you invoke a function without an object, inside the function this will point to Window object.To avoid this we usually do like this
myObject.myFunction = function(callback){
callback();
}
randomObject.callBack = function(){
console.log(this);
}
function proxyCallback(){
randomObject.callBack();
}
myObject.myFunction(proxyCallback);
In javascript, this refers to the object context in which a function is called. This is not necessarily related to any object on which it has been defined.
You can think of it as though functions are not defined as members of objects, but called as members of objects.
There are four things that this might resolve to, depending on context.
A newly created object, if the function call was preceded by the new keyword.
The Object to the left of the dot when the function was called.
The Global Object (typically window), if neither of the above are provided.
The first argument provided to a call or apply function.
In your situation, something like this might be appropriate:
myObject.myFunction(function(){randomObject.callBack()});
This creates a closure so that within myFunction, callback is called as a member of randomObject.
Hi guys I have a function which accepts this as a parameter - 'this' referring to the dom element which upon clicked should run a function. The thing is that I want this function to be called after a small delay however passing the variable term this doesn't work as when the function is executed 'this' then doesn't refer to the object in passed in the parameter but to the window object.
How can I get this done?
You could capture this:
var t = this;
window.setTimeout(function() {
// use the t variable here
}, 2000);
PrototypeJS adds the bind() method to Function.prototype. This method allows you to bind a function and arguments to the context of a particular object. Simply,
window.setTimeout((function() {
alert(this);
}).bind(this), 2000);
The best part is that this method is now part of the ECMA-262 specification, which JavaScript is based upon, and native implementations are rolling out into modern browsers. PrototypeJS will only add this method if it's not already implemented.
I've set up an example script at http://jsfiddle.net/rLpbx/.