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:
- Most frequent pitfalls: Mixing
?.with&&, misusing??for||, shallow copying withstructuredClone, catch swallowing errors - Most worth bookmarking: The Strategy Pattern lookup table demo, the ultimate
Object.prototype.toString.call()technique - Core idea: Let the language do the work for you; don't hand-code it yourself
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~
- A pitfall you just stepped into
- Code you write repeatedly in your project
- An API you want to figure out but have been too lazy to look up
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 😂
Top 1 of 2 from juejin.cn, machine-translated. The original thread is authoritative.
I don't know why there are posts like this every year.
Because there are graduates every year. [grin]