Perfection Kills

by kangax

Exploring Javascript by example

← back 4308 words

Professional javascript: review

Few months ago, I promised Nicholas Zakas to review 2nd edition of his “Professional Javascript for Web Developers” (published by Wrox). Nicholas was kind enough to send me a copy of a book, but the lack of time at that moment didn’t let me dive into reviewing it right away. Last week I have decided to finally get together and fulfill a long-standing promise.

Here it goes:

What I liked

  • Technically correct most of the time (many other books, for example, have much more errors and misconceptions).
  • Follows specs and standards closely, uses proper terminology.
  • Covers great variety of topics.
  • Promotes good practices, both — javascript and general programming ones.

What I didn’t like

  • Often oversimplified. Covers only about 85% of core language.
  • Browsers behavior and bugs are often talked about without specifying actual versions (*)
  • Uses unnecessary browser sniffing.
  • Information about ECMAScript 5 is heavily outdated.

(*) Because of this, some statements turn out to be plain wrong. For example, there is — “When defining an object via object literal notation, the Object constructor is never actually called (except in Firefox).”. Yet, this only happens in earlier versions of Firefox (<= 2, IIRC). Then there’s — “The children collection is supported in all browsers except Firefox.” — however, Firefox 3.5 does in fact have working children. Unfortunately, there are few more of such misleading assertions.

General nitpicks

  • Not all examples work in Safari 2.x (although book claims they do)
  • Many methods are not written defensively (e.g. assuming that certain methods/properties exist before accessing them)
  • Use of new Array() instead of [] (probably for clarity)
  • Mix of HTML and XHTML (ideally, it would be nice to drop XHTML for well known reasons)
  • Variables are sometimes initialized with (redundant) null

Verdict

The book is a solid foundation for anyone serious about Javascript. Even though not perfect, it is probably one of the best ones on the market at the moment; The quality of so many other books is simply horrendous. I wish “Professional Javascript” would cover things in more details and employed defensive techniques, or even better — had defensive mindset. However, the fact that it doesn’t only leaves room for future improvement. I would rate it as 7/10.

If anyone is interested in more detailed ramblings review, here it is — all broken down by chapters. Enjoy.

Chapter 2

Chapter 2 is about Javascript in HTML. It covers script and noscript elements, document modes and the way scripts are embedded into web pages. It’s nice to see good practices being promoted here: not using html comments in scripts, preferring external scripts to inline ones as well as script elements positioning in the document for best performance. One glaring mistake in this chapter is mention of “</script>” terminating script element, and how “</scr” + “ipt>” should be used instead. This is not really true. What terminates script element in HTML4.01 is actually “</”; and to work around it, it’s usually recommended to break tags as “<” + “/script>” or “<\/script>”.

When explaining CDATA markers, I wish author mentioned that it is mime-type that affects document nature. Prepending document with XHTML doctype — contrary to a popular myth — doesn’t make document true XHTML. Serving it with application/xhtml+xml does. This is important because CDATA markers are now being blindly injected all over the web into documents served as HTML (but with XHTML doctype).

Chapter 3

Chapter 3 gives a brief, yet in-depth overview of language basics. It’s a pretty friendly introductory chapter. At times, it feels like a couple more things could be added to explanations. Section on number conversion, for example, mentions Number and parseInt but misses unary plus (+) operator. Same goes for string conversion — so commonly used something + '' solution is missing.

Some things in this chapter sound questionable. For example, it was strange to hear that “Logically, a null value is an empty object pointer”, that “The value undefined is a derivative of null” and that var o = new Object; //legal, but not recommended. I also didn’t agree with the assertion that ”[…] ECMAScript’s data types have dynamic aspects that make other data types unnecessary” — proper hash table would certainly be a useful addition to a language (since prototypal nature of objects in ECMAScript makes it a rather non-trivial task). There’s an insignificant mistake about undeclared variable — “Only one operation can be performed on an undeclared variable: you can call typeof on it.” — is not technically true; delete operator will not throw ReferenceError either when given undeclared identifier.

Another thing I wish author gave more attention to is the notion of native vs. host objects. Instead, there’s only a very brief section about IE host objects — “The Internet Explorer (IE) implementation of JavaScript has a slightly different approach to JavaScript objects. In IE, only developer-defined objects inherit from Object. All Browser Object Model (BOM) and Document Object Model (DOM) objects are represented differently and so may not have all of the properties and methods of Object.” Unfortunately, IE is not the only browser whose environment has “misbehaving” host objects. It would be nice to see proper explanation of true nature of host objects and caution about how unpredictable they really are (and, more importantly, are allowed to be, by specification).

