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!