JavaScript: The Good Parts

by

  • On Amazon
  • ISBN: 978-0596517748
  • My Rating: 6/10

JavaScript: The Good Parts is, as the title indicates, about the good parts of the JavaScript programming languages, i.e. about the parts of the language you should use.

The book is like the language it covers: it has good and "bad" parts. While I like it that the book is small and dense, it still contains unnecessary stuff like all those railroad diagrams used to visualize things a programmer should already know from other languages.

My notes

Good Parts

Most programming languages contain good parts and bad parts. I discovered that I could be a better programmer by using only the good parts and avoiding the bad parts. After all, how can you build something good out of bad parts?

In JavaScript, there is a beautiful, elegant, highly expressive language that is buried under a steaming pile of good intentions and blunders.

JavaScript is an important language because it is the language of the web browser. Its association with the browser makes it one of the most popular programming languages in the world.

JavaScript is built on some very good ideas and a few very bad ones. The very good ideas include functions, loose typing, dynamic objects, and an expressive object literal notation. The bad ideas include a programming model based on global variables.

JavaScript is Lisp in C's clothing.

JavaScript is a loosely typed language, so JavaScript compilers are unable to detect type errors.

A controversial feature in JavaScript is prototypal inheritance. JavaScript has a class-free object system in which objects inherit properties directly from other objects.

Despite its deficiencies, JavaScript is really good.

Grammar

Obsolete comments are worse than no comments.

Block comments are not safe for commenting out blocks of code, the following example causes a syntax error:

/*
var rm_a = /a*/.match(s);
*/
So, it is recommended that /* */ comments be avoided and // comments be used instead.

It is not permitted to name a variable or parameter with a reserved word. Worse, it is not permitted to use a reserved word as the name of an object property in an object literal or following a dot in a refinement.

Unlike most other programming languages, there is no separate integer type, so 1 and 1.0 are the same value. This is a significant convenience because problems of overflow in short integers are completely avoided, and all you need to know about a number is that it is a number.

The value NaN is a number value that is the result of an operation that cannot produce a normal result. NaN is not equal to any value, including itself. You can detect NaN with the isNaN(number) function.

Unlike many other languages, blocks in JavaScript do not create a new scope, so variables should be defined at the top of the function, not in blocks.

Falsy values:

  • false
  • null
  • undefined
  • The empty string ''
  • The number 0
  • The number NaN
All other values are truthy.

for in enumerates the property names (or keys) of an object. On each iteration, another property name string from the object is assigned to the variable. It is usually necessary to test object.hasOwnProperty(variable) to determine whether the property name is truly a member of the object or was found instead on the prototype chain:

for (myVar in obj) {
  if (obj.hasOwnProperty(myVar)) {
    ...
  }
}

The values produced by typeof are 'number', 'string', 'boolean', 'undefined', 'function', and 'object'. If the operand is an array or null, then the result is 'object', which is wrong.

Objects

An object is a container of properties, where a property has a name and a value.

JavaScript includes a prototype linkage feature that allows one object to inherit the properties of another.

Object literals provide a very convenient notation for creating new object values. An object literal is a pair of curly braces surrounding zero or more name/value pairs:

var empty_object = {};

var stooge = {
  "firstname": "Jerome",
  "lastname": "Howard"
};

The quotes around a property's name in an object literal are optional if the name would be a legal JavaScript name and not a reserved word.

A property's value can be obtained from any expression, including another object literal.

Values can be retrieved from an object by wrapping a string expression in a [ ] suffix. If the string expression is a string literal, and if it is a legal JavaScript name and not a reserved word, then the . notation can be used instead. The . notation is preferred because it is more compact and it reads better:

stooge["firstname"]
stooge.firstname

The undefined value is produced if an attempt is made to retrieve a nonexistent member.

The || operator can be used to fill in default values: var middle = stooge["middlename"] || "(none)";

A value in an object can be updated by assignment. If the property name already exists in the object, the property value is replaced. In the other case, the object is augmented.

Objects are passed around by reference. They are never copied.

Every object is linked to a prototype object from which it can inherit properties. All objects created from object literals are linked to Object.prototype.

The prototype link is used only in retrieval. If we try to retrieve a property value from an object, and if the object lacks the property name, then JavaScript attempts to retrieve the property value from the prototype object, and so on. If the desired property exists nowhere in the prototype chain, then the result is the undefined value. This is called delegation.

The prototype relationship is a dynamic relationship. If we add a new property to a prototype, that property will immediately be visible in all of the objects that are based on that prototype.

The for in statement can loop over all of the property names in an object. The enumeration will include all of the properties – including functions and prototype properties that you might not be interested in – so it is necessary to filter out the values you don't want. The most common filters are the hasOwnProperty method and using typeof to exclude functions.

The delete operator can be used to remove a property from an object. It will remove a property from the object if it has one. It will not touch any of the objects in the prototype linkage.

One way to minimize the use of global variables is to create a single global variable for your application. That variable then becomes the container for your application:

var MYAPP = {};
MYAPP.stooge = {
  "firstname": "Joe",
  "lastname": "Howard"
};

Functions

The best thing about JavaScript is its implementation of functions. It got almost everything right.

Functions in JavaScript are objects. Function objects are linked to Function.prototype. Every function is also created with two additional hidden properties: the function's context and the code that implements the function's behavior.

In addition to the declared parameters, every function receives two additional parameters: this and arguments. The this parameter is very important in object oriented programming, and its value is determined by the invocation pattern. There are four patterns of invocation in JavaScript: the method invocation pattern, the function invocation pattern, the constructor invocation pattern, and the apply invocation pattern. The patterns differ in how the this parameter is initialized.

