I've been meaning to read JavaScript: The Good Parts by Douglas Crockford for over a year. It's been sitting on my stack of shame (stack of programming books I have every intention of reading) collecting dust for MONTHS. It's an understatement to say that I'd been doing myself a disservice to not have read it sooner. Crockford's book provides a concise yet complete assessment of all of JavaScript's frustrating nuances.
JavaScript is a hastily constructed language that was shipped before it had time to be polished. The web runs on a half-baked language! However, by using a subset of JavaScript, and following a few rules, we can make writing and maintaining JavaScript manageable. The book is a quick and enjoyable read in no small part because Crockford is not afraid to point out the terrible features of the language. In a general sense JavaScript can cause a lot of heartache because its familiar syntax coaxes you into believing it behaves like the languages that syntax came from. This leads to code being written based on incorrect assumptions.
What follows isn't so much a review of the book, but a brief overview of a JavaScript nuances the book describes really well. Specifically, these are items that I see developers new to the language really struggle with. The range of topics covered by the book is extensive, and every chapter is worth reading.
While the syntax of the language implies otherwise, variable scope does not change when code is wrapped in a block with curly braces. Instead a new scope is created when a function is defined. Variables defined within a function are only visible within that function. More importantly though is that variables defined within a function are visible everywhere within the function. This is called variable hoisting.
Basic variable hoisting:
1: 2: 3: 4: 5: 6: 7: 8: |
|
Hoisted variable within a block within a function:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: |
|
Variable declared twice within a function. Once at the beginning of the function, and again within an if statement:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: |
|
Because of variable hoisting Crockford recommends declaring all variables at the beginning of the function. Doing this helps prevent using variables before they're assigned. Variable hoisting is a great example of how JavaScript's familiar syntax can lead to programmatic errors that are either easier to catch or impossible to make in other languages.
The this keyword is a source of great pain in JavaScript. The value of this changes significantly based on how or when it is used.
JavaScript: The Good Parts describes the this keyword as an additional parameter that gets passed in with every function. The value of this depends on how a function is invoked. A JavaScript "bad part" is that the value of this is not necessarily bound to the same scope as the function it's used in.
Changing a globally declared variable within a function:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: |
|
The example above demonstrates that a function defined in the global scope is bound to the global object. The value of this did not change when used inside of a function. this was still bound to the global object scope.
So when does the context of this change? There are two cases: 1. When this is passed in as an argument to the "apply" function, and 2. When a new object is created using the new operator.
In JavaScript functions are objects and just like any other object they can have methods. One of those methods is "apply."
Using apply we can call a function and tell it what this is:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: |
|
The "apply" method allows us to invoke a function, explicitly define what this is bound to within that function, and pass in a list of arguments.
As mentioned above, the context of this does not necessarily change when variable scope changes. The value of this within a function can be, and often is, the same as this in the global scope.
Using the new operator when invoking a function does change the context of this. Functions that are invoked with new are called constructor functions and by convention start with an upper-case letter (PascalCase). Invoking a constructor function without the new operator can cause all sorts of nasty things to happen!
Example of using constructor functions with and without new:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: |
|
In the preceding example, the value of a global variable was accidentally changed when a constructor function was invoked without the new operator.
The new operator changes the behavior of the return keyword too. All functions return something. When that something is not explicitly stated, a function returns undefined. When paired with new, return returns this. That is why, in the previous example, "someOtherThing" was undefined.
The value of this takes on the context of the global object when it is not the property of an object. This also has consequences:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: |
|
The most common way of maintaining access to an object's context within that object is to assign this to a variable:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: |
|
Everyone writing JavaScript should read this book. It has a wealth of information for developers new to the language, and it provides some interesting background and historal context that JavaScript veterans will appreciate. Go buy it!
This is the first post I've ever written and shared on software development. I actually wrote it in February, but I never shared it outside of a couple trusted friends.