Settings

Theme

Making the Most of the JavaScript Language (2016)

thenorthcode.net

75 points by RobBollons 9 years ago · 46 comments

Reader

moxious 9 years ago

As someone who works with this every day, I'd say this is pretty solid advice.

The "use a more functional approach" and "take advantage of ES6" are by far the biggest for me. ES6 added so much good stuff, I wouldn't consider it optional. It's better than incremental upgrades, feels almost like a different language to me.

Every language has its quirks, and while I'll keep on hating Javascript's, I'll never lose sight of the fact that if you're holding out for the perfect language, you're not going to be doing much coding.

  • noir_lord 9 years ago

    TypeScript does to me (and technically I suppose it is a different language), where it gets interesting is that TS made me a better JS programmer since I could express stuff the way I wanted to and then look at the output.

    The only transpiler I've seen that gets close in terms of quality of output is BuckleScript, that thing is damn near magic but TS has the momentum and adoption.

    TypeScript has been so good and caused me so few problems that I actually look at ways of moving more bits to the frontend, it makes it pleasant to work and I think (personally) the highest praise you can give a language is that it's pleasant.

    • slackingoff2017 9 years ago

      Typescript with TsLint is a godsend on larger codebases. It makes JavaScript into a pretty solid language for big projects.

      Without it you frequently run into objects passed to you after going through 10k lines of code and it becomes a wicked game to figure out which properties the object actually has at that point.

      JavaScript, even ES6, is IMO crap for large codebases.

      Does this object have the property I'm looking for? Did somebody overwrite it with the wrong type 5000 lines ago? Why is the prototype chain broken? Where's my damn inline documentation.

      My company has abandoned JavaScript wholesale with great relief. If it was a physical object we would have tossed it off the balcony.

      • tisdy 9 years ago

        Typescript is essential for large codebases / many devs.

        > My company has abandoned JavaScript wholesale with great relief. If it was a physical object we would have tossed it off the balcony.

        I would like to do the same with our gigantic SPA-ghetti.

        • WorldMaker 9 years ago

          There's no time like the present to start! Typescript's allowJS compiler option and comment-based approach lets you start with the spaghetti you have and slowly transition to Typescript.

          (Though I'm still fan of the "rip off the Band-Aid" approach I used in a few projects back in the day: rename every .js to .ts and then fix compiler errors until things compile. allowJS is a great option now.)

      • Guthur 9 years ago

        The true source of your problems there are mutation, which in my opinion is the primary source of issues. Immutable data structures being the most benefit.

        I also believe dynamic language tooling has regressed.

  • erikpukinskis 9 years ago

    What do you like about ES6? It just looks like syntactic sugar to me, but I'm always interested in re-evaluating.

    Writing [1,2,3].map(function(x) { return x+10 }) really doesn't bother me. I don't see what the benefit is of an arrow function. Are there other things that you like?

    As the OP mentions, the inheritance stuff is just likely to cause architecture headaches. I like JavaScript's concurrency model (single threaded with callbacks) so I don't see any benefit to the async stuff.

    It just seems like people added a ton of complexity to give up a few keystrokes. Transpiling and shimming and adding more control structures make your application more complicated, so there is a real cost. And keystrokes are not generally a big problem when I'm programming. I'm not in a race.

    • geoelectric 9 years ago

      The big deal of an arrow function, brevity aside, is that it inherits the value of 'this' from the scope that creates it. Regular functions do not, so to use them as first-class functions you often have to bind before passing, pass around thisObjs for rebinding, do a 'that = this' type trick, etc.

      Same goes for 'arguments', though that comes up less. Basically, they're much more representative of a simple lambda than a classical function.

      • erikpukinskis 9 years ago

        What's wrong with explicitly binding the scope? I really like that in JavaScript you have to be explicit about that kind of thing.

        • haimez 9 years ago

          I think the problem with "this" is that you are trying to recreate classes instead of passing it as a parameter, or using currying. Arrow functions might be slightly more intuitive to intention, but the problem is still there. The implicit nature of "this" in JS is always worse than any explicitly controlled alternative.

        • mercer 9 years ago

          The syntax of the arrow function does a great job of making this explicit too. On top of that it offers 1) brevity/ease of use, and 2) standardization.

          From my experience at least, binding to the outer/creating scope is something I do very often, and as far as I know my use of JS is pretty idiomatic.

          Concerning 1, being able to use terser, clearer syntax for something I do so often is a huge win, and in regards to 2, it's nice to see an arrow function and to immediately know what this means, and hella better than having to search for a .bind(), var self = this, var that = this, Car.method(), etc, all equally valid as an approach, and depending entirely on the whims of the programmer behind this code.

        • geoelectric 9 years ago

          I actually find it much more clear for this to be set at the place you see the function defined in code--assuming you know what this is in the wrapping scope, you know exactly what it will be in this scope. Technically a bind is more explicit but in practice the bind tends not to happen where the function is defined so you have to go fishing around the code path for it.

          And honestly, the sugar often speaks to readability as well. Compare myArray.every(function (x) { return x; }); with myArray.every(x => x).

          • erikpukinskis 9 years ago

            The first is more readable. It uses more common tokens, and is therefore more easily recognizable. You're confusing brevity for readability.

            I don't understand your scope preference. These seem the same to me:

              family.findEldestFirst(person => this.isRoyal() && person.name == "Kate")
            
            vs

              family.findEldestFirst(isPrincess.bind(family))
            
              function isPrincess(person) {
                return this.isRoyal() && person.name == "Kate"
              }
            
            11 tokens vs 16, same order of magnitude. Second one has an explicit return, which is good for readability. Shorter lines, to which is better for readability. Also relies on fewer control structures, which is good for readability. No need to understand the =>/-> distinction, which is very subtle and beginners won't know about it. To me I'd rather just learn functions and closures and be done with it.

            ... And frankly I think using "this" that way in either case is not a win. I'd rather make family its own argument to isPrincess:

              family.findEldestFirst(isPrincess.bind(null, family))
            
              function isPrincess(family, person) {
                return family.isRoyal() && person.name == "Kate"
              }
            
            Mm. Dat explicitness. Anyway... I think I'm not understanding you. How is this forcing your hand in terms of where you bind the scope?

            Franky, my sense is that the people who like ES6 and promises are people who are just allergic to writing named functions. Named functions make callback hell go away, and they solve all the problems these arrow functions do. Somehow people think it's bad form to define lots of functions. But defining functions is my job. :) It doesn't bother me.

            My sense is it's just Rubyists who miss Ruby and think "more Rubyish" is better. I sympathize because my path was BASIC -> PHP -> C# -> Ruby -> JavaScript. But I think it is a mistake to try to bring your old idioms to a new language.

            ... and if I wanted to reduce character count above all else, I'd just go use PERL.

    • olalonde 9 years ago

      > It just seems like people added a ton of complexity to give up a few keystrokes.

      It's either "only syntactic sugar" or it "added a ton of complexity". To be honest, you sound like someone who's bitter because all the hard stuff you had to learn to deal with is now super simple and intuitive.

      > I like JavaScript's concurrency model (single threaded with callbacks) so I don't see any benefit to the async stuff.

      This sentence also demonstrates you either don't understand the concurrency model or async/await (async/await doesn't change the concurrency model).

      The syntactic sugar isn't only there to save keystrokes, it also makes code easier to read and is less prone to errors.

    • whipoodle 9 years ago

      ES6 is not just syntactic sugar, in fact arrow functions themselves are not only syntactic sugar- they bind `this` lexically, which is different to how `function() {}` works. Symbols, generators... the list goes on.

      I won't disagree about the added complexity.

    • k__ 9 years ago

      sure, if I would write code like that it wouldn't bother me too.

          const add = x => y => x +y;
          ...
          [1,2,3,4].map(add(1));
      
      now we are talking ;)
