Từ khóa "let", "const" và "var" trong JavaScript ES6


Từ khóa "let", "const" và "var" trong JavaScript ES6









Trước khi phiên bản JavaScript ES6 (ECMAScript 2015) được giới thiệu,
chúng ta chỉ có một từ khóa để khai báo biến, đó là "var". Tuy nhiên,
ES6 đã giới thiệu hai từ khóa mới, "let""const", để cung cấp
các phương thức khai báo biến linh hoạt hơn trong JavaScript. Trong bài viết
này, chúng ta sẽ tìm hiểu về từ khóa "let", "const"
"var" trong ES6 và điểm khác biệt giữa chúng.




Nếu bạn mới học và sử dụng Javascript thì có một vài điều bạn sẽ từng nghe là:



  • "var""let" có thể gán lại giá trị khác.

  • "const" không thể gán lại giá trị.


  • Developer không nên sử dụng "var" nữa. Thay vào đó là
    "let" "const".




Tính chất của "var", "let" "const" thì không có gì để
bàn cãi. Vậy tại sao lại không nên sử dụng "var" hay khi nào nên sử
dụng "let""const"?. Hi vọng bài viết này sẽ có ý nghĩa với
bạn.




"var", "let" và "const". Sự khác nhau là gì?


Để phân tích sự khác nhau giữa chúng thì mình sẽ sử dụng 3 yếu tố:



  • Phạm vi truy cập (scope).


  • Tính khai báo lại, gán lại giá trị (redeclaration and reassignment).

  • Hoisting.



Hãy bắt đầu với từ khóa "var".



Var



"var" là từ khóa truyền thống để khai báo biến trong JavaScript trước
ES6. Biến khai báo bằng "var" có phạm vi toàn cục (global scope) hoặc
phạm vi hàm (function scope) tùy thuộc vào việc khai báo biến xảy ra ở đâu.
Điều này có nghĩa là biến khai báo bằng "var" có thể truy cập từ bất kỳ đâu
trong phạm vi toàn cục hoặc phạm vi hàm, dù sau khi khai báo.



Tính chất



Biến được khai báo bằng "var" có phạm vi toàn cục hoặc phạm vi hàm (function
scope). Nghĩa là biến có thể truy cập từ bất kỳ đâu trong phạm vi toàn cục
hoặc phạm vi hàm mà nó được khai báo.



Ví dụ 1: 






Biến number có phạm vi global – nó được khai báo các hàm
bên ngoài trong phạm vi global – vì vậy bạn có thể truy cập nó ở mọi nơi (bên
trong và bên ngoài các hàm).





var number = 50

function print() {
var square = number * number
console.log(square)
}

console.log(number) // 50

print() // 2500




Ví dụ 2: 




Ở đây, chúng ta đã khai báo biến number trong hàm print, vì vậy nó có phạm vi là local. Điều này có nghĩa là biến chỉ có thể được
truy cập bên trong hàm đó. Mọi cố gắng truy cập vào biến bên ngoài hàm nơi nó
được khai báo sẽ dẫn đến lỗi tham chiếu đến biến không được xác định (a variable is not defined reference error).





function print() {
var number = 50
var square = number * number
console.log(square)
}

print() // 2500

console.log(number)
// ReferenceError: number is not defined





Khai báo, gán lại giá trị (redeclare and reassign)



Bạn có thể gán lại giá trị mới cho biến "var" bất cứ lúc nào trong phạm vi của
nó.



Ví dụ: 







var number

console.log(number)
// undefined





Biến number được khai báo với từ khóa là "var" và không được gán
giá trị. Giá trị mặc định của biến number sẽ là undefined.





var number = 50




Bây giờ thì giá trị khởi tạo của biến number sẽ là 50.




Từ khóa "var" cho phép chúng ta khai báo lại ở bất kỳ đâu trong phạm vi
của nó. Hãy xem ví dụ sau đây:





var number = 50
console.log(number) // 50

var number = 100
console.log(number) // 100





Bạn có thể gán lại giá trị khi đã khởi tạo biến bằng từ khóa "var".





var number = 50
console.log(number) // 50

number = 100
console.log(number) // 100

number = 200
console.log(number) // 200




Hoisting



Các biến được khai báo với "var" hoisted lên đầu phạm vi hàm hoặc
global của chúng, giúp chúng có thể truy cập được trước dòng chúng được khai
báo.



Ví dụ:





console.log(number) // undefined

var number = 50

console.log(number) // 50





