`==` và `===` khác nhau thế nào? Type coercion hoạt động ra sao?
`===` so sánh giá trị và kiểu, không ép kiểu. `==` ép kiểu (type coercion) theo quy tắc phức tạp trước khi so sánh nên cho kết quả khó đoán, ví dụ `[] == false` là true. Mặc định dùng `===`; chỉ `x == null` là cách viết gọn hợp lý cho null và undefined.
Closure trong JavaScript là gì?
Closure là hàm "nhớ" được biến ở phạm vi (scope) nơi nó được tạo ra, kể cả khi hàm cha đã chạy xong.
Destructuring, spread và rest operator khác nhau thế nào?
Destructuring bóc giá trị từ array/object vào biến (hỗ trợ default, đổi tên). Spread `...x` mở rộng iterable/object thành phần tử rời; rest `...x` ở vế nhận gom phần còn lại thành array/object. Spread chỉ copy nông (shallow).
Khác nhau giữa var, let và const?
var có function scope và bị hoist (giá trị undefined); let/const có block scope và nằm trong Temporal Dead Zone trước khi khai báo; const không cho gán lại biến.
map, filter, reduce dùng thế nào? Method nào của Array làm thay đổi mảng gốc?
`map` biến đổi từng phần tử, `filter` lọc, `reduce` gộp thành một giá trị; cả ba không đổi mảng gốc. Method mutating gồm `push`, `pop`, `splice`, `sort`, `reverse`, `fill`; bản không mutate là `toSorted`, `toReversed`, `toSpliced`, `with` (ES2023).
Optional chaining `?.` và nullish coalescing `??` khác gì `&&` và `||`?
`a?.b` trả undefined nếu `a` là null/undefined thay vì ném lỗi. `a ?? b` chỉ dùng `b` khi `a` là null/undefined, còn `a || b` dùng `b` với mọi giá trị falsy (0, "", false). Vì vậy `??` an toàn hơn để đặt giá trị mặc định.
Vì sao 0.1 + 0.2 !== 0.3 trong JavaScript? Xử lý thế nào?
Number là số thực 64-bit IEEE 754 nhị phân, nên 0.1 và 0.2 không biểu diễn chính xác, tổng ra 0.30000000000000004. So sánh bằng sai số (epsilon), làm tròn khi hiển thị, hoặc tính tiền bằng số nguyên (đơn vị nhỏ nhất), BigInt hay thư viện decimal.
`this` trong JavaScript được xác định như thế nào?
this được quyết định lúc gọi hàm (không phải lúc định nghĩa), trừ arrow function: nó lấy this của scope bao ngoài.
call, apply, bind khác nhau thế nào? Tự viết bind
`call(ctx, a, b)` và `apply(ctx, [a, b])` gọi hàm ngay với `this` chỉ định (khác ở cách truyền đối số). `bind(ctx, ...pre)` trả về hàm mới đã khóa `this` và đối số đầu (partial application), không gọi ngay; `this` đã bind không thể bị override bởi call/apply.
debounce và throttle khác nhau thế nào? Viết code mỗi loại
Debounce hoãn gọi hàm cho đến khi ngừng kích hoạt đủ N ms (phù hợp ô tìm kiếm, resize xong). Throttle đảm bảo hàm chạy tối đa một lần mỗi N ms (phù hợp scroll, mousemove). Cả hai đều dựa vào closure giữ timer hoặc timestamp.
ES Modules khác CommonJS thế nào? Live binding và tree shaking là gì?
ESM (`import`/`export`) được phân tích tĩnh, bất đồng bộ, export là live binding (tham chiếu sống) và hỗ trợ tree shaking, top-level await. CommonJS (`require`) chạy đồng bộ lúc runtime, export là bản sao giá trị tại thời điểm gán trên `module.exports`.
Event bubbling, capturing và event delegation là gì?
Sự kiện DOM đi qua 3 pha: capturing (từ window xuống target), target, rồi bubbling (từ target lên). Event delegation gắn một listener ở phần tử cha và dựa vào `event.target` để xử lý con, giúp ít listener hơn và hoạt động với phần tử thêm động.
Event loop hoạt động thế nào? Microtask vs macrotask?
JS đơn luồng; event loop chạy hết code đồng bộ, rồi xử lý toàn bộ microtask (Promise.then) trước, sau đó mới lấy 1 macrotask (setTimeout, I/O) tiếp theo.
Promise.all, allSettled, race, any khác nhau thế nào?
all: tất cả thành công, 1 lỗi là reject; allSettled: đợi hết, không bao giờ reject; race: kết quả đầu tiên (thành công hoặc lỗi); any: thành công đầu tiên.
Prototype và prototype chain là gì? class khác function constructor ra sao?
Mỗi object có liên kết nội bộ [[Prototype]] tới object khác; khi truy cập property không có sẵn, engine đi ngược chuỗi này đến null. `class` chỉ là cú pháp trên prototype, nhưng không hoisting như function, bắt buộc dùng `new` và chạy strict mode.
Shallow copy và deep copy khác nhau thế nào? structuredClone có giới hạn gì?
Shallow copy (spread, Object.assign, slice) chỉ copy cấp đầu, object lồng nhau vẫn chung tham chiếu. Deep copy sao chép đệ quy: dùng `structuredClone` (hỗ trợ Date, Map, Set, vòng tham chiếu; không clone được function, DOM, class instance giữ prototype).
Garbage collection hoạt động thế nào? Các nguyên nhân memory leak phổ biến
JS dùng mark-and-sweep: object không còn reachable từ root (global, call stack) thì bị thu hồi; reference counting không xử lý được vòng tham chiếu. Leak xảy ra khi còn tham chiếu không cần: global, listener/timer chưa dọn, closure giữ object lớn, cache không giới hạn, DOM detached.
Iterator protocol và generator hoạt động thế nào? Dùng khi nào?
Iterable có method `[Symbol.iterator]()` trả về iterator với `next()` cho `{ value, done }`. Generator (`function*`) tạo iterator có thể tạm dừng ở `yield` và nhận giá trị qua `next(x)`, cho phép lazy evaluation, chuỗi vô hạn và luồng điều khiển hai chiều.
Map, Set, WeakMap, WeakSet và WeakRef khác nhau thế nào?
`Map`/`Set` giữ thứ tự chèn, key là bất kỳ kiểu nào (so sánh SameValueZero). `WeakMap`/`WeakSet` chỉ nhận object làm key, giữ tham chiếu yếu nên không cản GC, không iterate được, không có `size`. `WeakRef` giữ tham chiếu yếu tới một object, `deref()` có thể trả undefined.
Microtask starvation là gì? Làm sao tránh chặn event loop?
Event loop chạy hết toàn bộ microtask queue (kể cả microtask mới sinh ra) trước khi sang macrotask hay render. Nếu microtask liên tục tự lên lịch microtask khác, timer, I/O và render bị bỏ đói (starvation) và trang đứng. Tránh bằng cách nhường quyền qua macrotask (`setTimeout`, `MessageChannel`, `scheduler.yield`).
Proxy và Reflect là gì? Ứng dụng và hạn chế
`Proxy` bọc một object và chặn thao tác cơ bản (get, set, has, deleteProperty, ownKeys, apply...) qua các trap. `Reflect` cung cấp phiên bản mặc định của các thao tác đó, dùng trong trap để giữ đúng ngữ nghĩa. Ứng dụng: reactivity (Vue 3), validation, logging, default value.