Friday, December 11, 2015

ES6, arrow functions, semantic this, and Chrome debugger tools

I'm sure at some point parts of this post will be irrelevant. But for now, this captures what I learned and what has helped me.

One of the really nice things about ES6 is arrow functions and semantic this. Arrow functions are just lambda expressions in javascript for callbacks. For example, if I want to use the javascript array filter function, I can write this:

    function isNotNull(item) { return item !== null; }

    var filtered = someArray.filter(isNotNull);

If isNotNull is universally useful, this is a great approach. However, if the function isn't generally so reusable, you could always write it inline:

    let filtered = someArray.filter(function(item) { return item !== null; });

Which is fine. But both of these methods are subject to javascript's arcane rules for what gets passed as the this value to functions. If your callback needs to reference the surrounding context, then you are left using bind to make sure that you have the correct value of this when the function is called, or to adding the ubiquitous let self = this; somewhere before the filter operation.

But arrow functions and semantic this changes all of that. Let's expand the function to look for values in the array that match some member value of the containing class. Something more like this:

    someMemberFunction() {
        var filtered = someArray.filter(function(item) {
            return item === this.memberValue;
        });
    }

This won't work, because when the filter is called, the value of this will not be the surrounding class, it will likely be the array. The traditional solution is:

    someMemberFunction() {
        var self = this;
        var filtered = someArray.filter(function(item) {
            return item === self.memberValue;
        });
    }

It works, but having code littered with stuff just to fix the context is clutter. Now, with ES6, you can write this:

    someMemberFunction() {
        var filtered = someArray.filter(item => item === this.memberValue);
    }

And it works, because of semantic this.

That isn't actually the point of this blog, though. If you use arrow functions, when you step through in the debugger, you will see that inside the context of the arrow function, this is undefined. What's that about?

Well, I use bable as my transpiler. And it turns out that babel does the self = this trick for you in a closure that surrounds the arrow function. So, while this is the current context is undefined, if you look in the surrounding closure, you will see a variable named some variant of _thisn that has the correct value. That value is being used by the transpiled code as the value of this inside the arrow function. But you don't see it if you are using source maps, you just see the beautiful ES6 code, and a this that appears to be undefined.

I admit that I got fooled by this at first, and even today, if you look through my Aurelia apps, you will see a few places where I am setting self = this even though I am using arrow function.

Cleaning that up, though.

2 comments:

Han Li said...

Having the same issue with a lot of 'this' undefined in debugger. Quite annoying.

scott qian said...

I got same issue, thank you for saving my time!