Biến number được khai báo ở phạm vi toàn cục (global) nên sẽ hoisted
lên đầu phạm vi. Nghĩa là chúng ta có thể truy cập biến number trước
dòng mà chúng ta khai báo nó mà không bị lỗi.


Nhưng khi biến hoisted sẽ được gán giá trị mặc định là undefined.





Hãy xem ví dụ dưới đây:





function print() {
var square1 = number * number
console.log(square1)

var number = 50

var square2 = number * number
console.log(square2)
}

print()
// NaN
// 2500





Trong hàm print ở trên, biến number được khai báo trong phạm vi
hàm và hoisted lên đầu phạm vi hàm đó. Chúng ta có thể truy cập được
biến number trước dòng khai báo.




Như vậy, chúng ta thấy trong square1, chúng ta
gán number number . Vì số
được hoisted  với giá trị mặc định là undefined,
nên hình square1 sẽ undefined * undefined nên không xác định dẫn đến NaN.




Sau khi dòng khai báo có giá trị ban đầu được thực thi, number sẽ có
giá trị là 50. Vậy trong square2 là  number * number sẽ là 50 * 50, kết quả là 2500.



Let



"let" là từ khóa ES6 để khai báo biến có phạm vi khối (block scope).
Biến được khai báo bằng "let" chỉ có thể truy cập được trong phạm vi
khối bao quanh nó (ví dụ: trong một khối if, vòng lặp, hoặc một khối mã nguồn
khác). Điều này giúp giảm thiểu xung đột và nhầm lẫn trong việc sử dụng biến.
Bên cạnh đó, bạn có thể gán lại giá trị cho biến được khai báo bằng
"let".



Tính chất



Biến được khai báo bằng "let" có phạm vi khối (block scope). Nghĩa là
biến chỉ có thể truy cập được trong phạm vi khối bao quanh nó (ví dụ: trong
một khối if, vòng lặp, hoặc một khối mã nguồn khác). 



Hãy xem ví dụ bên dưới:





let number = 50

function print() {
let square = number * number

if (number < 60) {
var largerNumber = 80
let anotherLargerNumber = 100

console.log(square)
}

console.log(largerNumber)
console.log(anotherLargerNumber)
}

print()
// 2500
// 80
// ReferenceError: anotherLargerNumber is not defined






Trong ví dụ này, chúng ta có một biến phạm vi global là number và một
biến phạm vi local là square. Ngoài ra còn có biến phạm vi block là
otherLargerNumber vì nó được khai báo bằng let trong một khối.




Mặt khác, largeNumber - mặc dù được khai báo trong một khối tuy nhiên
nó được khai báo bằng từ khóa "var" nên nó hoisted lên đầu phạm vi hàm
print và  được truy xuất ở mọi nơi trong phạm vi hàm đó. Vì
vậy, largeNumber có phạm vi cục bộ (phạm vi hàm) như được
khai báo trong hàm print.




Chúng ta có thể truy cập number ở khắp mọi nơi. Chúng ta chỉ có thể
truy cập square và largeNumber trong hàm vì chúng có
phạm vi cục bộ (phạm vi hàm). Nhưng việc truy cập
AnotherLargerNumber bên ngoài khối sẽ gây ra lỗi
anotherLargerNumber is not defined.




Khai báo, gán lại giá trị (redeclare and reassign)



Giống như "var", biến được khai báo bằng từ khóa "let" có thể được gán lại một
giá trị khác nhưng không thể khai báo lại.





let number = 50
console.log(number) // 50

number = 100
console.log(number) // 100






Ở đây, mình đã gán lại một giá trị khác 100 sau khi khai báo ban đầu
50.



Nhưng khai báo lại một biến với let sẽ gây ra lỗi:






let number = 50

let number = 100
// SyntaxError: Identifier 'number' has already been declared





Chúng ta sẽ gặp lỗi như sau: Identifier 'number' has already been
declared.



Hoisting




Các biến được khai báo bằng "let" được hoist lên đầu phạm vi global,
local hoặc block của chúng, nhưng cách hoist của chúng hơi khác so với biến
được khai báo bằng từ khóa "var".




Các biến "var" được nâng lên với giá trị mặc định là
undefined, giúp chúng có thể truy cập được trước dòng khai báo của
chúng (như chúng ta đã thấy ở trên).




