這五個 JavaScript 問題,99% 的程序員嘴上都懂,代碼里全寫錯了
這些問題不是那種“八股文式考點”。 它們更像是:決定你寫出來的代碼,究竟是“能跑”,還是“好用”的那幾道分水嶺。
你以為只是隨手搜一眼“再確認一下”, 然后瀏覽器里瞬間打開五個標簽頁,越看越亂。 對,就是這一類:看上去簡單,細摳下去,全是坑。
我們把這五個問題攤開說清楚。
1. == 和 ===,到底差在哪?
JavaScript 里有兩種“相等”:
==:寬松相等,比較前會做類型轉換===:嚴格相等,要求值和類型都一致
0 == '0' // true
0 === '0' // false== 會偷偷幫你做這些事:
- 字符串和數字相遇:先嘗試把字符串轉成數字
null和undefined會被當成“差不多一家人”- 布爾值會被轉成
0或1再比較
表面上看起來“真智能”, 實際上就是給埋 Bug 鋪平了道路。
為什么你必須在乎?
- 你以為在寫判斷邏輯,其實在賭 JS 的隱式轉換規則
- 很多“偶爾才出現一次”的線上事故,根源就是
== - 團隊協作時,一個人用
==,另一個人用===,邏輯一合并,現場就開始燒腦
實用結論:
除非你非常清楚自己在干什么, 否則默認只用:
===和!==。
你不是在跟面試官證明“我懂隱式轉換”, 而是在給未來的自己少挖幾個坑。
2. 事件循環(Event Loop)到底在干嘛?
一句實話:如果你搞不清 event loop,所有異步代碼都是“玄學調試”。
JavaScript 是單線程的, 但它能同時處理定時器、網絡請求、用戶輸入, 靠的就是事件循環。
來看一個最常見的例子:
console.log('1');
setTimeout(() => console.log('2'), 0);
console.log('3');
// 輸出:1, 3, 2為什么不是 1, 2, 3?
因為運行順序其實是:
- 先執行同步代碼:打印
1→ 安排一個定時器任務 → 打印3 - 當前這輪執行棧清空以后
- 事件循環去任務隊列里取出
setTimeout的回調 → 打印2
為什么你必須在乎?
async/await本質就是 Promise + 事件循環Promise.then()和setTimeout()不在同一個隊列里(微任務 vs 宏任務)- 那些“明明寫對了,卻總是順序抽風”的異步 Bug,全藏在這里
只要你不理解:
- 調用棧(Call Stack)
- 微任務隊列(Microtask Queue)
- 宏任務隊列(Task/Macrotask Queue)
你調試異步的時候,就永遠像在算命。
3. 閉包(Closures)到底是啥?
一句話版本:
閉包 = 函數 + 它誕生時所在的詞法作用域。
換成人話: 一個函數記住了自己出生時周圍的那些變量, 哪怕這個函數被帶離了“出生地”, 它照樣能訪問當時的那些值。
看一個標準例子:
function counter() {
let count = 0;
return function() {
count++;
return count;
};
}
const inc = counter();
inc(); // 1
inc(); // 2這里發生了什么?
counter()執行完了,按理說里面的count應該被回收- 但我們返回了一個內部函數,這個函數還在用
count - JS 看到有人在用這塊變量,就把它“悄悄保留”了下來
- 每次
inc()調用的,都是同一個count
為什么你必須在乎?
閉包不是一個“考點名詞”,而是一堆常用寫法的地基:
- 實現“私有變量”:外面訪問不到,里面一直記得住
- 做緩存 / Memoization:第一次算,后面用記憶
- Currying:一步步傳參的函數式寫法
- 甚至很多庫里的事件綁定、hook 機制,全依賴閉包
你如果只會說“閉包就是函數套函數”, 等同于沒懂。你得知道:閉包 = 生命周期延長 = 狀態被托管在一個函數里。
4. React 里的 Hooks,到底解決了什么問題?
在 Hooks 出現之前, React 想要有狀態、有生命周期,必須寫 class 組件:
this到處綁來綁去- 生命周期方法分散 (
componentDidMount/componentDidUpdate/componentWillUnmount) - 邏輯拆成幾塊,維護起來很痛苦
Hooks 出來后,React 徹底轉向函數式組件世界。
一個最小例子:
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}我們在這個例子里做了什么?
- 用
useState在函數組件里加上了“記憶功能” - 每次點擊,組件重新渲染,但
count的值通過 Hook 被保留下來
為什么你必須在乎?
useEffect:做副作用(網絡請求、訂閱、手動操作 DOM)useRef:拿“不會觸發重渲染”的可變引用useCallback:避免把函數 prop 每次都當新對象傳下去
Hooks 的本質,是:
把“狀態”和“生命周期”這兩件事, 從 class 里拆出來, 變成一套可以復用、組合、解耦的函數機制。
你如果還停留在“會用 useState 就算會 React Hooks”, 那寫出來的組件,很快就會變成魔法森林。
5. Angular 里的依賴注入(DI),是在注入什么?
如果你覺得 Angular 難,大概率就卡在這里。
DI(Dependency Injection,依賴注入) 是 Angular 的靈魂之一: 它負責:
- 把服務(Service)實例“發”給你
- 決定這些實例在整個應用里是“一份”還是“多份”
- 管理它們的生命周期和作用域
最典型的例子:
@Injectable()
export class UserService {
constructor(private http: HttpClient) {}
}這里發生了什么?
@Injectable()聲明:這是一個可以被注入的服務- 構造函數里聲明
HttpClient,Angular 會自動把實例“塞”進來 - 你不用手動
new HttpClient(),也不用到處傳來傳去
為什么你必須在乎?
如果你不理解:
providers放在哪一層(模塊 / 組件)- 服務是單例,還是每個組件各一份
- token、scope、樹狀注入的關系
你在調 Angular 的 Bug 時,就等于在迷宮里跑。
DI 做的,是一件很現實的事:
把“誰依賴誰”“誰該活多久”這些問題, 從你手寫代碼里抽出來, 變成框架幫你管理的一張圖。
總結一下:5 個問題背后的“硬技能”
1. == vs ===
- 明面上:寬松相等 vs 嚴格相等
- 本質上:你要不要賭 JS 的轉換規則?
- 好習慣:默認用
===,需要==時,先詳細寫出所有可能的 case 再說。
2. 事件循環(Event Loop)
- 明面上:單線程 + 異步
- 本質上:誰先跑、誰后跑,誰永遠排在隊伍前面
- 想寫好
async/await,不理解它,等于瞎寫。
3. 閉包(Closures)
- 明面上:函數里套函數
- 本質上:狀態被“托管”在函數和作用域里
- 你在寫的是“行為+記憶”的組合,而不是普通函數調用。
4. React Hooks
- 明面上:
useState、useEffect、useRef那堆 API - 本質上:把狀態邏輯拆成可復用的函數片段
- 寫得好:組件邏輯清晰、可組合
- 寫得爛:一堆 useEffect 疊羅漢,誰也不敢改。
5. Angular DI
- 明面上:
@Injectable()、constructor(private svc: XxxService) - 本質上:一個集中管理依賴和生命周期的系統
- 你理解它,應用結構就清晰; 你忽略它,調試就會非常痛苦。
最后的小聲提醒
這 5 個問題, 之所以反復出現在面試、文章、爭論里, 并不是因為大家都“不知道”。
而是因為——我們都以為自己“差不多懂了”, 但一寫到具體項目里,就暴露得完全不夠深。
你要拉開和大多數開發者的差距, 不靠多會幾個新框架, 而是靠:
- 把這些“老問題”真的吃透
- 寫代碼時,知道自己每一行是在利用語言特性,還是在賭運氣
當你能把這五個問題講清楚、用到位, 你已經悄悄超過了絕大多數只停留在“會用 API”層面的開發者。