whatever_dude 9 years ago

What I can't recommend enough (and hinted at in the article) is for someone to use a transpiler like Babel (and core-js, babel-preset-env, and etc). There's no reason not to use many of the new features and new functions of ES2015+, and you can do that right now even if you're targeting old browsers with transpilation and a bit of automatic shimming.

  • moxious 9 years ago

    Transpilation is wonderful but it does add a lot of complexity to the build chain. I'm with you the language features are worth it but it's just a quirk of developing for browsers that they don't move as fast as JS itself and that all of this extra machinery is necessary.

    • noir_lord 9 years ago

      Not sure that's true anymore with TypeScript.

      They made a real focus on tooling and it's been widely adopted.

      In intellij it's as simple as turning on the option and creating a .ts file, the identically named js file is then dropped in the same directory (by default) and you just include that.

      It's about 2 minutes from start to finish to setup.

      Then it's a natural progression to using https://www.typescriptlang.org/docs/handbook/tsconfig-json.h...

      I mean sure if you are going with a split bundle, hot code reloading and tree shaking via webpack then there is a high start cost but ts on it's own is rather fantastic.

      vscode does the same thing as intellij if you want to try it without downloading intellij.

      • mercer 9 years ago

        > Not sure that's true anymore with TypeScript.

        Excellent point, and one of the less-advertised benefits of using TypeScript.

        I tend to use Babel/Webpack in most of my projects, mostly because I have the time to waste on keeping up with the 'bleeding edge' and I kind of enjoy it (in perhaps a slightly masochistic way).

        That said, more than once I picked TypeScript for some project not primarily because of its main purpose, but rather because it offered a simple way to transpile 'ESNext' to ES5. Despite the fact that a basic Babel/Webpack setup is second nature to me now, it still feels easier to just use TypeScript to do all of it at once, and get types as part of the deal!

        • noir_lord 9 years ago

          Yep and TS makes it trivial to target old JS versions and in 2.4 you can use the type inference on normal JS as well.

          Its all a win.

  • goatlover 9 years ago

    How does debugging in the browser console work when you use that approach?

    • Zyst 9 years ago

      Normally you have mapping files which tell your browser which bits correlate to the outputted "compiled" code.

      Thus in your browser you see the code you write, you put in breakpoints as desired, and everything generally works.

      I don't have much depth as to how it all works behind the scenes though, I'd love to know more.

    • whatever_dude 9 years ago

      Like everyone said it works perfectly well with source maps. You get proper debug errors, break points, etc.

      That said, it is another thing you need to maintaining and depending on the number of transformations your code has to go through, you introduce more points where the source map can break.

gonzofish 9 years ago

JS has some seriously rough edges, but I love it because I adhere to a lot of what the article preaches. I never end up with any gotchas because I use the "right" parts of the language.

fenwick67 9 years ago

Classical inheritance is useful in JS sometimes, but I agree with the sentiment of "don't use it unless you have a reason to".

  • jonny_eh 9 years ago

    Ya, that part I don't agree with. Especially since he also says to use ES6 features, which includes the new class syntax, making it a lot more straightforward to use classes + inheritance (even if it's still prototypical under the hood).

    • mercer 9 years ago

      I suppose it could be considered more 'general' programming advice, not specific to JS. At least here on HN there seems to be a consensus that classical inheritance more than 1 layer deep is a bad idea (correct me if I'm wrong though, dear HN readers and writers!).

      Personally I'm a huge functional programming weenie for no reason I can coherently defend, so I prefer to avoid ES6 classes. But quite often they end up being the most reasonable solution to my problem, and having syntax for it is great. I just avoid them by default.

romanovcode 9 years ago

While I use `const` for a lot of my variables that I don't want to change it feels super-super wrong.

  • savanaly 9 years ago

    What feels wrong about it?

    • romanovcode 9 years ago

      You wouldn't declare your in-scope variables as `const` in C/C++/C#/Java wold you?

      • clappski 9 years ago

        In C++ I always do, everything is 'const auto &' if it can be. C# const is different, it's more like C++ '#define'.

        Why wouldn't you always go for the most immuatable declaration? Even in C++, where you can have 'const int f(const int y) const;' it's still worth using even though you have to type it a few times.

    • aaron-lebo 9 years ago

      It's an incredibly verbose way to do what you'd prefer to be the default.

      val or con would have been better.

      • WorldMaker 9 years ago

        Going way back to ancient Lisps `let` has been the const declaration in a lot of programming languages. It's too bad ES2015 decided to use `let` for `var`/`val`, because `let` should have been const.

        That said, I've come to terms with const, even if I find I accidentally use let sometimes from time spent in Lisp and F#.

        • kazinator 9 years ago

          let is not a "const declaration" in most major dialects of Lisp, current and historic. In Scheme, Common Lisp, ISLisp, Emacs Lisp and others, it introduces mutable bindings.

      • simonbw 9 years ago

        Is there really much of a difference between "const" and "con"? I get that one is 66% longer than the other, but does it actually affect the readability of the code or the amount of time it takes to write it?

      • BurningFrog 9 years ago

        Yeah, those 60% extra characters do bother me. And the uneven indentation.

        I know some/most people don't care. I can't be one of them.

        Maybe we'll get `val` in ES9.

  • whipoodle 9 years ago

    They're not variables if they don't vary ;)