Tuy nhiên, các biến khai báo bằng "let" được hoist lên mà không cần
khởi tạo mặc định. Vì vậy, khi bạn cố gắng truy cập các biến như vậy, thay
vì gặp lỗi undefined hoặc variable is not defined, bạn sẽ nhận
được lỗi cannot access variable before initialization. Hãy xem một ví
dụ:






console.log(number)
// ReferenceError: Cannot access 'number' before initialization

let number = 50





Ở đây, chúng ta có một biến global, number được khai báo bằng
"let". Bằng cách cố gắng truy cập biến này trước dòng khai báo, chúng
ta nhận được lỗi ReferenceError: Cannot access variable before initialization.



Đây là một ví dụ khác với biến phạm vi local:





function print() {
let square = number * number

let number = 50
}

print()
// ReferenceError: Cannot access 'number' before initialization





Ở đây, chúng ta có một biến local (phạm vi hàm), number được khai báo
bằng "let". Bằng cách cố gắng truy cập biến này trước dòng khai báo,
chúng ta cũng nhận được lỗi ReferenceError: Cannot access variable before initialization.



Const



"const" cũng là một từ khóa ES6 để khai báo biến, nhưng khác với
"let", biến được khai báo bằng "const" là hằng số
(constant). Một hằng số không thể gán lại giá trị mới sau khi đã được
khởi tạo. Điều này đảm bảo rằng giá trị của hằng số không thay đổi trong quá
trình thực thi chương trình. Một hằng số cũng có phạm vi khối (block scope)
như "let".



Tính chất



Biến được khai báo bằng "const" cũng có phạm vi khối (block scope)
tương tự như "let". Tuy nhiên, biến được khai báo bằng "const"
là hằng số (constant) và không thể gán lại giá trị mới sau khi đã được
khởi tạo.




Các biến được khai báo với const tương tự như let về phạm vi.
Các biến như vậy có thể có phạm vi global, local hoặc
block.



Dưới đây là ví dụ:





const number = 50

function print() {
const square = number * number

if (number < 60) {
var largerNumber = 80
const anotherLargerNumber = 100

console.log(square)
}

console.log(largerNumber)
console.log(anotherLargerNumber)
}

print()
// 2500
// 80
// ReferenceError: anotherLargerNumber is not defined






Đây là từ ví dụ trước của chúng ta, nhưng mình đã thay let bằng
const. Như bạn có thể thấy ở đây, biến number có phạm vi
global, biến square có phạm vi local (được khai báo
trong hàm print) và otherLargeNumber có phạm vi
block (được khai báo bằng const).




Ngoài ra còn có largeNumber, được khai báo trong một block.
Nhưng vì nó được khai báo với từ khóa var nên biến chỉ có phạm vi cục
bộ (phạm vi hàm). Do đó, nó có thể được truy cập bên ngoài block (if).




Bởi vì AnotherLargeNumber có phạm vi là block, nên việc
truy cập nó bên ngoài blokc sẽ xảy ra lỗi
anotherLargerNumber is not defined.




Khai báo, gán lại giá trị (redeclare and reassign)




Về điểm này, const khác với var let.
const được sử dụng để khai báo các biến hằng – là các biến có giá trị
không thể thay đổi. Vì vậy, các biến như vậy không thể được khai báo lại và
chúng cũng không thể được gán lại cho các giá trị khác. Khi cố tình hay vô
tình gán lại giá trị thì sẽ xảy ra lỗi.



Hãy xem một ví dụ với khai báo lại:






const number = 50

const number = 100

// SyntaxError: Identifier 'number' has already been declared






Tại đây, bạn có thể thấy lỗi cú pháp
SyntaxError: Identifier 'number' has already been declared.




Bây giờ, hãy xem một ví dụ với việc gán lại:






const number = 50

number = 100

// TypeError: Assignment to constant variable





Tại đây, bạn có thể thấy lỗi
TypeError: Assignment to constant variable.




Hoisting




Các biến được khai báo với const, giống như let, được hoist
lên đầu phạm vi global, local hoặc block của chúng – nhưng không có khởi tạo
giá trị mặc định.




Các biến var, như bạn đã thấy trước đó, được hoist lên với giá trị
mặc định là undefined để có thể truy xuất chúng trước khi khai báo mà
không gặp lỗi. Truy cập một biến được khai báo với const trước dòng
khai báo sẽ đưa ra lỗi cannot access variable before initialization.



Hãy xem một ví dụ:






console.log(number)
// ReferenceError: Cannot access 'number' before initialization

const number = 50





Ở đây, số là một biến có phạm vi global được khai báo bằng const. Bằng
cách cố gắng truy cập biến này trước dòng khai báo, chúng ta sẽ nhận được
ReferenceError: Cannot access 'number' before initialization. Điều
tương tự cũng sẽ xảy ra nếu nó là một biến có phạm vi local.



Tóm lại


Dưới đây là hình ảnh tóm tắt nội dung:








Trong bài viết này đã giải thích rõ các yêu tố đóng vai trò quyết đinh
trong việc khai báo các biến trong Javascript.




Hành vi hoisting có thể gây ra các lỗi không mong muốn trong ứng dụng của bạn.
Đó là lý do tại sao các developer thường được khuyên nên tránh sử dụng biến
var và nên sử dụng với let const.



Lưu ý khi khai báo biến trong Javascript


Trong Javascript có 4 cách để khai báo một biến:




  • Automatically

  • Sử dụng var

  • Sử dụng let

  • Sử dụng const





Các cách sử dụng từ khóa như var, let và const thì các bạn đã biết ở trên. Vậy
automatically declared là như thế nào?




Nghĩa là chúng ta có thể sử dụng biến đó trong lần đầu mà không cần khai báo.



Ví dụ:





x = 5;
y = 6;
z = x + y;
console.log(z); // 11





Có thể thấy, mình không hề khai báo các biến x, y, z bằng các từ khóa
var, let const. Tuy nhiên, ta không nhận một cái lỗi
nào và đoạn code vẫn cho ra kết quả.




Vậy bạn có biết những biến x, y, z sẽ có những tính chất gì không? Cùng khám
phá thử nha!



Chúng ta vẫn dựa trên những yêu tố sau:



  • Phạm vi truy cập (scope).


  • Tính khai báo lại, gán lại giá trị (redeclaration and reassignment).

  • Hoisting.





1. Phạm vi truy cập (scope)


Xét ví dụ dưới đây:





number = 50

function print() {
square = number * number
console.log(square)
}

console.log(number) // 50

print() // 2500





Trong ví dụ trên, biến number được khai báo ở phạm vi global,
biến square được khai báo trong phạm vi là local (phạm vi hàm). Kết quả
cho thấy có thể truy cập đến biến number kể cả bên trong và bên ngoài
phạm vi hàm. 



Thêm một ví dụ nữa: 





function print() {
number = 50
square = number * number
console.log(square)
}

print() // 2500

console.log(number) // 50





Trong ví dụ trên,
biến number và biến square được khai
báo trong phạm vi là local (phạm vi hàm). Kết quả cho thấy có thể truy cập đến
biến number kể cả bạn khai báo bên trong phạm vi hàm. 




Hãy thử comment dòng sử dụng hàm print lại và bạn sẽ thấy lỗi
là ReferenceError: number is not defined.




2. Khai báo, gán lại giá trị (redeclare and reassign)




Bạn có thể gán lại giá trị mới cho biến automatically declared bất cứ lúc
nào, bất kỳ đâu trong chưng trình của bạn.



Ví dụ: 





function print() {
x = 3;
}

print()

console.log(x) // 3

x = 4;

console.log(x) // 4





Có thể thấy, mặc dù mình khai báo biến x trong hàm print tuy
nhiên mình vẫn có thể truy xuất và gán lại giá trị cho biến x ở bên
ngoài phạm vi hàm print.



3. Hoisting




Biến được khai báo dưới dạng automatically declared sẽ không được hoisting lên
đầu phạm vi như var, let const.



Hãy xem ví dụ dưới đây:



console.log(x)
x = 3
// ReferenceError: x is not defined



Mình cố truy cập biến x trước dòng khai báo của nó và nhận được một lỗi như sau: ReferenceError: x is not defined.


Nếu biến x được hoist lên đầu phạm vi thì ta sẽ nhận một lỗi là ReferenceError: Cannot access 'x' before initialization.



Điều này xảy ra tương tự với phạm vi local block.

Kết luận


Biến được khai báo dưới dạng automatically declared sẽ có những đặc điểm sau:
  • Có thể truy xuất đến biến đó ở bất kỳ đâu trong chương trình.
  • Có thể gán lại và khai báo lại.
  • Không được hoisting ở bất kì phạm vi nào.
Nếu bạn thắc mắc điều gì ở bài viết, hãy đừng gần ngại comment phía bên dưới để mình cùng thảo luận nha.

Hiju Blog

I'm HiJu

Post a Comment

Previous Post Next Post