¿Por qué este bloque de código está creando un bucle infinito?

Estaba jugando con un violín hoy mientras intentaba responder una pregunta y encontré algo confuso. Siendo un novato de JS, no puedo depurar lo que está mal. Incluso traté de verificar la fuente 0 de $.fn.show en la fuente de jQuery, pero no pude averiguar qué está pasando mal.

HTML:

<input type='text' id='dataBox'/>
<input type='button' value='toggle' id='toggleButton' />​

código jQuery:

jQuery(function ($) {
    var _oldShow = $.fn.show;
    $.fn.show = function (speed, oldCallback) {
        return $(this).each(function () {
            var obj = $(this),
                newCallback = function () {

                    if ($.isFunction(oldCallback)) {
                        oldCallback.apply(obj);
                    }
                    obj.trigger('afterShow');
                };
            obj.trigger('beforeShow');
            if(speed)    
                _oldShow.apply(obj, [speed,newCallback]);
            else    
                _oldShow.apply(obj, [newCallback]);
        });
    }
});


$('#dataBox').bind('beforeShow', function () {
        alert('beforeShow');
    });


$('#toggleButton').click(function(){
        $('#dataBox').show();
}); 

El problema es que por algún error que cometí, está causando que esta línea se ejecute un número infinito de veces obj.trigger('beforeShow');

y de ahí el alert en este bloque

 $('#dataBox').bind('beforeShow', function () {
    alert('beforeShow');
 });  

parece no parar.

Independientemente de lo que estoy tratando de hacer o si esto se puede hacer de otra manera, ¿alguien puede explicar qué estoy haciendo mal aquí? He estado intentando durante varias horas, pero no pude averiguarlo.

VIOLÍN

preguntado el 22 de mayo de 12 a las 16:05

Una sugerencia para facilitar la depuración. Usa la declaración debugger; en lugar de alert('beforeShow'); en su método de enlace. Esto colocará un punto de interrupción allí para que pueda ver el seguimiento de la pila en el depurador. -

@BrantOlsen debugger; funciona con Visual Studio, supongo que si el OP está usando php o no está del lado del servidor, por ejemplo, console.log sería una mejor sugerencia -

@3nigma También funciona con Chrome y Firebug. -

@BrantOlsen que no sabía, gracias por compartir -

@BrantOlsen Gracias por el debugger; declaración. -

3 Respuestas

Echemos un vistazo a esta sección.

    if(speed)    
        _oldShow.apply(obj, [speed,newCallback]);
    else    
        _oldShow.apply(obj, [newCallback]);

    });

_oldShow se asigna como $.fn.show; antes, y .apply() llama a una función con argumentos como una matriz y la capacidad de establecer el contexto de this. (ver aquí)

Entonces, al final de la función, siempre terminamos llamando a la función nuevamente, activando infinitamente beforeShow.

contestado el 22 de mayo de 12 a las 17:05

Si observa el código detenidamente, puede ver que en él la función $.fn.show acaba de envolverse con otra función que llama a la original, no a sí misma. - Dmitry

@Dmitry No entiendo lo que intentas decir. _oldShow es igual a la función anónima que contiene la función que contiene el bloque if anterior, llamándose a sí misma infinitamente. - Snuffleupagus

para verificar su teoría, puede alertar algo como esto: "alert ($.fn.show === _oldShow);". Ejemplo: jsfiddle.net/D9vP6 si lo hace, puede ver que las funciones son diferentes. - Dmitry

@Dmitry Todavía no tienes ningún sentido. Todo lo que probaste es que la función se llama a sí misma a una recursividad infinita, que era lo que explicaba mi respuesta. - Snuffleupagus

Pero estoy haciendo una copia de $.fn.show en la primera linea var _oldShow = $.fn.show; ¿Solo hace referencia a $.fn.show y no una copia duplicada de la función. Parece que tienes razón. Pero entonces, ¿cómo puedo hacer una copia de $.fn.show y usar eso más tarde? - Prasenjit Kumar Nag

Mire el código de la función show (está en alerta): http://jsfiddle.net/D9vP6/4/

Parecía llamarse a sí mismo bajo alguna condición para normalizar el argumento. Después de redefinir esta función, recibe una recursividad infinita.

Para evitar tal comportamiento, debe tener todo esto normalizado en su código y no desencadenar eventos bajo algunas condiciones.

contestado el 22 de mayo de 12 a las 17:05

en realidad es el original $.fn.show. Mira esto jsfiddle.net/joycse06/evdpj/3 +1 por esto. - Prasenjit Kumar Nag

@Joy, sí lo es y trata de llamarse a sí mismo, lo que en realidad ya no es él mismo, ya que se redefinió. Entonces te llama función en lugar de eso, que llama original y así sucesivamente. - Dmitry

El problema con el código anterior es que en algún momento jQuery llama al $.fn.show desde dentro de sí mismo y eso crea el bucle infinito. Entonces, la forma correcta de evitar eso es hacer una verificación de argumentos como la siguiente:

 jQuery(function($) {
    var _oldShow = $.fn.show;
    $.fn.show = function(speed, easing, oldCallback) {

        var args = Array.prototype.slice.call(arguments),
            duration = args[0] || 0,
            easing = 'linear',
            callback = function() {},
            callbackArgIndex = 1;

        // jQuery recursively calls show sometimes; we shouldn't
        //  handle such situations. Pass it to original show method.    
        if (!this.selector) {
            _oldShow.apply(this, args);
            return this;
        }

        if (args.length === 2) {
            if ($.isFunction(args[1])) {
                callback = args[1];
                callbackArgIndex = 1;
            }
            else {
                easing = args[1];
            }
        }
        else if (args.length === 3) {
            easing = args[1];
            callback = args[2];
            callbackArgIndex = 2;
        }

        return this.each(function() {
            var obj = $(this),
                oldCallback = callback,
                newCallback = function() {

                    if ($.isFunction(oldCallback)) {
                        oldCallback.apply(obj);
                    }
                    obj.trigger('afterShow');
                };
            obj.trigger('beforeShow');
            args[0] = duration;

            if (callback) {
                args[callbackArgIndex] = newCallback;
            }
            else {
                args.push(callback);
            }

            _oldShow.apply(obj, args);


        });
    }
});


$('#dataBox').bind('beforeShow afterShow', function(e) {
    alert(e.type);
});


$('#toggleButton').click(function() {

    $('#dataBox').show();

});​

Esto funciona bien y los eventos se activan correctamente.

El bloque que impide el ciclo infinito es este

if (!this.selector) {
      _oldShow.apply(this, args);
      return this;
}

Lo que esto está haciendo es llamar a la función original y regresar en los casos en que jQuery llama $.fn.show varias veces (jQuery parece estar haciéndolo por alguna razón).

Violín de trabajo

contestado el 30 de mayo de 12 a las 08:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.