Chapter 4

Chapter 4 presents simple and clear explanation of variables, as well as so-often-crucial difference between objects and primitives. It’s nice to see argument passing (a source of so many confusions) explained correctly and thoroughly. Another pleasant surprise is Activation/Variable objects being referenced by their original names (contrary to what so many other authors like to do). Undeclared assignments are recommended against, however there’s no mention of related IE bugs, nor is there an explanation of the difference between variable declaration in global context and undeclared assignments (which might not be very important, after all). Explanation of garbage collection is a nice ending of the chapter.

On the down side, some things are only touched upon briefly and some are plain incorrect.

Note on typeof operator is overly generic and misleading — ”[…] When used on a regular expression, typeof incorrectly returns “function” as well.” typeof does in fact return “function” for regex objects but only in some environments — definitely not everywhere. The reason this happens is because in those environments regex objects actually implement internal [[Call]] method. By specification, native objects implementing [[Call]] should return “function” when applied typeof to, so implementations that do this are actually very much conforming.

Another incorrect statement is — “Even though there are only two types of execution contexts […]”. Third overlooked type of execution context is, of course, eval one. Since eval context is not mentioned, its semantics are not mentioned either (however, eval code is being discussed later when talking about global eval method).

Finally, author talks about scope augmentation and how both with and catch affect execution context — “Certain statements cause a temporary addition to the front of the scope chain that is later removed after code execution. There are two times when this occurs […]”. What’s unfortunately missing here is mention of named function expressions which result in scope augmentation as well. That is a third — perfectly valid — scenario of scope augmentation.

Chapter 5

Chapter 5 is about reference types — Object, Array, Date, RegExp, etc. As always, each type is discussed in great detail and with plenty of corresponding examples. It’s an excellent overview of these language types. The chapter also emphasizes what to avoid, such as trailing commas after last value in array and object literals. However, there are few ambiguous statements and, as with other parts of the book, some things seem to be a bit oversimplified.

As an example, author says — “There are two ways to create an instance of Object”. What goes unnoticed is that those are two explicit ways to create an object, and that there’s also an implicit one via function contsructor, e.g.: new (function(){ this.foo = 'bar'; });.

