Thursday, February 20, 2014

Lions and Tigers and Javascript (oh my!)

Javascript is awful.

There, I said it. But we're kind of stuck with JS, and it's a good language to know, and it is pretty powerful in a lot of ways. So it's a worthwhile language, but there are a lot of things to watch out for. Since we're doing a project in Javascript, I thought I'd do a post about some of my favorite, and least favorite, things about the language.

The main thing about Javascript to keep in mind is that it was designed for small "script-y" tasks. It was never intended for software development on any kind of major scope, so the core features of the language are built around the novelty of making a website be able to do something, and not to in any way aid the comprehension of the programmer, or the design of large, modular programs. It's a language built on making "clicky button do thing", not software.

Here are some of the things about the language that pop out at me as amazing, terrible, or bizarre (I'll admit, they're mostly gotchas and problems, but there are some wicked cool things you can do with some of these double-edged features):

Object-Oriented to an extreme
This is actually a nice feature. Everything in JS is one of four types: Booleans, Numbers, Strings, and Arrays/Objects. Functions are objects. Arrays and objects are identical, but have different (but interchangable) access patterns. Numbers, booleans, and strings are all actually objects as well, interestingly - they can all be instantiated with the "new" keyword. Once you come to grips with how this works, it can be an incredibly powerful feature, allowing the elimination of tons of work compared to implementing a feature in, say, Java. It's not very secure or powerful in a low level, but it does simplify a lot of things.

Dynamic Types
This is a double edged sword, but I consider it mostly a bad thing. Being able to change the type of an object on the fly without any kind of warning can cause major bugs, like changing "x" from a Number to a String, then performing concatenation instead of addition and wondering why you get "52" instead of "7" when you output x.

Global Scope
Everything in Javascript is global, unless you specify them as local with the "var" keyword. Even then, you can still go through the top-level window element, then follow the crumb trail till you find the appropriate object. Absolutely everything is visible to absolutely everything else, as long as you know where to look. This makes modularity require careful planning, and security laughably nonexistant in JS.

Silent runtime failures
What's worse than a compilation error? A runtime error. What's worse than a runtime error? one that can happen completely silently. Javascript will (depending on the browser and the problem) sometimes spit out errors and then just keep on trucking. Obviously, this can be a major headache. Remember to keep the console open to look for errors when testing, or else silent failures can completely ruin your day.

Javascript supports functional paradigms
That's right! Because functions are objects, you can pass functions around, make lambda expressions, and more. You can make a function foo(function) which takes a function as a parameter, then call it like this "foo(bar)" and have, inside of foo: "function();", which will call "bar". You can even do "foo(function(){ //some code//})" which is a lambda in all but name. BUT before you run off to go write something big using lambdas, you might want to know that...

...Javascript does not have tail call optimization
Why support functional programming and not have tail call optimization? Who knows. Maybe the functional programming support wasn't even intentional, or maybe the tail call optimization interfered with backwards compatibility or the basic language features - the crux of the biscuit is that it doesn't have it, so before you freeze your web browser, don't write anything recursively if you can at all avoid it.

The functional stuff is still useful
Callbacks are contested as bad style by some, but they jive with javascript just fine because of the first-class function business. You can create powerful event-driven systems with callbacks in Javascript with only a few lines of code.

Eval
Just about the most dangerous command in javascript, eval("x") executes x as if it were javascript code. Besides the obvious security loopholes that can arise if you don't scrub your input properly, it's slow, obscures meaning, and is almost never actually necessary.

All Numbers are 64-bit Doubles
This means both that you pay a serious performance price for having them, and that you have absolutely no choice about integer versus floating point numbers, except to use Math.floor()

Bitwise operators are slow
In a bizarre twist, bitwise operators are incredibly inefficient. Don't use bitpacking or bit flags unless you really need to. As a general rule of thumb, trying to be super-efficient in Javascript by using the same techniques you might in C++ or Java is a good way to make things worse.

Functions can return anything, or nothing.
Since everything is an object, you should be careful about what kind of objects you get from functions, and what kind you return. You can have a function that returns a number in some places, a string in others, a complex data structure in one spot, and nothing at all otherwise.

(Global) Functions and variables can be overwritten on-the-fly.
If you redefine a system function, hijinks ensue, and it's perfectly legal. Be careful that the name you're defining isn't already in use! This applies to any variable currently in-scope. Global (top-level) names are always visible. If you overwrite the top-level Math library to say, "foo", you will no longer be able to access any of the Math functions! Make sure to use the "var" keyword to define a scope variable.

