JavaScript Fundamentals: Common Quirks
Welcome back! JavaScript is full of surprises... let’s demystify a few that trip up even experienced devs. Here’s a rapid-fire guide to quirks you’ll see everywhere in Node.js and beyond.
1. undefined
vs null
JavaScript has two ways to say “no value”:
undefined
: Default for unassigned variables, missing properties, or no return.null
: You set it on purpose to mean “empty.”
const a = undefined; // undefined
const b = null; // null
console.log(typeof null); // 'object' (quirk!)
console.log(a == null); // true (loose equality)
console.log(a === null); // false (strict)
Tip: Use null
for intentional emptiness, and let undefined
happen naturally.
2. Dynamic this
The this
keyword in JavaScript is tricky: its value depends on how a function is called, not where it's defined. This often confuses developers coming from other languages.
Key patterns:
- Method call:
obj.method()
→this
is the object. - Callback: In async callbacks,
this
can change unexpectedly.
const user = {
name: 'Alice',
greet() { // Method call: 'this' is 'user'
console.log(`Hello, my name is ${this.name}`);
},
delayedGreetProblem() {
// Problem: 'this' changes inside a regular function callback
setTimeout(function() {
console.log(`(Problem) this.name: ${this.name}`);
}, 100);
},
delayedGreetArrowFix() {
// Solution: Arrow function preserves 'this' from 'user'
setTimeout(() => {
console.log(`(Arrow) this.name: ${this.name}`);
}, 100);
},
delayedGreetBindFix() {
// Solution: .bind(this) permanently sets 'this'
setTimeout(function() {
console.log(`(Bound) this.name: ${this.name}`);
}.bind(this), 100);
}
};
user.greet(); // Hello, my name is Alice
user.delayedGreetProblem(); // (Problem) this.name: undefined
user.delayedGreetArrowFix(); // (Arrow) this.name: Alice
user.delayedGreetBindFix(); // (Bound) this.name: Alice
Tip: In Node.js, always use arrow functions or .bind(this)
for callbacks if you need to access the surrounding this
. This prevents bugs from unexpected context changes.
3. Prototypal Inheritance
Objects inherit from other objects, not classes. If a property isn’t found, JS looks up the prototype chain.
const animal = { eats: true };
const rabbit = Object.create(animal);
rabbit.jumps = true;
console.log(rabbit.eats); // true (from prototype)
class Dog {
constructor(name) { this.name = name; }
speak() { console.log(`${this.name} barks!`); }
}
const rex = new Dog('Rex');
rex.speak();
Tip: ES6 class
is just cleaner syntax for prototypes. Know the chain!
For a deeper dive into prototypal inheritance, check out my previous article on Object Prototyping in JavaScript.
JavaScript teaches patience, humility, and the value of clear documentation…
mostly because you'll spend a lot of time trying to figure out why
[] + {}
equals[object Object]
.
Conclusion
Master these quirks and you’ll write code that’s both clever and reliable. Next: Node.js Core: The Event Loop and Asynchronous Programming for even deeper async magic!