luord 9 years ago

I disagree on "avoiding classical inheritance", though I didn't drink the functional programming kool-aid so what do I know. That aside, I like the article, JS is my second favorite language and I'v never gotten the hate for it. I'm glad it's improving so much.

Stratoscope 9 years ago

> Closures are a great way to maintain private variables in JavaScript but also seem to be a source of great misunderstanding.

Very true on both counts, but unfortunately the article perpetuates some of this misunderstanding.

> Where it gets interesting is when you return a function from an outer function...

Abbreviated example from the article:

  var outer = function () {
    var a = 1;
    return function inner() {
      return a;
    };
  };
...and that's the only example. No mention of where closures are actually most useful, and not a peep that you don't need a function that returns a function to get one. Any function call can get you a closure.

I deal with this frequently on Stack Overflow. Someone asks a question where a closure is a great solution, and then someone answers with a complicated example involving a function that returns a function.

It seems to come up a lot in Google Maps API code:

  var places = [
    { name:"Test", lat:10, lng:10 },
    ...
  ];

  function initMap()
    var map = new google.maps.Map(...);
    for( var i = 0;  i < places.length;  ++i ) {
      var marker = new google.maps.Marker(...);
      // This is the complicated part:
      marker.addListener( 'click', (function( marker ) {
        return function() {
          // This works because of the closure:
          infowindow.open( map, marker );
        }
      })( marker ) );
    }
  }
It doesn't have to be done that way! You'll get a closure that works just as well if you simply call a function in the loop:

  function initMap()
    var map = new google.maps.Map(...);
    for( var i = 0;  i < places.length;  ++i ) {
      addMarker( places[i] );
    }

    function addMarker( place ) {
      var marker = new google.maps.Marker(...);
      marker.addListener( 'click', function() {
        // We have a closure here too:
        infowindow.open( map, marker );
      });
    }
  }
There are other ways to do this in modern JavaScript (such as using let inside the loop). I'm using old-school JavaScript here just to show that it could be done this simply even in the oldest browsers.

Of course, the same thing can be done with forEach():

  function initMap()
    var map = new google.maps.Map(...);
    places.forEach( addMarker );

    function addMarker( place ) {
      var marker = new google.maps.Marker(...);
      marker.addListener( 'click', function() {
        // We have a closure here too:
        infowindow.open( map, marker );
      });
    }
  }
Or with the forEach callback inline:

  function initMap()
    var map = new google.maps.Map(...);
    places.forEach( function( place ) {
      var marker = new google.maps.Marker(...);
      marker.addListener( 'click', function() {
        // We have a closure here too:
        infowindow.open( map, marker );
      });
    });
  }
The point in each of these cases is that you don't need a function that returns a function to get a closure, but people who answer SO questions perpetuate this myth day after day.
whipoodle 9 years ago

Closures are so nice. Working without them makes everything so much more difficult and inelegant.

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection