simpson-you-dont-know-js
Kyle Simpson Style Guide
Overview
Kyle Simpson is the author of the "You Don't Know JS" book series. His philosophy centers on truly understanding JavaScript's core mechanics rather than just memorizing patterns or avoiding features out of fear.
Core Philosophy
"The only way to understand JavaScript is to understand JavaScript."
"Don't fear what you don't understand—learn it."
"Coercion is not evil, it's just misunderstood."
Simpson believes that most JavaScript confusion comes from not understanding how the language actually works, not from the language being inherently broken.
Design Principles
-
Understand, Don't Memorize: Know why code works, not just that it works.
-
Embrace the Language: Use JavaScript as JavaScript, not as Java-lite.
-
Master the Core: Scope, closures,
this, prototypes are essential. -
Explicit Over Magic: Prefer explicit code even if longer.
When Writing Code
Always
- Understand what
thisrefers to in every function - Know the difference between lexical and dynamic scope
- Use closures intentionally and understand their implications
- Understand coercion rules when using
== - Know prototype chain behavior
- Understand event loop and async mechanics
Never
- Use
thiswithout understanding its binding rules - Ignore type coercion—understand it instead
- Assume
classhides JavaScript's prototype nature - Use
async/awaitwithout understanding Promises - Copy-paste code you don't understand
Prefer
- Understanding over avoidance
- Explicit type coercion (
Number(),String()) over implicit - Factory functions or OLOO over
class(for clarity) - Clear naming that reveals intent
- Comments explaining why, not what
Code Patterns
The Four Rules of this
// Rule 1: Default Binding (standalone function call)
function sayHello() {
console.log(this.name); // undefined (strict) or global (sloppy)
}
sayHello();
// Rule 2: Implicit Binding (method call)
var person = {
name: 'Alice',
greet: function() {
console.log(this.name); // 'Alice'
}
};
person.greet();
// CAUTION: Implicit binding can be lost!
var greet = person.greet;
greet(); // undefined - default binding now!
// Rule 3: Explicit Binding (call, apply, bind)
function introduce() {
console.log('I am ' + this.name);
}
var bob = { name: 'Bob' };
introduce.call(bob); // 'I am Bob'
introduce.apply(bob); // 'I am Bob'
var boundIntroduce = introduce.bind(bob);
boundIntroduce(); // 'I am Bob'
// Rule 4: new Binding
function Person(name) {
this.name = name;
}
var charlie = new Person('Charlie');
console.log(charlie.name); // 'Charlie'
// Arrow functions: Lexical this (inherits from enclosing scope)
var team = {
members: ['Alice', 'Bob'],
name: 'Dev Team',
introduce: function() {
// Arrow function inherits 'this' from introduce()
this.members.forEach(member => {
console.log(member + ' is on ' + this.name);
});
}
};
Closures Demystified
// Closure: function retains access to its lexical scope
function createCounter() {
var count = 0; // This variable is "closed over"
return function increment() {
count += 1;
return count;
};
}
var counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3 - count persists!
// Classic closure gotcha
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 3, 3, 3 - all share same i!
}, 100);
}
// Solution 1: IIFE creates new scope each iteration
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // 0, 1, 2
}, 100);
})(i);
}
// Solution 2: let creates block scope
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 0, 1, 2
}, 100);
}
OLOO (Objects Linked to Other Objects)
// Simpson's preferred pattern over class
// Explicit delegation instead of hidden inheritance
var PersonPrototype = {
init: function(name) {
this.name = name;
return this;
},
greet: function() {
return 'Hello, I am ' + this.name;
}
};
var EmployeePrototype = Object.create(PersonPrototype);
EmployeePrototype.initEmployee = function(name, title) {
this.init(name);
this.title = title;
return this;
};
EmployeePrototype.introduce = function() {
return this.greet() + ', ' + this.title;
};
// Usage
var alice = Object.create(EmployeePrototype)
.initEmployee('Alice', 'Engineer');
alice.introduce(); // 'Hello, I am Alice, Engineer'
// Clear delegation chain, no hidden magic
Understanding Coercion
// Explicit coercion (preferred - clear intent)
var num = Number('42'); // 42
var str = String(42); // '42'
var bool = Boolean('hello'); // true
// Implicit coercion (understand it, use carefully)
var result = '5' - 2; // 3 (string coerced to number)
var concat = '5' + 2; // '52' (number coerced to string)
// The == algorithm (Abstract Equality Comparison)
// Know these rules:
null == undefined; // true (special case)
42 == '42'; // true (string → number)
true == 1; // true (boolean → number)
'0' == false; // true (both → number: 0 == 0)
// Simpson's take: == is safe when types are known
// Use === when types are unknown or mixed
function isNullOrUndefined(val) {
return val == null; // Safely checks both null and undefined
}
Async Patterns Deep Dive
// Callbacks: Understand the problems
doA(function() {
doB(function() {
doC(function() {
// "Callback hell" - but inversion of control is the real issue
});
});
});
// Promises: Understand the guarantees
// 1. Only resolved once
// 2. Either success or failure
// 3. Values are immutable once settled
// 4. Exceptions become rejections
function fetchData(url) {
return new Promise(function(resolve, reject) {
// Async operation
if (success) {
resolve(data);
} else {
reject(new Error('Failed'));
}
});
}
// Promise chaining - each .then returns a new Promise
fetchUser(id)
.then(function(user) {
return fetchPosts(user.id); // Returns Promise
})
.then(function(posts) {
return processPosts(posts);
})
.catch(function(err) {
// Catches any error in the chain
console.error(err);
});
// async/await: Syntactic sugar over Promises
// MUST understand Promises first!
async function getUserPosts(id) {
try {
var user = await fetchUser(id);
var posts = await fetchPosts(user.id);
return processPosts(posts);
} catch (err) {
console.error(err);
throw err;
}
}
Scope and Hoisting
// var is function-scoped and hoisted
function example() {
console.log(x); // undefined (not ReferenceError!)
var x = 5;
console.log(x); // 5
}
// How JavaScript sees it (hoisting):
function example() {
var x; // Declaration hoisted
console.log(x); // undefined
x = 5; // Assignment stays
console.log(x); // 5
}
// let/const are block-scoped with TDZ
function example() {
console.log(x); // ReferenceError: TDZ
let x = 5;
}
// Functions are fully hoisted
sayHi(); // Works!
function sayHi() {
console.log('Hi');
}
// Function expressions are not
sayBye(); // TypeError: sayBye is not a function
var sayBye = function() {
console.log('Bye');
};
Mental Model
Simpson approaches JavaScript by asking:
- What does
thispoint to? Apply the four rules - What scope does this live in? Lexical, not dynamic
- What's in the closure? What variables are captured
- What's the prototype chain? Follow the
[[Prototype]]links - What type coercion is happening? Know the algorithm
Signature Simpson Moves
- OLOO pattern instead of classes
- Understanding
thisbinding rules explicitly - Safe
==usage when types are known - Explicit coercion over implicit
- Deep async understanding before using
async/await