There is no runtime error when the number of arguments and the number of parameters do not match. If there are too many argument values, the extra argument values will be ignored. If there are too few argument values, the undefined value will be substituted for the missing values. There is no type checking on the argument values: any type of value can be passed to any parameter.

When a function is stored as a property of an object, we call it a method. When a method is invoked, this is bound to that object.

When a function is not the property of an object, then it is invoked as a function: var sum = add(3, 4); When a function is invoked with this pattern, this is bound to the global object. This was a mistake in the design of the language. A consequence of this error is that a method cannot employ an inner function to help it do its work because the inner function does not share the method's access to the object as its this is bound to the wrong value. The workaround is for the method to define a variable and assign it the value of this, then inner function will then have access to this through that variable. By convention, the name of that variable is that.

If a function is invoked with the new prefix, then a new object will be created with a hidden link to the value of the function's prototype member, and this will be bound to that new object. Functions that are intended to be used with the new prefix are called constructors. By convention, they are kept in variables with a capitalized name.

Because JavaScript is a functional object-oriented language, functions can have methods. The apply method lets us construct an array of arguments to use to invoke a function. It also lets us choose the value of this. The apply method takes two parameters. The first is the value that should be bound to this. The second is an array of parameters.

JavaScript does have function scope. That means that the parameters and variables defined in a function are not visible outside of the function, and that a variable defined anywhere within a function is visible everywhere within the function. It is best to declare all of the variables used in a function at the top of the function body.

Some methods do not have a return value. If we have those methods return this instead of undefined, we can enable cascades. In a cascade, we can call many methods on the same object in sequence in a single statement.

Inheritance

Patterns of code reuse are extremely important because they have the potential to significantly reduce the cost of software development.

In classical languages, objects are instances of classes, and a class can inherit from another class. JavaScript is a prototypal language, which means that objects inherit directly from other objects.

Arrays

An array is a linear allocation of memory in which elements are accessed by integers that are used to compute offsets. Arrays can be very fast data structures. Unfortunately, JavaScript does not have anything like this kind of array. Instead, JavaScript provides an object that has some array-like characteristics.

Array literals provide a very convenient notation for creating new array values. An array literal is a pair of square brackets surrounding zero or more values separated by commas: var numbers = ['zero', 'one', 'two']; The first value will get the property name '0', the second value will get the property name '1', and so on.

In most languages, the elements of an array are all required to be of the same type. JavaScript allows an array to contain any mixture of values.

Every array has a length property. It is the largest integer property name in the array plus one. This is not necessarily the number of properties in the array.

Since JavaScript's arrays are really objects, the delete operator can be used to remove elements from an array: delete numbers[1]; // numbers is ['zero', undefined, 'two']. Unfortunately, that leaves a hole in the array. This is because the elements to the right of the deleted element retain their original names. What you usually want is to decrement the names of each of the elements to the right. Fortunately, JavaScript array have a splice method. It can do surgery on an array, deleting some number of elements and replacing them with other elements.

A common error in JavaScript programs is to use an object when an array is required or an array when an object is required. The rule is simple: when the property names are small sequential integers, you should use an array. Otherwise, use an object.

JavaScript itself is confused about the difference between arrays and objects. The typeof operator reports that the type of an array is 'object', which isn't very helpful.

Regular Expressions

The methods that work with regular expressions are regexp.exec, regexp.test, string.match, string.replace, string.search, and string.split. Regular expressions usually have a significant performance advantage over equivalent string operations in JavaScript.

Regular expressions are best when they are short and simple. Only then can we have confidence that they are working correctly and that they could be successfully modified if necessary.

There are two ways to make a RegExp object. The preferred way is to use a regular expression literal. Regular expression literals are enclosed in slashes. The other way to make a regular expression is to use the RegExp constructor. The RegExp constructor is useful when a regular expression must be generated at runtime using material that is not available to the programmer.

Methods

If you are assembling a string from a large number of pieces, it is usually faster to put the pieces into an array and join them than it is to concatenate the pieces with the + operator.

The sort method sorts the contents of an array in place. However, it sorts arrays of numbers incorrectly because JavaScript's default comparison function assumes that the elements to be sorted are strings. Fortunately, you may replace the comparison function with your own.

Style

Good programs have a structure that anticipates – but is not overly burdened by – the possible modifications that will be required in the future. Good programs also have a clear presentation. If a program is expressed well, then we have the best chance of being able to understand it so that it can be successfully modified or repaired.

JavaScript's loose typing and excessive error tolerance provide little compile-time assurance of our programs' quality, so to compensate, we should code with strict discipline.

Style matters in programming for the same reason that it matters in writing. It makes for better reading.

The likelihood a program will work is significantly enhanced by our ability to read it, which also increases the likelihood that it actually works as intended. It is also the nature of software to be extensively modified over its productive life. If we can read and understand it, then we can hope to modify and improve it.

Sometimes I think about comments as a time machine that I use to send important messages to future me.

When reviewing the features of a language, I pay special attention to features that are sometimes useful but occasionally dangerous. Those are the worst parts because it is difficult to tell whether they are being used correctly.

Beautiful Features

Features can have a negative value to consumers because they make the products more difficult to understand and use.

Designs that just work are much harder to produce than designs that assemble long lists of features.

In designing products and programming languages, we want to get the core features – the good parts – right because that is where we create most of the value.