You are reading a single comment by @ChristianW and its replies. Click here to read the full conversation.
  • @ChristianW,

    sorry being off for a bit... did some woodworking... a totally different beast but great balance to the 'just sitting' and hacking.

    To begin with the post #9* where some off the oddities (JavaScript: The Good Parts... Douglas Crockford): context of the 'this': For a generic understanding, JavaScript is - next to other many other things - a scripting language that supports functions and provides very low entry level in coding.... and that is what happens with the setTimeout() and setInterval(): the first argument is a function(), nothing else... and when it is 'finally' called, the function knows nothing about the very high level concept of coding nor oo - context - (anymore) when it was passed as argument to the setTimeout() and setInterval(). The execution hits the 'this' and tries to resolve it... and it may get a context, just not the one you intended.

    Recall of code from post #9:

    var Obj = {
        message: "foo",
        init: function() {
            this.message = "foo";
            setInterval(()=>{
              this.log();
            }, 1000);
        },
        log: function() {
            console.log(this.message);
        }
    };
    
    Obj.init();
    

    This - original context not known anymore - is not all bad, because:

    • do you always need a context
    • and plain scripting in a simple problem needs only one (global context), so why demand a context?

    But you figure... and you figured it - almost - because another oddity made it right: fat arrow functions do not have their own context like a regular function... oops... you got mocked by JS's understanding of context... again... The fat arrow function has no own context like a regular function has but takes the creation context and holds on to it...

    Therefore, to overcome the 'this' context for setTimeout() and setInterval() with regular functions, JavaScript offers multiple options:

    The first, always working one, is to bind the function to the context you want before passing:

    var obj = {
        message: "foo",
        init: function() {
            setInterval(this.log.bind(this), 1000);
        },
        log: function() {
            console.log(this.message);
        }
    };
    
    function onInit() {
            obj.init();
    }
    
    setInterval(onInit,999);
    

    The bind() in line #4 makes sure the any this in function passed refers to the context (and object) you intended.

    Alternative and not to use the bind(), you set a local variable to point to the context (this) and used that in the function (some use 'that' for that - word play). In the beginning I used _this, but most of the time I just use _ for the variable name and create an anonymous function (the bind() does this behind the scene... and is even more efficient):

    var obj = {
        message: "foo",
        init: function() {
            var _ = this;
            setInterval(function(){ _.log(); }, 1000);
        },
        log: function() {
            console.log(this.message);
        }
    };
    
    function onInit() {
            obj.init();
    }
    
    setInterval(onInit,999);
    

    You may notice some other things:

    • A good convention in oo is to leave uppercase beginning names for 'class' like things... (in JS constructor function), or for constants, some times globals too... but it can get confusing. So I stick with former.
    • I wrapped the init() invocation into the 'famous' onInit function
    • I used a timeout to trigger the onInit()... which I then remove on last upload (to RAM) before save()-ing the code into the FlashEEProM.

    Latter two have nothing to do with what we discuss here. Have an onInit() to get the code activated at a 'conscious' point is a good practice. Last one makes sure that applicaiton code does not mess with the upload... because the upload is a running js code as well... (Espruino understands only JS thru the REPL in the console, and therefore, what is going on on upload is javascript execution. You can read about this in conversation about: simple explanation how to save code that espruino run on start?. I has aged... but is still true when uploading into the RAM. For simple examples it is never an issue, but if code is active - especially timeouts and intervals and dynamic inits, it is a bad practice to get them going before the upload is complete. @Gordon has done a lot to make the execution save and avoid contentions with the upload, but there is always the possibility that the application or a used driver to connect to a sensor or actuator has an initialization that cannot be know to the save() command nor to a (re)load and resume on power up / power cycle.

    More is to be said about context... but this is enough for now.

    Not having a clear - reproducible - initialization after power cycle / messing w/ the upload can cause some of the unpredictable things... like the ) found ( ...found ( Got ')' expected EOF ).

  • Aaah - thanks for the brief explanation.
    I was aware of the scope of this and the technique of using that but did not expect it to be an issue with just referencing a callback function like setTimeout(this.log, …) expecting it to be resolved just before the timeout is started.
    However, the bind() function was new to me, thanks for the suggestion.
    And that function() {} is different from () => {} regarding the scope of this did not occur to me.
    Javascript is easy and nice in some aspects, but has a lot of oddities when digging deeper...

About

Avatar for ChristianW @ChristianW started