2
\$\begingroup\$

I'm working on redeveloping a running project, and the main goal is to enhance the JS code as much as possible.

I'm using the observer pattern to watch the window events like scroll, and resize

The following is my implementation:

module.exports = {

    // Watch window scrolling/resizing, then fire a set of watchers
    watchWindow: {
        watchers: {
            scroll: {},
            resize: {}
        },
        initialized: false,
        init: function () {
            this.initialized = true;
            var watchers = this.watchers;
            $window.on('scroll resize', function (e) {
                for(var id in watchers[e.type]) {
                    if(watchers[e.type].hasOwnProperty(id)) {
                        if (typeof watchers[e.type][id] === 'function') {
                            watchers[e.type][id](e.type === 'scroll' ? $window.scrollTop() : $window.width());
                        }
                    }
                }
            });
        },
        addWatcher: function (settings) {
            var id = Date.now();
            this.watchers[settings.event][id] = settings.callback;
            !this.initialized && this.init();
            return id;
        },
        removeWatcher: function(settings) {
            delete this.watchers[settings.event][settings.id];
        }
    }
}

Then that's how I'm using it:

// Watch window resizing
utilities.watchWindow.addWatcher({
    event: 'resize',
    callback: function (windowWidth) {
        mobileMenuEnabled = windowWidth < sideMenuBreakpoint;
    }
});
\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

Your code is very to the point and efficient. I like your method of using the global event which the elements 'register' for. While the browser likely does something similar for its event handling, your method puts you in complete control and ensures efficiency.

I do have a couple suggestions though.

First and simplest would be to add some debouncing to your event handling. If you're not familiar with this, the concept is to add a delay on your handling to prevent excessive processing. Something like this.

bool noEvent = false;
function eHandle(){
    if(noEvent){
        return;
    }
    noEvent = true;
    setTimeout(function(){noEvent = false;},100);
    //normal event handling here
}

It's a simple concept that will reduce the amount of work the browser has to spend on your handling. Adjust the timeout interval based on what kind of response time you need.

The other thing you might want to consider is using css for your width checking instead. I notice in your example that you're setting a boolean based on the width of the element. You can use css media queries to adjust the styling of elements based on certain parameters, including screen width. If you can use this in some of your use-cases, then it will reduce the amount of watchers you need to register.

@media (max-width:750px){
    p{
        color:red;
    }
}
@media (min-width:751px){
    p{
        color:green;
    }
}

Lastly if you're interested where the term debouncing comes from, it comes from electronics. When you push a button it actually bounces a bit before it settles. The bounce is very fast and short lived but at millions of checks per second it's easy for a microcontroller to detect. To prevent the bounces being interpreted as dozens of quick button pushes, the programmer adds a delay during which button changes are ignored. 10-50ms delays are typical.

\$\endgroup\$
1
  • \$\begingroup\$ I like your mention of debouncing because it will make the code more efficient. Regarding media queries of course I use it, but here I want to get a Boolean value to use it in somewhere else. \$\endgroup\$ Commented Feb 27, 2016 at 8:43
1
\$\begingroup\$

You could add multiple event handlers for each Watcher instead of initializing a global one.

watchWindow: {
    addWatcher: function (settings) {
        $window.on(settings.event, function (e) {
            settings.callback(e.type === 'scroll' ? $window.scrollTop() : $window.width());
        })
    },
    removeWatcher: function(settings) {
        $window.off(settings.event, settings.callback);
    }
}

In this example you would be using the same settings object to add and remove event handlers, but you could of course keep your map of handlers and generated id's if you prefer.

\$\endgroup\$
1
  • \$\begingroup\$ But by this way you attach many listeners to the window and each time an event occurs, all of them fire. With my implementation, we have only one event listener and when an event occur we call all watchers' callback. \$\endgroup\$ Commented Feb 25, 2016 at 11:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.