Nhảy tới nội dung

Không nên sử dụng var nữa

var là cách khai báo biến cũ. var có một số vấn đề. Để giải quyết những vấn đề đó, letconst đã được giới thiệu trong ES2015. Ở đây, chúng ta sẽ giải thích về var và các vấn đề của nó. Khi viết code mới, khuyến nghị sử dụng letconst thay vì var.

Khai báo biến với var

Bạn có thể khai báo biến bằng var như sau:

js
var name = "tuan";
js
var name = "tuan";

Bạn cũng có thể khai báo biến mà không cần giá trị khởi tạo. Trong trường hợp đó, giá trị của biến là undefined.

js
var name;
js
var name;

Các vấn đề của var

Khai báo biến bằng var có một số hành vi cần chú ý.

Khai báo biến cùng tên

Với var, khi khai báo biến cùng tên, không có lỗi xảy ra và biến được khai báo sau sẽ có hiệu lực. Điều này có thể vô tình ghi đè biến hiện có và tạo ra kết quả không mong muốn.

js
function test() {
var x = 1;
var x = 2;
console.log(x);
}
js
function test() {
var x = 1;
var x = 2;
console.log(x);
}

Với letconst, khai báo biến cùng tên sẽ gây ra lỗi.

ts
let x = 1;
let x = 2;
SyntaxError: Identifier 'x' has already been declared
 
const y = 1;
const y = 2;
SyntaxError: Identifier 'y' has already been declared
ts
let x = 1;
let x = 2;
SyntaxError: Identifier 'x' has already been declared
 
const y = 1;
const y = 2;
SyntaxError: Identifier 'y' has already been declared

Ghi đè biến global

Khi var được định nghĩa như biến global, nó trở thành property của object window, có nguy cơ ghi đè các property hiện có.

Ví dụ, nếu bạn định nghĩa biến innerWidth như biến global trên trình duyệt, API tiêu chuẩn window.innerWidth sẽ bị ghi đè, khiến cho dù có thay đổi kích thước trình duyệt, giá trị trả về vẫn luôn giống nhau.

js
var innerWidth = 10;
console.log(window.innerWidth);
10
js
var innerWidth = 10;
console.log(window.innerWidth);
10

letconst không được định nghĩa trong scope global, nên không có nguy cơ vô tình ghi đè property của object window.

ts
const innerWidth = 10;
console.log(window.innerWidth);
500
ts
const innerWidth = 10;
console.log(window.innerWidth);
500

📄️ Scope của biến

Scope là phạm vi hiệu lực của biến, xác định biến có thể được tham chiếu từ đâu. Trong JavaScript có 2 loại scope chính là global scope và local scope.

Hoisting của biến

Trong JavaScript, các biến được khai báo sẽ được tạo ở đầu scope. Điều này được gọi là hoisting của biến. Biến được khai báo bằng var sẽ được tạo ở đầu scope và được khởi tạo với giá trị undefined. Trong ví dụ sau, tham chiếu đến biến greeting không gây lỗi mà trả về undefined.

ts
console.log(greeting);
undefined
var greeting = "Xin chào";
 
// ↓ Do hoisting, thực tế code chạy như sau
 
var greeting;
console.log(greeting);
greeting = "Xin chào";
undefined
ts
console.log(greeting);
undefined
var greeting = "Xin chào";
 
// ↓ Do hoisting, thực tế code chạy như sau
 
var greeting;
console.log(greeting);
greeting = "Xin chào";
undefined

Với hoisting của var, không có lỗi tham chiếu, nên có nguy cơ vô tình tham chiếu giá trị undefined và gây ra bug không mong muốn.

Với letconst, tham chiếu đến biến trước khi khai báo sẽ gây ra Reference Error.

ts
console.log(x);
Block-scoped variable 'x' used before its declaration.
Variable 'x' is used before being assigned.
2448
2454
Block-scoped variable 'x' used before its declaration.
Variable 'x' is used before being assigned.
let x = 1;
 
console.log(y);
Block-scoped variable 'y' used before its declaration.
Variable 'y' is used before being assigned.
2448
2454
Block-scoped variable 'y' used before its declaration.
Variable 'y' is used before being assigned.
const y = 2;
ts
console.log(x);
Block-scoped variable 'x' used before its declaration.
Variable 'x' is used before being assigned.
2448
2454
Block-scoped variable 'x' used before its declaration.
Variable 'x' is used before being assigned.
let x = 1;
 
console.log(y);
Block-scoped variable 'y' used before its declaration.
Variable 'y' is used before being assigned.
2448
2454
Block-scoped variable 'y' used before its declaration.
Variable 'y' is used before being assigned.
const y = 2;

Tuy nhiên, điều cần lưu ý là hoisting vẫn xảy ra với letconst. Vậy tại sao Reference Error lại xảy ra?

Với var, khi hoisting xảy ra, biến được khởi tạo với undefined, cho phép tham chiếu giá trị. Ngược lại, với letconst, dù hoisting xảy ra, biến không được khởi tạo cho đến khi được đánh giá. Do đó, Reference Error xảy ra vì tham chiếu đến biến chưa được khởi tạo.

Trong ví dụ sau, nếu hoisting không xảy ra với let hay const, tại thời điểm đánh giá console.log(x), biến var x = 1 được khai báo ở đầu function sẽ được tham chiếu và output 1. Tuy nhiên, thực tế biến x được khai báo bằng let được tạo trong block scope ở trạng thái chưa khởi tạo, gây ra Reference Error khi tham chiếu đến x chưa khởi tạo.

ts
function output() {
var x = 1;
{
console.log(x);
Block-scoped variable 'x' used before its declaration.
Variable 'x' is used before being assigned.
2448
2454
Block-scoped variable 'x' used before its declaration.
Variable 'x' is used before being assigned.
let x = 2;
}
}
 
output();
ts
function output() {
var x = 1;
{
console.log(x);
Block-scoped variable 'x' used before its declaration.
Variable 'x' is used before being assigned.
2448
2454
Block-scoped variable 'x' used before its declaration.
Variable 'x' is used before being assigned.
let x = 2;
}
}
 
output();

Scope

Trong JavaScript, scope của biến được khai báo bằng var là function, nên dù khai báo biến trong {}, biến x được định nghĩa ban đầu vẫn bị ghi đè.

ts
function print() {
var x = 1;
if (true) {
var x = 2;
console.log(x);
2
}
console.log(x);
2
}
ts
function print() {
var x = 1;
if (true) {
var x = 2;
console.log(x);
2
}
console.log(x);
2
}

Scope của letconst là block scope. Trong ví dụ sau, với var biến x bị ghi đè, nhưng ở đây chúng được định nghĩa như các biến khác nhau trong block scope.

ts
function print() {
const x = 1;
if (true) {
const x = 2;
console.log(x);
2
}
console.log(x);
1
}
ts
function print() {
const x = 1;
if (true) {
const x = 2;
console.log(x);
2
}
console.log(x);
1
}