A slight ambiguity is encountered when talking about object literals — “In this example, the left curly brace ({) signifies the beginning of an object literal because it occurs after an assignment operator (in any other context, the left curly brace indicates the beginning of a block statement).” This phrase can be understood as if braces constitute object literal only when it is on the right hand side of assignment expression (as in this case). It would be better if instead author mentioned that braces constitute object literal when in expression context (and block — when in statement context); few examples of expression and statement contexts would be helpful too.

There’s no mention of number literals being valid production in property names of object literals (i.e. — { 5: ‘foo’ }); It was surprising not to see the discussion of dot property access vs bracket-based one (“[” and “]”) — something that I find to be rather important. One notable difference, of course, is that with bracket accessor it is possible to access properties whose names are not valid identifiers, such as “class” or “foo-bar”. When discussing arrays and their methods, it would be nice to see a mention of Array#sort not guaranteed to be stable.

I found section on function expressions vs. function declarations to be very short and simplistic. Although author rightfully describes difference between two in terms of when functions are instantiated (declaration — during variable declaration phase, expression — during actual execution), there are many interesting things that are left untouched: named function expressions and their semantics, use of function declarations in statements and how it should be avoided, infamous NFE bugs in JScript and why naming functions could be beneficial for profiling and debugging. Finally, there’s a rather vague and overgeneralized statement about Safari not understanding named function expressions.

I didn’t like that this value was being called scope, as scope can be misinterpreted with what’s otherwise known as execution context. I would rather see this being called simply as “this value”.

Unfortunately, there was also no mention of JScript bugs with Number.protoype.toFixed and String.prototype.split with regex and capturing groups.

Another misleading assertion was — “Though ECMA-262 doesn’t indicate a way to access the Global object directly […]”. It’s not clear why Mr. Zakas didn’t provide and explain rather ubiquitous way of accessing Global object from within any context — (function(){ return this; })(); — especially crucial in non-browser environments, which might not have global window property.

Chapter 6

Chapter 6 describes OOP in Javascript. It was nice to see author talk about constructors rather than classes, compare various ways of creating reusable objects and explain popular conventions, such as capitalizing constructors. I was a bit disappointed to hear a somewhat vague — ”[…] the this object always points to the Global object (window in web browsers) when a function is called in the global scope.”. Even though technically correct, in my opinion, it would be better to say that function’s this value references Global object when function is called as a function, no matter from which scope — global or local.

The rest of the chapter delves into OOP in great detail, and is sprinkled generously with very helpful and descriptive diagrams. Speaking of prototypal inheritance, Zakas attributes a popular pattern of cloning to Douglas Crockford who wrote an article on this object in 2006. However, I’m not sure why there’s no attribution to Lasse Reichstein Nielsen who presented it 3 years earlier than Crockford, in 2003.

Chapter 7

Chapter 7 is about more complex aspects of ECMAScript — anonymous functions, recursion, closures, private variables and other related goodness.

Unfortunately, it starts somewhat badly by making few erroneous statements about function expressions. First, it says that “function expressions create anonymous functions” (not necessarily). Then it asserts that function(arg0, arg1, arg2){ } is a valid code (not always; it is only “valid” in expression context — such as in assignment or return statements; when in Global code or in function body, this is, of course, a SyntaxError).

Nevertheless, chapter goes on to explain closures quite thoroughly, paying attention to all of the details of how Activation objects are created and initialized, how they constitute scope chain and how this all relates to closures. A minor nitpick is that self-executing anonymous function in few examples are not wrapped with parenthesis; it would be nice if author mentioned this “wrapping” as a good practice and explained why not wrapping functions leads to confusion when scanning through large functions.

The rest of the chapter is informative and in-depth, but unfortunately presents yet another harmful example towards the end — when simulating private variables it uses undeclared assignment to define variable as global one and does so on purpose! This is especially bizarre, given that a bit earlier there’s a clear recommendation to avoid such “pattern”. Undeclared assignment could be easily avoided there, of course.

Chapter 8

Browser Object Model is the topic of this chapter. It talks about window as well as its properties/methods, location, navigator and other “global” members provided by browsers. I have nothing but positive comments here. The only nitpick is with getQueryStringArgs abstraction, which suffers from object being used as a hash table (and have keys “conflict” with Object.prototype.* members).

Chapter 9

This chapter talks about client detection. Fortunately, author explains that capability/feature detection is a more robust approach to browser scripting. However, I feel that some things could be mentioned in more details and under a different angle. For example, there’s no recommendation to test standard methods before proprietary ones, when possible. Neither is there an explanation of typeof being more reliable way of testing host objects presence and/or capabilities. A couple of feature test examples are both rather trivial; I was expecting to see more involved ones. There is however a rather entertaining coverage of userAgent string history and a comprehensive method of parsing that string. Overall, I wish there was more emphasis on the dangers and fragile nature of browser sniffing in this part of the book.

Chapter 10

Chapter 10 describes Document Object Model gradually and throughly. It focuses on properties and semantics of all Node types, and how to work with them. Relationships between nodes are presented very clearly through graphs.

I’m not a fan of convertToArray solution presented there, as it uses rather slow and unnecessary try-catch at run time instead of testing for IE quirk once. It’s also strange to hear rather bold statements such as — “The Element type constructor and prototype are accessible in script in all browsers, including IE as of version 8.” This is far from being true: at least Safari <=2 and Opera <8 don’t provide these “constructors”. It’s very possible that mobile browsers do not expose them either.

Quite shockingly, an example of document.createElement specifics in IE is presented with blatant and completely unnecessary browser sniff. It was ironic to see this “solution” after previous chapter discussed how feature detection should be preferred to browser sniffing whenever possible.

When talking about children there is no mention of bugs in older Safari. contains section, on the other hand, warns about Safari 2.x bugs, but spoils the moment by suggesting to sniff for browser (something that’s, again, unnecessary). An implementation of contains that follows unfortunately turns sniffing advise into reality.

An implementation of getInnerText fortunately tests for features, but fails to mention differences between textContent and innerText.

Implementations of loadScriptString and loadStyleString that follow are excellent examples of proper abstractions, where features are tested and are tested in correct order (however, it would be nice to see alternative example which would avoid run-time try-catch in favor of single load-time test).

Chapter 11

This covers DOM Level 2 and 3; Styles, Traversal and Range modules. Everything presented here is informative and thought-through. Minor nitpick is vague statements about browser bugs and corresponding browser versions. There’s also a warning about floatcssFloat mapping, but nothing about forhtmlFor one.

I liked that most of cross-browser abstractions use feature detection in correct order. One notable example is getBoundingClientRect abstraction which actually shows non-trivial feature test for the first time. The only downside to that solution is that it uses (often slow-to-create) arguments.callee instead of a special variable in a closure — to store temporary offset value.

Chapter ends with a comprehensive overview of Ranges — both DOM and proprietary IE ones.

Chapter 12

This one talks about Events. Capturing, bubbling phases and event flow are illustrated nicely with diagrams. There are sections on DOM Level 0 and Level 2 event handlers, discussing both — DOM and IE event models. Other topics include event object, simulating events and memory considerations.

Unfortunately, scope chain augmentation of intrinsic event handlers is not mentioned. Event abstraction utility is rather simple. When describing events order for dblclick, there’s no mention of IE deviations; instead, these events are claimed to be fired in exact order (presented there) — something that’s rather misleading. Nothing is being said about Opera lack of contextmenu event and when talking about event delegation, there’s no mention of focus/blur events.

It was very nice to see “beforeshow” / “beforehide” events discussed, together with effect of “unload” event handler on page caching. Event model specifics of less traditional devices/clients, such as Wii and iPhone, were touched upon as well.

“Removing event handlers” section is a little disappointing. It makes it sound as if Internet Explorer always leaks memory as pages are navigated, suggesting “unload” event as a remedy. Instead, it would be useful to see actual leak pattern, and explanation of event handlers forming circular references through closures. It would also make sense to mention that attaching event handlers “properly” allows to avoid both — unload cleanup (and so disabled bfcache) and leaks.

Chapter 13

Scripting forms is about… forms, and everything related: from basic things like submitting them to more advanced like rich text editing and serialization. As always, there’s plenty of good recommendations, suggesting to avoid things like form resetting, accessing forms directly on a document, accessing form controls directly on forms, etc.

One rather important aspect I found missing here is the problem of unsafe names in forms. Another insignificant nitpick is that getSelectedText abstraction doesn’t feature test selectionStart on form control, before accessing it (it’s missing in Safari 2.0, for example). On a similar note, an example demonstrating tab forwarding is accessing a text field that might not exist.

This chapter presents a pretty solid function for form serialization, but it’s unfortunately a bit off and incorrectly serializes controls without names.

Chapter 14

This chapter dives into error handling in Javascript, as well as everything that has to do with debugging, including overview of debugging tools on various browsers. It’s an in-depth look at a great variety of error-handling aspects of cross-browser scripting.

Unfortunately, when talking about try-catch, author doesn’t mention rather important IE deviations. When describing EvalError, he doesn’t explain in which cases it might actually occur — something that specification makes very clear — “if value of the eval property is used in any way other than a direct call (that is, other than by the explicit use of its name as an Identifier which is the MemberExpression in a CallExpression), or if the eval property is assigned to, an EvalError exception may be thrown.”. In fact, EvalError can be easily observed in, say, Firefox 3.5 where using eval in a new expression — new eval() — leads to exactly this type of exception.

Another potential source of confusion is with checking value for Array type in one of the examples. It’s being done via instanceof operator and without explanation of why it might not work and what to do instead (note that iframe issue is mentioned later, in Chapter 20, however [[Class]]-based testing is not mentioned even there).

Chapters 15 & 16

— are about XML in Javascript. One discusses XML DOM, XPath and XSLT support in browsers. The other one continues on the similar subject, delving into ECMAScript for XML (E4X). Unfortunately, I’m not very familiar with these aspects of browser scripting, so can’t provide any useful feedback here. Overall, this section of a book looks like a solid foundation for anyone willing to master XML in Javascript.

Chapter 17

— is about Ajax. It talks about XHR objects, Cross-Domain requests, JSON, and Security — explaining each of those from the ground up. It was nice to see non-standard extensions mentioned — XHR’s timeout and ‘progress’ events, Microsoft’s XDomainRequest, etc. One minor thing I didn’t like is that createXHR abstraction used (often expensive) arguments.callee instead of storing variable in a closure.

A slightly confusing explanation is given in eval section where phrases such as “evaluate as data format”, “statement without name”, and “it identifies a value rather than a statement” don’t make much sense; It looks like by “named statement” author really meant “labeled statement”, and by “identifies as a value” — “parsed as expression”.

Chapter 18

— is named “Advanced Techniques” and covers quite few interesting topics — scope-safe constructors, lazy function loading, binding and currying. It also explains timers quite extensively.

Strangely, a pattern of forking function declarations is not covered when talking about lazy-loading functions. That pattern is tremendously useful and is somewhat ubiquitous by now; I’m surprised it’s missing here. bind and curry abstractions are very simple (unoptimized), apparently only demonstrating general ideas behind them.

It would be nice to see more robust event abstraction: with error handling, so that integrity of event handlers is not compromised when one of them errors out. Drag and drop example is presented in a nice encapsulated way - using module pattern, however there is a slight mistake of using indexOf to check if element has certain class.

Chapter 19

— explains everything related to Client-Side storage: cookies, best practices on using them and some of the restrictions. A pretty solid cookie utility is presented there as well. Other things discussed are Internet Explorer user data and DOM Storage.

Unfortunately, it also describes StorageItem interface, which doesn’t exist in Web Storage specs any longer. Some of the localStorage bugs and quirks are not mentioned (quite obviously, since they didn’t even exist at the time of the writing). There’s no recommendation to prefer getItem/setItem to plain property accessors, since latter ones are rather destructive and are not generally safe. Compare:


localStorage['clear'] = 'foo';
localStorage.clear; // 'foo', not a `clear` method

// vs.
localStorage.setItem('clear', 'foo');
localStorage.getItem('clear'); // 'foo'

// the following is currently true in Firefox, but not WebKit (see: https://bugs.webkit.org/show_bug.cgi?id=30996)
localStorage.clear; // `clear` method

Chapter 20

— is about Best Practices. I almost fully agree with everything said here. The chapter is full of valuable advice, both — Javascript and general ones. Not only does it tell you what to do, but also explains why. This is important.

There were moments when I didn’t share same vision as Mr. Zakas, such as the one where he recommends to use comments in places where there are large amounts of code; I believe in a different approach — breaking code into smaller, more understandable chunks, rather than adding comments on top. Another oddity was with example demonstrating decoupling of application logic and event handlers. Ironically, validateValue would both — validate and perform destructive action, changing visibility of an element. This was very unintuitive and is something I would avoid.

When talking about performance, it wasn’t clear why variable access is considered to take O(1) operations, and property lookup — O(N). Both of these have to go through some jumps and hops internally — variable is resolved via scope chain, whereas property — via prototype chain (and only if not found directly on an object). It’s understandable that property lookups are slightly slower, since besides actual property lookup, there’s also an initial “base” variable resolution, but where O(1) and O(N) are coming from was a mystery.

Chapter 21

This chapter talks about Upcoming APIs, namely Selectors API and those from HTML5. Describing specifications that are in a state of draft is a risky business, which author rightfully mentions upfront. This section of a book is a nice cursory overview of most of the upcoming goodness — querySelectorAll, getElementsByClassName, classList, data attributes, cross-document messaging, media elements, canvas, offline support, history management, and others.

Chapter 22

The finale of the book is The Evolution of JavaScript. It first covers wide range of extensions to JavaScript, from 1.5 to 1.8 — constants, accessors, array extras, array and string generics, block-level scope with let, generators and iterators, as well as few others. The only minor nit here is that examples of array extras had functions with unused parameters.

Unfortunately, a lot of information about ECMAScript 5 here is outdated. It’s even called by its previous name — ECMAScript 3.1.

  • [[Flexible]] attribute used throughout the examples is now called [[Configurable]]
  • [[Getter]] and [[Setter]] attributes are called [[Get]] and [[Set]] accordingly
  • [[Const]] attribute no longer exists
  • Object.clone no longer exists
  • Object.keys doesn’t accept second (sort) argument
  • Function objects don’t have name or parameters properties
  • arguments object is not an instance of Array
  • Decimals do not exist
  • “use subset cautious”; doesn’t exist and is replaced with Use Strict Directive — “use strict”;

There’s also a slight mistake in one of the examples when accessing getPrototypeOf as an instance method, rather than as static one of an Object.

Did you like this? Donations are welcome

comments powered by Disqus