Monday, January 31, 2011

JavaScript: The Surprising Parts

This post is a summary of the the things that surprised me as I've been reading several books to update my knowledge of JavaScript (and practices that have changed since 2004/2005).  While this info would have been big news to most and worth writing articles about back then, it begs the question as to why I should blog about it given that now there are several good books available on the subject. There are two self-canceling reasons; (1) this blog will act as a cheat sheet for me, and (2) the very act of writing it will make me more likely not to need a cheat sheet.  While there are many small things that I didn't happen to know (or remember knowing), I am writing here about the things that actually surprised me, and they tended to be either big or small surprises, so I will organize them that way.

Small Surprises
  • [JGP, p.6] undefined is not a reserved word!
  • [JGP, p.107] undefined is not a constant; it is a global variable which can be redefined! (so is NaN).
  • [JGP, p.103] Reserved words can be used as object property names (unless accessed via dot notation)! Even stranger, an empty string is a legal property name! (as in obj[""]=123;
  • [JGP, p.7] integer types are really double under the hood!
  • [JGP, p.10, 102] Nested block statements (i.e. {...}) do not define a new variable scope!
  • [JGP, p.29] If a method is called as a simple function, this is not undefined but rather defined as the global object!
  • [JGP, p.36, 102] Local variables exist before they are declared! (which is why they may as well all be declared at the beginning of a function)
  • [JGP, p.105] The arguments array is not an array! (For that matter, arrays are not arrays!)
  • [JGP, p.109] The == operator does type coercion and the === operator does not. But the type coercion is done so poorly that it is too dangerous to use == at all in general!
  • [JGP, p.102] The return statement will return undefined if the return value expression does not start on the same line as the return keyword!
  • [HPJ, p.2] Browsers block loading anything else while the JavaScript inside a script tag executes! 
  • [HPJ, p.7] Scripts can be dynamically loaded by creating script tags via DHTML, and those scripts don't block loading other elements (like images, etc).
  • [HPJ, p.20] Global variables are not the fastest to access (compared to variables in other scopes), as would be the case in compiled languages, but are in fact the slowest since all the other scopes are searched first.
  • [JGP, p.112, HPJ, p.156] Bitwise operators are either really fast, or really slow, depending on who is judging. Crockford, in Appendix B: Bad Parts, say bitwise operators are "very slow", whereas, Zakas, in Use the Fast Parts, say bitwise operators are "incredibly fast"!

Large Surprises
  • [JGP, p.37, HPJ, pp.21-26] Closures imply Cactus Stacks.  I didn't really understand in a deep way what closures were, and JGP didn't help (nor any other JavaScript articles/books for that matter). After all, Pascal had nested function definitions where the inner procedures had access to the outer local variables, but that didn't make closures.  What really made me understand was realizing that JavaScript was like Scheme with regard to closures. (See JavaScript is Scheme not Java below). As I wrote in a comment to someone else's blog attempting to explain JavaScript closures...
    I think it would be more accurate to say right up front that our normal notion of a “stack” (as used to hold parameters and local variables) does not apply in languages like JavaScript (and Scheme, etc). Unlike Pascal, Ada, C, C++, Java, etc., JavaScript has only “heap with garbage collection"; all those “stack frames” to hold parameters and local variables are just more objects on the heap. When no more references to each exist, they individually get garbage collected. This means that there is no special “saving the stack frame” after the outer function has returned for closures to use…they all just get garbage collected like anything else does when all references to it are deleted.
    BTW, the only way I ever grokked this was after learning Scheme by watching the UC-Berkeley Comp Sci 61A class on iTunes U. Except for the EARRRRLY Fortran on CDC supercomputer days, where recursion was not supported and the hardware instruction set did not implement stacks, I had not experienced a language that wasn’t fundamentally stack-oriented (well ok, there was that funny Lisp language in the exotic languages class). The 61A class video explained (with nice pictures) how the “stack frames” are instead “environments” that chain to each other (just like JavaScript objects chain via Prototypes). There is a global environment with the global vars, plus a new environment for each function (to hold params and local vars), and instead of a stack of frames on a hardware stack, it is a linked list of environments on the heap.
    It probably also requires explaining right up front that unlike the Pascal, Ada, C, Java, etc languages (where functions are defined at COMPILE-TIME), JavaScript, Scheme, etc languages define functions at RUN-TIME. Because of this, there is always an "environment" that is active at the time of function creation/definition, namely, the call-stack that exists while the code creating a function is executed. [EXCEPT, of course that there is no stack... There is a great way to visualize how the stack is really a tree at].
    A reference to this “current environment” chain is saved as a part of the function definition. In Java-like languages this makes no sense because there is no “current stack or environment” at compile time when the function was defined/compiled.
    SO, when a function gets called, instead of “pushing” its params/local vars on top of THE stack, it “pushes” it on top of an environment chain that was saved with the function definition. And since it isn't a real (hardware) stack, but actually sitting in the heap, the function can refer to “local variables” of its “outer” function long after that outer function has returned.
  • [JGP, pp.40-57]  JavaScript is Scheme not Java.  Having accidentally watched the entire 61A course that teaches Scheme and functional programming (mentioned above) before ever reading JGP, I immediately recognized the exotic techniques of JGP chapter 4 (currying, etc) as paralleling the topics in 61A. [BTW, I originally watched 61A because I wanted to learn about this Hadoop buzzword I kept coming across in Job Ads. When I looked it up and saw that it had something to do with Map/Reduce (whatever that was), and that this iTunes class had a section on Map/Reduce, I tried to watch just those lessons. It soon became clear that I needed to start from the beginning of the course to learn Scheme itself.] Learning the culture and toolkit of functional programming, which goes way beyond simply using function pointers or having functions be objects, I could see that the new style of programming JavaScript was not very strange Java, but fairly normal Scheme.  Back in 2005, I was aware of, but avoided, many of those alternate ways that JavaScript could be twisted to define classes, objects, functions, methods, inheritance, etc, BECAUSE, why would you want to make your  JavaScript code look so un-Java-like!?! If one embraces JavaScript as Scheme, it is not a weird syntax but simply a different programming paradigm: functional programming with lambdas and lexical scoping.

    One of the many differences between functional and non-functional programming is the cognitive "weight" of functions.  In Fortran thru Java (and JavaScript as I had been using it), methods and functions are fairly heavy weight things that get full blown API documentation (e.g. JavaDoc/jsDoc), and lots of whitespace around them, and are mostly not defined in a nested scope.  In functional programming, functions definitions are like water, and are created, used, and abandoned in as casual a fashion as Strings are in "normal" languages. Function definitions within function definitions in Scheme are as common as string concatenations in Basic. SO, one has to get over the feeling that it is expensive overkill to define a function just to wrap another function so that a one variable closure can be created.
[JGP] Crockford, Douglas. JavaScript: The Good Parts, O'Reilly, 2008
[HPJ] Zakas, Nicholas. High Performance JavaScript, O'Reilly, 2010

No comments: