That title probably doesn't help much, I tried though. Anyway I ran into this extremely mysterious (and frustrating) bug resulting in a RangeError: Maximum call stack size exceeded
in some OO JS I'd written. Took me a couple hours but I finally got to the cause. Here's a simple example that will result in the same exception:
// Class
var Foo = function(){
// "Public" function
this.bar = function(){
console.log('loop!');
$(this).trigger('bar');
}
}
var foo = new Foo();
$(foo).trigger('bar');
Running this code will result in loop!
being logged to the console a ton of times before eventually resulting in the range exception.
Clearly there's something about jQuery's trigger
function I don't understand, and it boils down to this: why would foo's bar
function be called at all? Yes, the event name and the class's function name are the same, but I don't understand how the two are related.
That title probably doesn't help much, I tried though. Anyway I ran into this extremely mysterious (and frustrating) bug resulting in a RangeError: Maximum call stack size exceeded
in some OO JS I'd written. Took me a couple hours but I finally got to the cause. Here's a simple example that will result in the same exception:
// Class
var Foo = function(){
// "Public" function
this.bar = function(){
console.log('loop!');
$(this).trigger('bar');
}
}
var foo = new Foo();
$(foo).trigger('bar');
Running this code will result in loop!
being logged to the console a ton of times before eventually resulting in the range exception.
Clearly there's something about jQuery's trigger
function I don't understand, and it boils down to this: why would foo's bar
function be called at all? Yes, the event name and the class's function name are the same, but I don't understand how the two are related.
-
1
Sorry, but I cant see the problem. You first call
bar
fromfoo
.. and insidebar
, you call againbar
fromfoo
(this). So it is an expected behavior. – wendelbsilva Commented Dec 11, 2013 at 18:16 - 1 ...resulting in a loop... – Andy Commented Dec 11, 2013 at 18:17
-
Interesting...it seems that the methods of a constructor function act as events, and can be
trigger
ed using jQuery. Edit: Doesn't have to be a constructed object. Any regular object can have any of its methods triggered – tewathia Commented Dec 11, 2013 at 18:23 -
@wendelbsilva So, you're saying that
$(foo).trigger('bar')
is equivalent tofoo.bar()
? That's not true in my experience, the former triggers an event (not "calls a function") on the target object. – Madbreaks Commented Dec 11, 2013 at 18:27
4 Answers
Reset to default 8You would need to use .triggerHandler
to mitigate this issue.
Per the jQuery docs:
Note: For both plain objects and DOM objects other than window, if a triggered event name matches the name of a property on the object, jQuery will attempt to invoke the property as a method if no event handler calls event.preventDefault(). If this behavior is not desired, use
.triggerHandler()
instead.
http://jsfiddle/bcsqF/
From the jQuery Trigger documentation:
Description: Execute all handlers and behaviors attached to the matched elements for the given event type.
bar
is a propery or behavior of Foo
, so the bar
property (which is a method) is called on $(foo).trigger('bar');
. In the bar
function, the this
refers to the foo
class, so the $(this).trigger('bar');
. calls the bar function on foo. This would be qual to calling this.bar() in the bar function, which is a more obvious recursion.
All in all this isn't´the behaviour I would have expected to, I expected that Trigger could be only called on DOM elements, but the documentation states otherwise.
Lets dig into jQuery source.
trigger:
function (type, data) {
return this.each(function () {
jQuery.event.trigger(type, data, this);
});
}
jQuery.event
function (src, props) {
// Allow instantiation without the 'new' keyword
if (! (this instanceof jQuery.Event)) {
return new jQuery.Event(src, props);
}
...
// Put explicitly provided properties onto the event object
if (props) {
jQuery.extend(this, props);
}
...
}
As you can see jQuery.event
object is constructed in a way that it's extended with own properties of you object. It means that you can use trigger
to invoke them. Hence a loop.
In addition to calling any attached event handlers, trigger
will also attempt to invoke the native behavior. So if you are calling $('form').trigger('submit')
in addition to executing any bound handlers, it will fire the native method form.submit
to actually send the form. I suppose trigger looks on the object for a method with the same name as the triggered event, and assumes that to be acpanying native event, which it then calls.