0

I'm only started to understand closures when suddenly it throws me another curveball, making my understanding of the scoping in closures even worse. I have a relatively simple closure just so I can get jQuery working inside of an object, which is then called by event handlers outside.

<div class="hamburger-link">
   <div class="bar bar1"></div>
   <div class="bar bar2"></div>
   <div class="bar bar3"></div>
</div>

<div id='sidebar-overlay'></div>
<div id="sidebar"></div>

<script>
var cwn = (function($) {
    var app = {
        overlay: {
            e: "#sidebar-overlay",
            activate() {
                $(this.e).fadeIn();
                $('body').css('overflow', 'hidden');
            },
            deactivate() {
                $(this.e).fadeOut();
                $('body').css('overflow', 'unset');
            },
        },
        sidebar: {
            e: '#sidebar',
            open() {
                app.overlay.activate();                
                $(this.e).animate({"width": 'show'});
            },
            close() {                
                app.overlay.deactivate();                
                $(this.e).animate({"width": 'hide'}, 'fast');
            }
        }
    };    
    return app;
})(jQuery);
<script>

So using it in the console itself or calling the function without the event handlers, results in it working - the sidebar activates and opens up and does what it's supposed to.

cwn.sidebar.open(); // THIS WORKS JUST FINE

However using this said function with an event handler results in this changing.

$('.hamburger-link').on('click', cwn.sidebar.open); // THIS CHANGES 'this' TO SOMETHING ELSE

Which then causes it to fail.

I have an interim solution - which is to replace this.e with app.sidebar.e but that just seems extremely cumbersome and it just seems to me that there is a better and simpler solution out there.

Shib
  • 296
  • 1
  • 13
  • Can you debug the "this" object in browser console, to see what content this object contains? – Reporter Apr 11 '22 at 11:03
  • Please provide a [mcve]. – jabaa Apr 11 '22 at 11:09
  • @Reporter when calling cwn.sidebar.open() it returns the object sidebar. However using the onclick event returns the DOM of element called in $('.hamburger-link') which is but that only shows when you remove the e from this – Shib Apr 11 '22 at 11:18
  • The `this` value is not bound to closures. The rule is simple, `this` always refers to the object which is used as the context when a function is called, it's just not always obvious where and how a function is called. This stands on "traditional" functions, arrow functions have their own rule. – Teemu Apr 11 '22 at 11:22
  • @Teemu how would you recommend I restructure this? I was really hoping that I'd learn a simpler way without having to put the entirety object var on it. – Shib Apr 11 '22 at 11:24
  • I'm bad with jQuery, but the issue is, that jQuery executes the same function for any event, in that function the attached listener function is called, and `this` value is bound to the element firing the event (mostly to `event.target`). A simple way would be to bind `this` when attaching the event, like `.on('click', cwn.sidebar.open.bind(cwn.sidebar)`. See also some [other techniques](https://stackoverflow.com/a/66778579/1169519), though I'm not sure if they'll work with jQuery. – Teemu Apr 11 '22 at 11:33

1 Answers1

1

It's fairly widely understood that object initializers do not support the use of this to refer to the object or properties within it during execution of the initializer. Refer to Self-references in object literals / initializers and the list of linked questions presented on page for a treatment of this.

This is not particularly related to the use of closures - more simply it's not supported by object initializer syntax.

However in this case, given a closure has already been set up by an IIFE, you could always define element selectors within the closure for use as constants within the app, for example:

var cwn = (function($) {

    // selectors
    
    const overlay= "#sidebar-overlay";
    const sidebar = "#sidebar";
    
    // app
    
    const app = {   
        overlay: {
            activate() {
                $(overlay).fadeIn();
                $('body').css('overflow', 'hidden');
            },
            deactivate() {
                $(overlay).fadeOut();
                $('body').css('overflow', 'unset');
            },
        },
        sidebar: {
            open() {
                app.overlay.activate();                
                $(sidebar).animate({"width": 'show'});
            },
            close() {                
                app.overlay.deactivate();                
                $(sidebar).animate({"width": 'hide'}, 'fast');
            }
        }
    };    
    return app;
})(jQuery);

There are of course alternatives, including binding functions within the object to the object they need to be called on. Even so I think it unlikely any particular solution will ever prove to be the best approach in all cases. Go for readability and maintainability when in doubt.

traktor
  • 17,588
  • 4
  • 32
  • 53
  • 1
    > Go for readability and maintainability when in doubt. Bless. This has been my attempt at making code easier to read and maintain as before me and my team were more or less just creating tons of functions with incredibly and needlessly verbose names. I am a bit sad that I could not keep the selectors inside the object as a property, as that was how I thought it was best to maintain as - but this is a decent alternative. Thanks :) – Shib Apr 11 '22 at 12:06