Search code examples
javascriptfunctionrefactoringclosurescode-reuse

How can I factor a closure out of an outer function to reuse said closure in different contexts?


Suppose I have this bit of code that deals with event handling.

var btn1 = document.getElementById('btn1');
var btn2 = document.getElementById('btn2');

btn.addEventListener('click', say('hello'));
btn2.addEventListener('click', shout('yo'));

function say(word) {
    var saying = word;
    var container = document.getElementById('result1');
    return function handleEvent() {
        var text = document.createTextNode(saying);
        container.innerHTML = '';
        container.appendChild(text);
    };
}

function shout(word) {
    var saying = word.toUpperCase();
    var container = document.getElementById('result2');
    return function handleEvent() {
        var text = document.createTextNode(saying);
        container.innerHTML = '';
        container.appendChild(text);
    };
}

Uppon clicking on btn1, "hello" is displayed in result1.

Uppon clicking on btn2, "YO" is displayed in result2.

Now, as you may have noticed, the two handleEvent functions are exactly the same. What I want to achieve is something like.

function say(word) {
    var saying = word;
    var container = document.getElementById('result1');
    return handleEvent;
}

function shout(word) {
    var saying = word.toUpperCase();
    var container = document.getElementById('result2');
    return handleEvent;
}

function handleEvent() {
    var text = document.createTextNode(saying);
    container.innerHTML = '';
    container.appendChild(text);
}

But that of course will not work as the handleEvent function is not declared inside the say and shout functions; thus, saying and container will not be defined.

I have tried playing around with bind() and declare saying and container on this inside the say and shout functions :

function say(word) {
    this.saying = word;
    this.container = document.getElementById('result1');
    return handleEvent.bind(this);
}

function shout(word) {
    this.saying = word.toUpperCase();
    this.container = document.getElementById('result2');
    return handleEvent.bind(this);
}

function handleEvent() {
    var text = document.createTextNode(this.saying);
    this.container.innerHTML = '';
    this.container.appendChild(text);
}

But that didn't work either.


How can i factor the handleEvent function out of the say and shout functions?

Thank you!

JsBin setup


Solution

  • Binding should have worked if you had created new objects to be bound to, instead of using this:

    function say(word) {
        return handleEvent.bind({
            saying: word,
            container: document.getElementById('result1')
        });
    }
    
    function shout(word) {
        return handleEvent.bind({
            saying: word.toUpperCase(),
            container: document.getElementById('result2')
        });
    }
    

    However, you still can solve this with a normal closure:

    function say(word) {
        return makeHandleEvent(word, 'result1');
    }
    
    function shout(word) {
        return makeHandleEvent(word.toUpperCase(), 'result2');
    }
    
    
    function makeHandleEvent(saying, id) {
        var container = document.getElementById(id);
        return function handleEvent() {
            var text = document.createTextNode(saying);
            container.innerHTML = '';
            container.appendChild(text);
        }
    }