'==' and '!=' do Black Magic
They actually convert the type of the object on the left to the type on the right silently before comparing, which can have unexpected results. Instead, use the '===' and '!==' comparisons to test value AND type.

 "new" does not work like in Java
This is good and bad. Prototypal inheritance is powerful, but counterintuitive to Java programmers. The notion is that you can make a "new foo" where "foo" is an object, and the new foo will have all of the properties of foo. You can fake a class system by making all objects intended for inheritance to start with a capital letter. You can also make a chain of classes by inheriting, modifying, inheriting, modifying, etc. This can be dangerous

Objects can have attributes (members) injected at any time
This is pretty cool, in my opinion, but it can be hazardous if used carelessly. You can make a new Foo called x, and then go ahead and say that x.y = "bar", and this new Foo object will now have a field, called y, containing the value "foo". Does this mean that objects of the same class can have radically different contents? ...yes, it does. Use with extreme caution.

This injection allows built-in libraries to be modified
If you decide the Math library needs linear algebra functions, you can just stick them in. Go ahead and say Math.matrixMult = function() { //some code //} and you're off to the races. This is really cool and also really dangerous if used improperly.

classes, functions, objects, methods, and arrays are the same thing
This has already been stated, but let me emphasise: you can make an array, give it some methods, start instantiating new ones with 'new', and call functions that are members ("methods") with the '.' operator. You can even use the '[]' and '.' operators semi-interchangeably. For instance, you can fill an array with a bunch of callback functions using an array syntax, then call "foo[0]()". Weird behavior can occur: strings default to using '[]' to reference a letter. Also, using the '.' syntax versus '[]' breaks down when using variables that have a Number value to reference: if foo = 1, f.foo might be different from f[foo], because f[foo] is interpreted as f[1], while f.foo refers to a field named the object foo. There are other bizarre behaviors. You can absolutely screw up if you're not careful, but this can still be amazingly powerful.

Javascript is not ECMAScript
But, when referring to javascript, most people mean ECMAScript. It is the language specification upon which Javascript is built, but javascript is built by Mozilla/Oracle, and supports features that other browsers don't. So while Javascript has list comprehensions and a boatload of other neat features, Chrome, Safari, and other browsers won't be able to interpret the code.

All functions are variadic
You can at any time pass any number of things to a function.

Careful with parameters
Because there are no type specifiers in Javascript, if you have a function that takes parameters of differing types - especially if there are a lot - they might get the order wrong, and your program will throw errors, possibly silently. Make sure you document all function headers with a type constraint so that users can see what is supposed to be passed, and try to not make a function like "function foo(num1, num2, str, num3, list, num3, str2){...}" because that will cause problems.

Semicolons are optional
foo = 1; is equivalent to foo = 1

Single and double quotes are identical
A char is just a string of size 1. It's strings all the way down.

NaN isn't NaN
Because NaN is a special value meaning not a number, no two "not numbers" can be guaranteed to be equal. Use isNaN(), not == NaN, to test for NaN-ness! This is a very good thing.

Null is an object
I wasn't kidding when I said everything is an object. However, you can't put methods and variables inside null, or change it's value. So at least there's that.

"this" refers to the object  calling a function, not the object containing an object.
In other words, if we have objects A and B, and A.x() passes A.y() to B.z(), like so: B.z(this.y), when we call y, we get an error. why? because inside of B.z, it evaluates it literally; "this.y()". Since B does not contain a function y(), it fails. The standard workaround is to create a local var called "that". Example: "var that = this; B.z(that.y);". This will work, and is considered a standard practice amongst JS programmers.

0, false, '', null, undefined, and NaN all evaluate to false inside of boolean operations
That's right, all of those are "falsy". This can be bad or good, depending on your opinion, but it can lead to unexpected behavior.


This is far from a comprehensive list of all of the problems - err, I mean, the features, of Javascript. The basic  tl;dr is this: it's easy to write horrible, awful, ugly javascript code that won't do what you want it to. Writing good Javascript code is entirely possible, however. I really recommend the book "Javascript: The Good Parts". Once you get past the surprising differences between it and other languages, Javascript can be an excellent language. It might just take a little while to get to that point.






No comments:

Post a Comment

Feel free to comment. If it's spam, I'll remove it. If spam becomes a problem, I'm going to change the comment policy. Till then, it's the wild west.