Hai lần gọi hàm không truyền state có dùng chung object mặc định hay không? Câu hỏi này dễ bị trả lời theo thói quen từ ngôn ngữ khác. Trong JavaScript, biểu thức default parameter được đánh giá khi lời gọi cần giá trị mặc định; nhưng điều đó không có nghĩa mọi biểu thức mặc định đều tạo một object mới.
Bài đối chiếu tài liệu ngày 10/11/2026 và chạy ví dụ trên Node.js 24.18.1. Các đoạn code chỉ tạo object và bộ đếm trong bộ nhớ, không gọi API, timer hoặc đo hiệu năng. Mục tiêu là phân biệt thời điểm đánh giá biểu thức với việc object nào được chia sẻ.
Object literal được tạo riêng ở mỗi lần cần default
function createTask(state = {attempts: 0}) {
return {state};
}
const first = createTask();
const second = createTask();
first.state.attempts += 1;
console.log(first.state === second.state);
console.log(first.state.attempts, second.state.attempts);
Kết quả là false và cặp 1, 0. Mỗi lời gọi thiếu state đánh giá một object literal mới, nên thay đổi state của first không làm state của second thay đổi. Wrapper {state} ở giá trị trả về là một object khác nữa; cần xác định đúng tầng object khi kiểm tra identity.
Default parameters trên MDN mô tả thời điểm đánh giá này. Đừng suy rằng default được dựng một lần rồi giữ nguyên chỉ vì biểu thức nằm ngay trong chữ ký hàm.
Biểu thức trả về một object dùng chung vẫn là dùng chung
const sharedState = {attempts: 0};
function createSharedTask(state = sharedState) {
return {state};
}
const sharedFirst = createSharedTask();
const sharedSecond = createSharedTask();
sharedFirst.state.attempts += 1;
console.log(sharedFirst.state === sharedSecond.state);
console.log(sharedSecond.state.attempts);
Kết quả là true và 1. Biểu thức default vẫn được đánh giá ở lời gọi, nhưng nó đọc biến sharedState, vốn trỏ đến cùng một object. “Đánh giá lại biểu thức” và “cấp phát object mới” là hai việc khác nhau. Muốn biết có chia sẻ hay không, đọc biểu thức thực sự trả về gì.
Tương tự, nếu caller chủ động truyền cùng một object vào createTask nhiều lần, các task sẽ nhận cùng tham chiếu đó. Default không tự clone đối số đã có. Đây có thể là ý định đúng hoặc là nguồn lỗi, tùy hợp đồng ownership của hàm.
Theo dõi một biểu thức mặc định có lời gọi hàm
let nextSequence = 0;
function allocateSequence() {
nextSequence += 1;
return nextSequence;
}
function createRecord(sequence = allocateSequence()) {
return {sequence};
}
console.log(createRecord().sequence);
console.log(createRecord(99).sequence);
console.log(createRecord(undefined).sequence);
console.log(nextSequence);
Bốn dòng là 1, 99, 2 và 2. Đối số 99 khiến biểu thức default không được dùng ở lời gọi ấy; undefined yêu cầu lấy giá trị mặc định. Bộ đếm là dữ liệu thử để quan sát thời điểm gọi, không phải bộ sinh ID phù hợp cho production, lưu trữ lâu dài hoặc xử lý đồng thời.
Nếu biểu thức mặc định có tác dụng phụ như đọc trạng thái hoặc tăng bộ đếm, test cần ghi nhận số lần nó chạy. Không chỉ kiểm tra object cuối cùng vì cùng một giá trị trả về có thể che việc một tác vụ phụ đã được gọi nhiều hơn dự kiến.
Default không thay cho kiểm tra đối số
Ví dụ đầu mặc định một state khi thiếu dữ liệu, nhưng vẫn nhận nguyên đối số caller đưa vào. Truyền null không tự tạo {attempts: 0}. Một object không có attempts hoặc có attempts sai kiểu cũng cần một quy tắc xử lý riêng nếu hàm thực tế sẽ thao tác lên nó.
Hãy xác định hàm có nhận ownership của state hay chỉ đọc nó. Nếu nhiệm vụ là cập nhật mà không sửa input, cần cách tạo kết quả khác phù hợp; default parameter không tự đảm bảo tính bất biến. Cũng không gọi một bản sao nông là đã tách mọi object lồng bên trong.
Cách giải thích và kiểm tra trong phỏng vấn
Một câu trả lời có thể đi qua ba bước: lời gọi nào cần default, biểu thức nào được đánh giá và tham chiếu nào được kết quả ấy giữ. Dự đoán identity trước, sau đó mới chạy code và sửa một thuộc tính để quan sát sự chia sẻ.
Thử riêng hai lời gọi bỏ đối số, hai lời gọi truyền cùng object và một lời gọi dùng undefined. Với biểu thức tạo sequence, thêm một đối số tường minh để chứng minh default không chạy ở mọi lần gọi. Cách phân tích này giúp tránh cả hai kết luận quá rộng: “default object luôn chia sẻ” và “default luôn clone dữ liệu”.

