A Memo for ECMAScript 5 - The Good, Bad, and Awful Parts
Table of Contents
- Introduction
- Recommended Reading
- The Bad Parts of JavaScript
- Understanding the
thisKeyword - Scope and Hoisting
- Common JavaScript Pitfalls
- Type Coercion and Falsy Values
- Objects and Prototypes
- Functions: Statements vs Expressions
- Best Practices
Introduction
ECMAScript 5 (ES5) introduced many improvements to JavaScript, but the language still retains some problematic features from its earlier versions. Understanding these quirks is essential for writing robust, maintainable code.
This memo covers the "bad" and "awful" parts of JavaScript as identified by Douglas Crockford, along with practical guidance on how to avoid common pitfalls.
Recommended Reading
Essential resources for understanding JavaScript deeply:
- Awful Parts: Appendix A - JavaScript: The Good Parts
- Bad Parts: Appendix B - JavaScript: The Good Parts
The Bad Parts of JavaScript
These features should be avoided or used with extreme caution:
with Statement
The with statement was intended to provide shorthand for accessing object properties but creates ambiguity and performance issues.
// Avoid this
with (obj) {
a = b; // Is 'a' a property of obj or a global variable?
}
// Do this instead
obj.a = obj.b;
eval() Function
eval() executes arbitrary strings as code, creating security vulnerabilities and performance problems.
// Never do this
eval('var x = ' + userInput);
// Use JSON.parse for data
var data = JSON.parse(jsonString);
void Operator
The void operator is confusing and rarely needed. It evaluates an expression and returns undefined.
// Confusing
void 0; // returns undefined
// Clearer alternative
undefined;
Type Coercion
JavaScript's implicit type conversion leads to unexpected behavior:
// Surprising results
'5' + 3; // "53" (string concatenation)
'5' - 3; // 2 (numeric subtraction)
[] + {}; // "[object Object]"
{
}
+[]; // 0
// Use explicit conversion
Number('5') + 3; // 8
String(5) + '3'; // "53"
continue and switch Fall-through
Both can create confusing control flow:
// Always use break in switch
switch (value) {
case 1:
doSomething();
break; // Don't forget this!
case 2:
doSomethingElse();
break;
default:
doDefault();
}
Understanding the this Keyword
The value of this in JavaScript depends on how a function is called. This is one of the most confusing aspects of the language.
Method Invocation
When called as an object method, this refers to the object:
var obj = {
name: 'Object',
getName: function () {
return this.name; // 'this' is obj
},
};
obj.getName(); // "Object"
Using call() and apply()
Explicitly set this using call() or apply():
function greet(greeting) {
return greeting + ', ' + this.name;
}
var person = { name: 'Alice' };
// Using call (arguments listed)
greet.call(person, 'Hello'); // "Hello, Alice"
// Using apply (arguments as array)
greet.apply(person, ['Hi']); // "Hi, Alice"
Constructor Invocation with new
When using new, this refers to the newly created object:
function Person(name) {
this.name = name; // 'this' is the new instance
}
var alice = new Person('Alice');
console.log(alice.name); // "Alice"
Arrow Functions (ES6+)
Arrow functions inherit this from their enclosing scope:
var obj = {
name: 'Object',
delayedGreet: function () {
// Arrow function preserves 'this'
setTimeout(() => {
console.log(this.name); // "Object"
}, 100);
},
};
Scope and Hoisting
Variable Hoisting
Variable declarations are "hoisted" to the top of their scope:
function example() {
console.log(x); // undefined (not ReferenceError)
var x = 5;
console.log(x); // 5
}
// Equivalent to:
function example() {
var x; // Declaration hoisted
console.log(x); // undefined
x = 5; // Assignment stays in place
console.log(x); // 5
}
Function Hoisting
Function declarations are fully hoisted:
sayHello(); // Works! "Hello"
function sayHello() {
console.log('Hello');
}
// But function expressions are NOT hoisted
sayGoodbye(); // TypeError: sayGoodbye is not a function
var sayGoodbye = function () {
console.log('Goodbye');
};
Common JavaScript Pitfalls
Global Variables
Undeclared variables become global:
function badFunction() {
x = 10; // Creates global variable!
}
// Always use var, let, or const
function goodFunction() {
var x = 10; // Local variable
}
Semicolon Insertion
JavaScript automatically inserts semicolons, sometimes incorrectly:
// This returns undefined, not the object!
function getObject() {
return;
{
name: 'value';
}
}
// Correct way
function getObject() {
return {
name: 'value',
};
}
Reserved Words
Some words cannot be used as identifiers:
// These will cause errors
var class = "CSS"; // Error
var return = 5; // Error
// Object properties are safer but still problematic
var obj = {
class: "CSS" // Works in ES5+
};
The typeof Operator
typeof has some quirks:
typeof []; // "object" (not "array"!)
typeof null; // "object" (historical bug)
typeof function () {}; // "function"
typeof undefined; // "undefined"
// Use Array.isArray() for arrays
Array.isArray([]); // true
parseInt() Issues
Always specify the radix:
parseInt('08'); // 0 in older browsers (octal)
parseInt('08', 10); // 8 (always use radix!)
Floating Point Precision
JavaScript uses IEEE 754 floating point:
0.1 + 0.2; // 0.30000000000000004 (not 0.3!)
// For currency, use integers (cents)
var cents = 10 + 20; // 30
var dollars = cents / 100; // 0.30
NaN (Not a Number)
NaN is not equal to itself:
NaN === NaN; // false!
// Use isNaN() to check
isNaN(NaN); // true
Number.isNaN(NaN); // true (ES6, more reliable)
The + Operator
The + operator is overloaded for addition and concatenation:
5 + 3; // 8 (addition)
'5' + 3; // "53" (concatenation)
5 + '3'; // "53" (concatenation)
// Be explicit
5 + Number('3'); // 8
String(5) + '3'; // "53"
Type Coercion and Falsy Values
Falsy Values in JavaScript
These values evaluate to false in boolean context:
false
0
"" (empty string)
null
undefined
NaN
Truthy Gotchas
Some values are truthy but might surprise you:
Boolean([]); // true (empty array)
Boolean({}); // true (empty object)
Boolean('0'); // true (non-empty string)
Boolean('false'); // true (non-empty string)
Using hasOwnProperty
Always use hasOwnProperty when iterating:
var obj = { a: 1, b: 2 };
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key, obj[key]);
}
}
Objects and Prototypes
Prototype Inheritance
JavaScript uses prototypal inheritance:
var animal = {
speak: function () {
console.log('Sound');
},
};
var dog = Object.create(animal);
dog.speak = function () {
console.log('Bark');
};
dog.speak(); // "Bark"
Object.create() for Inheritance
Prefer Object.create() over constructor patterns:
// Using Object.create (cleaner)
var parent = {
greet: function () {
return 'Hello';
},
};
var child = Object.create(parent);
child.greet(); // "Hello"
Functions: Statements vs Expressions
Function Declaration (Statement)
// Hoisted - can be called before declaration
sayHello();
function sayHello() {
console.log('Hello');
}
Function Expression
// NOT hoisted - must be defined before use
var sayHello = function () {
console.log('Hello');
};
sayHello();
Named Function Expression
// Useful for recursion and debugging
var factorial = function fact(n) {
return n <= 1 ? 1 : n * fact(n - 1);
};
Best Practices
Avoid These Features
| Feature | Why Avoid | Alternative |
|---|---|---|
with |
Ambiguous scope | Direct property access |
eval() |
Security risk | JSON.parse() |
== |
Type coercion | === (strict equality) |
++/-- |
Can be confusing | += 1 or -= 1 |
| Bitwise operators | Confusing in JS | Explicit operations |
Typed wrappers (new String()) |
Creates objects, not primitives | Use literals |
Always Do These
- Use strict mode:
"use strict"; - Declare variables: Always use
var,let, orconst - Use
===: Avoid loose equality - Specify radix:
parseInt(str, 10) - Use semicolons: Don't rely on automatic insertion
- Use
hasOwnProperty: When iterating withfor-in
Code Example: Modern ES5 Pattern
'use strict';
var MyModule = (function () {
// Private variables
var privateVar = 'secret';
// Private function
function privateFunction() {
return privateVar;
}
// Public API
return {
publicMethod: function () {
return privateFunction();
},
};
})();
MyModule.publicMethod(); // "secret"
Conclusion
ECMAScript 5 is a capable language, but understanding its quirks is essential for writing reliable code. Key takeaways:
- Avoid the "bad parts":
with,eval, loose equality - Understand
this: Know how function invocation affectsthis - Be explicit: Use strict equality, specify radix, declare variables
- Use strict mode: Catches common mistakes early
- Learn prototypes: Understand JavaScript's inheritance model
These practices will help you write cleaner, more maintainable JavaScript code.