-->

Moving from classic event management to event dele

2019-02-20 02:15发布

问题:

The old event management in which each handler for specific actions was directly attached to the target element is becoming outdated, since considerations about performance and memory saving started spreading in the developers community.

Event delegation implementations had an acceleration since jQuery updated the old fashioned .bind() and .live() methods with the new .on() method to allow delegation.

This determines a change in some seasoned approaches, where to use event delegation a rework is necessary. I am trying to work out some best practice while keeping the coding style of my library, and looked for similar situations faced from other developers to find an answer.

Using OOP with functions as constructors, I usually have interfaces for objects creation like this:

var widget = new Widget({
    timeout: 800,
    expander: '.expanders'
});

with object literals given as argument, providing a clean map of names and values of the input passed. The class underlying this code could be something like the following:

var Widget = function(options) {
    // some private members
    var _timeout;
    var _$widget;
    var _$expander;

    // methods
    this.init = function() {
        _timeout = options.timeout || 500;
        _$expander = $(options.expander);
        _$widget = _$expander.next();
        _$expander.on('click', _toggle);
    };
    var _toggle = function(e) {
        if (_$widget.is(':visible')) {
            _$widget.hide(_timeout);
        } else {
            _$widget.show(_timeout);
        }
    };
    this.init();
};

Using "private" methods gave me some benefits in terms of code readability and cleanness (only useful methods are publicly exposed to the user), beyond the small gain in performance (each scope resolution takes more time than a local variable). But when speaking about event handlers, it clashes with the event delegation paradigm.

I thought to make public the methods that I used to associate internally to the listeners in the class:

    this.toggle = function(e) {
        if (_$widget.is(':visible')) {
            _$widget.hide(_timeout);
        } else {
            _$widget.show(_timeout);
        }
    };

then driving externally, or in another proxy class, the proper delegation with something like this:

var widget = new Widget({
    expander: '.expanders'
});

$(delegationContext).on('click', '.expanders', widget.toggle);

but it did not seem to me the best approach, failing in the exposure of a non-useful method in the interface, so I tried a way to let the main class know directly all the information to delegate the event autonomously, through the interface:

var widget = new Widget({
    timeout: 800,
    expander: {
        delegationContext: '.widgetContainer',
        selector: '.expanders'
    }
});

which would allow to keep on using private methods internally in the class:

var $context = $(options.expander.delegationContext);
$context.on('click', options.expander.selector, _toggle);

What are your practice and suggestions about it?
And what are the main trends of other developers you heard about as far as today?

回答1:

it clashes with the event delegation paradigm.

Forget that paradigm! Don't choose it because it's "cool" and directly attaching events "is becoming outdated", choose it only when it is necessary. Event delegation is not necessary for you, nor does it speed up anything. It is not a solution you can apply - you have no problem!

You have one element and a single element-specific event handler function (every widget expander has its own function). You can already infer from your problems applying event delegation that you cannot and should not use it here. Attach the handlers directly.

Event delegation is only useful when you have a vast amount of similar elements, located consistently in the DOM with one common ancestor, that would all going to be attached the same event handler function. This is not the case with your Widget constructor that does take instance-specific options such as the expander selector.

In your current Widget class, using event delegation would clash with the single responsibility principle. In the event delegation paradigm, you would need to see the elements in the delegation context as a whole, being as homogeneous as possible. The more element-specific data and state you add, the more delegation advantages you are loosing.

If you really want to use event delegation here, I would suggest something like

var Widgets = {
    init: function(delegationContext) {
        $(delegationContext).on("click", ".widget-expander", function(e) {
             $this = $(this);
             $this.next().toggle($this.data("widget-expander-timeout") || 500);
        });
    },
    activate: function(options) {
        $(options.expander)
         .addClass("widget-expander")
         .data("widget-expander-timeout", options.timeout);
    }
};

Here no constructors are used. You just initialise the event delegation context, and then you can add single elements to be captured by the delegation mechanism. All data is stored on the element, to be accessible from the event handler. Example:

Widgets.init('.widgetContainer');
Widgets.activate({expander: '.expanders', timeout: 800});

And what are the main trends of other developers you heard about as far as today?

Apart from that question being off-topic on StackOverflow, I can only advise you not to follow every trend someone heard about. Learn about new (or advertised or fancy) technologies, yes, but do not forget to learn about when to use them.