跪拜 Guibai
← Back to the summary

10 JavaScript Tricks That Cut 30% of Your Boilerplate


theme: devui-blue

JS Code Tips vol.1 — 10 Small Tricks to Write 30% Less Code

Xiao Bu's Code Tips Series, Issue 1 Theme: Conditionals · Types · Design Patterns The format is "pitfall → solution → where the pitfall is", so you can copy the homework right after reading 😎

Hey there! I'm Xiao Bu, the Bu from Bu Jian Shuo~

As someone who has "crashed" countless times in the coding world, I've figured it out: Pitfalls, you either don't step in them, or you step in a big one😂

Today's issue focuses on Conditionals · Types · Design Patterns — no fluff, all lessons cried out in real battles. Read it or not, it's up to you~ Anyway, a crash record is free

Ps: There's a surprise at the end (not an ad!)


1. Optional Chaining ?. — Kill a && a.b && a.b.c

Writing this kind of code used to raise my blood pressure:

// My old way (blood pressure warning)
if (user && user.profile && user.profile.address && user.profile.address.city) {
  console.log(user.profile.address.city);
}

Now, one line does it:

console.log(user?.profile?.address?.city);

Where the pitfall is: Optional chaining returns undefined when it hits null/undefined, but it won't short-circuit on 0, '', or false — don't mix it up with &&.


2. ?? vs || — 0 and '' Shouldn't Be Treated as Falsy

A classic crash scene:

function setVolume(vol) {
  return vol || 50; // User passes 0, and a default of 50 pops out
}

The correct fix:

function setVolume(vol) {
  return vol ?? 50; // Only uses default for null / undefined
}

Where the pitfall is: ?? cannot be mixed with || or && (the syntax will throw an error directly); you must separate them with parentheses.


3. The Logical Assignment Trio: ??= / ||= / &&=

Assignment + logical check, done in one line.

let count = 0;
count ||= 10; // count is 0, judged falsy by || → becomes 10
count ??= 10; // count is 0 (not null/undefined) → stays 0

let user = { name: "Xiao Bu" };
user.name &&= "[Verified] " + user.name; // name is truthy → prefix added

Where the pitfall is: If you don't understand the difference between || and ??, using this trio will crash you immediately. 0, empty strings, and false are all victims of ||=.


4. The Ultimate Type Check: Object.prototype.toString.call()

typeof [] is 'object', typeof null is also 'object' — blood pressure +1.

function getType(val) {
  return Object.prototype.toString.call(val).slice(8, -1);
  // Returns: 'Array' / 'Date' / 'RegExp' / 'Map' / 'Set' / 'Promise' ...
}

Use typeof where appropriate (for checking primitives), and use toString for complex objects. Don't stubbornly rely on instanceof.

Where the pitfall is: instanceof misjudges in iframes / Web Workers because the prototype chains differ. In cross-window scenarios, honestly just use Array.isArray().


5. Array.isArray() — More Reliable Than instanceof Array

Array.isArray([]); // true
Array.isArray({ length: 1 }); // false, identifies array-like objects too

Why not use instanceof Array? In an iframe, the Array from page A is not the same constructor as the Array from page B. [] instanceof Array would be false — a classic mystery bug.


6. Singleton Pattern: ES Modules Are Born This Way

Stop writing this:

class Config {
  constructor() {
    /* ... */
  }
}
Config.instance = null;
Config.getInstance = () => {
  if (!Config.instance) Config.instance = new Config();
  return Config.instance;
};

Just do this:

// config.js
export default {
  apiBase: "https://api.example.com",
  // ...
};

ES Modules are naturally singletons; no matter how many times you import, it's the same instance. Only consider a class if you need lazy loading.

Where the pitfall is: In CommonJS (old Node code), require() caches, but it caches the reference of module.exports. Be careful with shared state when exporting objects.


7. Strategy Pattern: Eliminate if/else Nesting

// Bad code (nesting hell)
function calc(type, a, b) {
  if (type === "add") return a + b;
  else if (type === "sub") return a - b;
  else if (type === "mul") return a * b;
  else if (type === "div") return a / b;
  else throw new Error("Unknown operation");
}

The correct fix — a lookup table:

const ops = {
  add: (a, b) => a + b,
  sub: (a, b) => a - b,
  mul: (a, b) => a * b,
  div: (a, b) => a / b,
};

function calc(type, a, b) {
  const fn = ops[type];
  if (!fn) throw new Error("Unknown operation");
  return fn(a, b);
}

Adding a new operation? Just add a line to the object; no need to touch calc.

Where the pitfall is: It's best to add type constraints to the keys in the table (TypeScript folks). Otherwise, calc('addd', 1, 2) with a typo won't explode until runtime.


8. Observer Pattern: A 30-Line EventEmitter

Publish/subscribe is an interview staple and genuinely useful.

class Emitter {
  constructor() {
    this.listeners = new Map();
  }
  on(event, fn) {
    if (!this.listeners.has(event)) this.listeners.set(event, []);
    this.listeners.get(event).push(fn);
  }
  emit(event, ...args) {
    (this.listeners.get(event) || []).forEach((fn) => fn(...args));
  }
  off(event, fn) {
    const arr = this.listeners.get(event);
    if (arr)
      this.listeners.set(
        event,
        arr.filter((f) => f !== fn),
      );
  }
}

Using it:

const bus = new Emitter();
b us.on("login", (user) => console.log(user.name, "logged in"));
b us.emit("login", { name: "Xiao Bu" }); // Xiao Bu logged in

Where the pitfall is: Forgetting to call off is the primary cause of memory leaks. Remember to unbind when Vue/React components are destroyed, or just use libraries like mitt / tiny-emitter to save the hassle.


9. Proxy Pattern: A "Read-Only View" More Flexible Than Object.freeze

Object.freeze is rigid; it's all or nothing. Proxy allows finer interception:

function readonly(obj) {
  return new Proxy(obj, {
    set(target, key, value) {
      throw new Error(`Property ${key} is not writable`);
    },
    deleteProperty(target, key) {
      throw new Error(`Property ${key} cannot be deleted`);
    },
  });
}

const config = readonly({ api: "https://..." });
config.api = "x"; // Error: Property api is not writable

Where the pitfall is: Proxy doesn't truly freeze the original object. readonly(config).api = 'x' throws an error, but config.api = 'x' can still modify it. For deep read-only, you need to wrap it recursively.


10. Decorator Pattern: Function-Style AOP

Want to add logging / timing / authentication without modifying the original function?

function withLog(fn) {
  return function (...args) {
    console.log("Call:", fn.name, args);
    const result = fn(...args);
    console.log("Return:", result);
    return result;
  };
}

const add = withLog((a, b) => a + b);
add(1, 2); // Call: add [1, 2]  Return: 3

The ES decorator proposal is about to land, and TypeScript has been able to use it for a while, but the function wrapping approach has better compatibility and can be used in legacy projects.

Where the pitfall is: Decorators change the original function's name (withLog((a, b) => ...) can't get the original name), so stack traces lose the name during debugging. Remember to set .name manually.


📦 Wrapping Up

These 10 tips condensed:

If you learned it, you earned it. Hesitation is as good as coming for nothing~


Written at the End

What tips do you want? Drop a topic in the comments~

Xiao Bu might see it... or might not reply 😂 After all, there are too many crashes in the code, can't free up a hand~

Ps: Likes and follows are up to fate; those who rush for updates will be hit 😂

Comments

Top 1 of 2 from juejin.cn, machine-translated. The original thread is authoritative.

user1841103045928

I don't know why there are posts like this every year.

不简说

Because there are graduates every year. [grin]