Menu

let, const và var trong JavaScript: phạm vi, hoisting, cách dùng

Ba cách khai báo biến trong JavaScript, và vì sao code hiện đại mặc định dùng const, dùng let khi bắt buộc và gần như không bao giờ dùng var.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Ba từ khóa, một nhiệm vụ

JavaScript có ba cách khai báo biến (variable): var, let và const. Cả ba đều gắn một cái tên với một giá trị, nhưng khác nhau về phạm vi (scope), quy tắc gán lại, và cách chúng hoạt động trước khi dòng khai báo chạy. Code hiện đại phần lớn dùng const, dùng let khi giá trị cần thay đổi, và hiếm khi hoặc không bao giờ dùng var.

Phiên bản ngắn gọn:

Phần còn lại của trang này giải thích vì sao ba dòng đó hoạt động như vậy.

const: lựa chọn mặc định

const tạo ra một biến không thể gán lại. Khi đã viết const x = 5, bạn không thể viết x = 6 sau đó: engine sẽ ném ra TypeError.

Quy tắc chỉ có vậy. Vì phần lớn biến trong một chương trình được viết tốt không cần gán lại, const phù hợp với đại đa số trường hợp. Ưu tiên const trước tiên giúp những chỗ giá trị thật sự thay đổi trở nên nổi bật.

Một điểm hay gây nhầm lẫn: const bảo vệ liên kết giữa tên và giá trị, không bảo vệ bản thân giá trị. Nếu giá trị là object hoặc mảng, nội dung bên trong vẫn thay đổi được:

const nghĩa là "cái tên này luôn trỏ đến object này". Nó không có nghĩa "object này không bao giờ thay đổi". Nếu bạn cần đóng băng chính object đó, Object.freeze(user) là công cụ phù hợp, nhưng trên thực tế phần lớn code chỉ dựa vào quy ước rằng không nên thay đổi object khai báo bằng const.

let: khi bạn thật sự cần gán lại

let giống hệt const ở mọi mặt, trừ việc bạn có thể gán lại nó. Hãy dùng nó cho biến đếm, biến tích lũy, biến vòng lặp, và bất cứ nơi nào giá trị thật sự thay đổi theo thời gian.

Nếu thấy mình viết let mà không bao giờ gán lại biến, hãy đổi nó thành const. Linter trong hầu hết các dự án sẽ nhắc bạn việc này.

Phạm vi khối

Cả let và const đều có phạm vi khối (block scope). Một khối là bất cứ thứ gì nằm giữa { và }: thân của if, của for, của một hàm, hay thậm chí một khối { ... } đứng riêng. Biến khai báo bên trong khối không tồn tại bên ngoài khối đó.

Đây là điều bạn muốn. Nó giữ biến trong đúng phạm vi công việc của chúng, và khiến việc vô tình trùng tên là không thể xảy ra.

var không làm được điều này, và đó là lý do chính để tránh nó.

var: phạm vi hàm và những bất ngờ

var có phạm vi theo hàm gần nhất, không phải khối gần nhất. Nghĩa là var khai báo bên trong if hoặc for sẽ lọt ra ngoài, vào hàm bao quanh:

Phạm vi lỏng lẻo đó là nguồn gốc của cả loạt bug kinh điển trong JavaScript, nổi tiếng nhất là các vòng lặp mà mọi lần lặp dùng chung một var i và tất cả callback cuối cùng đều thấy giá trị sau cùng.

var còn cho phép khai báo lại cùng một tên trong cùng phạm vi mà không báo gì, khiến lỗi gõ nhầm bị che giấu:

Code hiện đại gần như chỉ dùng let và const. var xuất hiện trong các codebase cũ, các câu trả lời cũ trên Stack Overflow, và những script cần chạy trong môi trường rất cũ.

Hoisting và temporal dead zone

Cả ba kiểu khai báo đều được hoisting: engine biết đến chúng trước khi chạy code trong khối, nhưng chúng hoạt động khác nhau trước khi dòng khai báo chạy.

var được hoisting và khởi tạo bằng undefined. Bạn có thể tham chiếu đến nó trước dòng var mà không bị lỗi:

let và const được hoisting nhưng không được khởi tạo. Đụng đến chúng trước dòng khai báo sẽ ném lỗi. Khoảng thời gian từ lúc vào phạm vi đến lúc dòng khai báo chạy được gọi là temporal dead zone (TDZ):

TDZ là tính năng, không phải bug. Nó biến việc "dùng trước khi khai báo" từ một undefined âm thầm thành một lỗi rõ ràng, giúp bắt được rất nhiều lỗi gõ nhầm và lỗi sai thứ tự.

Biến const trong vòng lặp for...of

Một trường hợp nhỏ nhưng phổ biến: vòng lặp for...of tạo ra một liên kết mới ở mỗi lần lặp, nên bạn có thể dùng const cho biến vòng lặp dù nó "thay đổi" giữa các lần lặp.

Mỗi lần lặp có liên kết name riêng, không có biến nào bị gán lại. Vòng lặp for (let i = 0; i < n; i++) cổ điển vẫn cần let, vì i là một biến duy nhất được tăng dần.

Một quy tắc thực tế

Chọn cách khai báo theo thứ tự ưu tiên sau:

  • const là mặc định. Nếu giá trị sẽ không bị gán lại, hãy thể hiện điều đó.
  • let khi biến thật sự cần thay đổi.
  • var chỉ khi làm việc trong codebase bắt buộc dùng nó.

Áp dụng nhất quán, quy tắc này giúp ý định của mọi biến rõ ràng ngay khi nhìn vào: gán lại được hay không, phạm vi trong khối này hay trong hàm này.

MAX_RETRIES và users không bao giờ thay đổi: const. successful tăng dần: let. user là liên kết mới ở mỗi lần lặp: const. Đọc từ trên xuống, bạn biết ngay giá trị nào thay đổi và giá trị nào không mà không cần chạy code.

Tiếp theo: kiểu nguyên thủy

Giờ bạn đã biết khai báo biến, câu hỏi tiếp theo là biến có thể chứa những loại giá trị nào. JavaScript có một nhóm nhỏ các kiểu nguyên thủy (primitive type) như số, chuỗi, boolean và vài kiểu khác, mỗi kiểu có những điểm đặc biệt riêng. Đó là nội dung của trang tiếp theo.

Câu hỏi thường gặp

let, const và var trong JavaScript khác nhau thế nào?

const và let có phạm vi khối và được thêm vào từ ES2015; var có phạm vi hàm và ra đời sớm hơn. Biến khai báo bằng const không gán lại được, còn let thì được. var cũng được hoisting và khởi tạo bằng undefined, trong khi let và const được hoisting nhưng không dùng được cho đến khi dòng khai báo chạy (temporal dead zone).

const có làm giá trị trở nên bất biến không?

Không. const chỉ ngăn việc gán lại biến. Nếu giá trị là object hoặc mảng, bạn vẫn thay đổi được nội dung bên trong: const user = {}; user.name = 'Ada' vẫn chạy bình thường. Hãy dùng Object.freeze hoặc một thư viện nếu bạn cần bất biến thật sự.

JavaScript hiện đại còn nên dùng var không?

Gần như không. let và const có quy tắc phạm vi rõ ràng hơn và bắt được nhiều bug hơn ngay lúc phân tích code. Nơi duy nhất bạn sẽ gặp var là các codebase cũ và thỉnh thoảng là những script phải chạy trong môi trường không hỗ trợ ES2015.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu