Tổng quan

Kể từ năm 2017, khi mà những tính năng mới được đưa vào trong ES2015 (ES6), người ta thấy rằng đa số những lập trình viên Javascript đã cảm thấy quen thuộc và bắt đầu sử dụng những tính năng này.

Một trong những tính năng thật sự hữu ích được thêm vào là letconst, cái mà được sử dụng để khai báo biến. Câu hỏi ở đây là chúng có gì khác biệt so với var. Nếu bạn thật sự vẫn chưa phân biệt được sự khác nhau giữa var, letconst. Bài viết này là dành cho bạn.

Hiểu cách khai báo biến bằng var

Trước khi có sự bổ sung của ES6, khai báo biến bằng var là cách duy nhất trong javascript. Mặc dù vậy, đã có những vấn đề xảy ra với cách khai báo biến này. Vì thế, đó là lý do tại sao chúng ta cần có những cách khai báo biến mới. Trước hết, chúng ta hãy cùng thảo luận về var nhiều hơn và những vấn đề xảy ra khi ta dùng var

Scope của var

Scope theo cách dễ hình dung nhất là nơi mà những biến khai báo có thể sử dụng được ( không phải cứ khai báo biến là nơi nào cũng dùng được ). Khai báo bằng var có thể là globally scoped hoặc function scoped. Nó sẽ là globally scoped nếu var được khai báo bên ngoài function. Điều đó có nghĩa là tất cả các biến được khai báo bằng var và bên ngoài function thì có sẵn ở mọi nơi để sử dụng. Scope của biến varfunction scoped chỉ khi nó được khai báo trong function. Điều đó có nghĩa là biến này chỉ có thể được truy cập bên trong function mà nó khai báo.

Hãy xem ví dụ minh họa bên dưới:

var greeter = "hey hi";   
function newFunction() {       
    var hello = "hello";   
}

 Ở đây, biến greeter sử dụng var để khai báo biến và là 1 globally scoped vì nó nằm bên ngoài hàm, trong khi biến hello cũng sử dụng var để khai báo nhưng là 1 function scoped vì nó nằm bên trong hàm newFunction. Vì vậy chúng ta không thể truy cập biến hello ở bên ngoài hàm newFunction. Nếu chúng ta vẫn cố gắng truy cập đến biến hello ở bên ngoài hàm newFunction thì lỗi sẽ xuất hiện:

var greeter = "hey hi";     
function newFunction() {
    var hello = "hello";    
}    
console.log(hello); // error: hello is not defined

Ngược lại, nếu chúng ta truy cập biến greeter bên trong hàm newFunction thì vẫn được và không có lỗi xảy ra. Điều này là do biến greeter nằm trong globally scope nên được phép truy cập ở mọi nơi.

var greeter = "hey hi";    
function newFunction() {       
    var hello = "hello";       
    console.log(greeter)     
}
NewFunction() // Xuất ra: "hey hi"

Biến var có thể được khai báo lại hoặc cập nhập giá trị

Điều này có nghĩa là biến var được phép khai báo lại hoặc cập nhập giá trị nếu chúng ở trong cùng 1 scope ( có thể là globally scope hoặc function scope ). Hãy xem ví dụ bên dưới:

var greeter = "hey hi";
// Khai báo biến "var" greeter bên trong globally scope   

var greeter = "say Hello instead";
// Định nghĩa lại biến "var" greeter trong cùng globally scope  

console.log(greeter) 
var greeter = "hey hi";
// Khai báo biến "var" greeter bên trong globally scope   

greeter = "say Hello instead";
// Cập nhập giá trị biến "var" greeter trong cùng globally scope       

console.log(greeter) 

Hoisting của var

Hoisting  là một cơ chế trong Javascript. Theo cơ chế này, các biến và hàm sẽ được di chuyển lên trên đầu của đoạn mã trước khi chương trình thực thi . Điều này có nghĩa:

console.log(greeter)
var greeter = "hey hi";

Chương trình sẽ biên dịch đoạn mã này thành:

var greeter = undefined;
// Biến "var" greeter được đưa lên đầu đoạn mã
// và được gán giá trị undefined
console.log(greeter)
greeter = "hey hi";

Kết quả:
// undefined

Biến var sẽ được đưa lên đầu đoạn mã trong scope của nó ( trường hợp này là globally scoped ) và khởi tạo nó với giá trị là undefined

Vấn đề khi dùng var

Sử dụng var khi khai báo biến cũng có một vài nhược điểm của nó. Tôi sẽ dùng đoạn code sau để minh họa cho điều này:

var greeter = "hey hi";
var times = 4;
if (times > 3) {
    var greeter = "say Hello instead";     
}
console.log(greeter) 
// Xuất ra: say Hello instead

Vì biến times > 3 nên trả về true, greeter sẽ được định nghĩa lại thành “say Hello instead”. Vấn đề sẽ không có gì lớn nếu bạn biết biến greeter đã được định nghĩa lại, nhưng điều đó cũng đồng nghĩa rằng sẽ là vấn đề lớn nếu bạn không nhận biết rằng biến greeter đã được định nghĩa trước đó và bạn đã viết đè lên biến greeter mà không hề hay biết.

Nếu bạn sử dụng biến greeter ở đâu đó trong code, bạn có thể sẽ nhận được 1 output lạ. Điều này có thể là nguyên nhân gây ra lỗi trong code của bạn. Đó là lý do tại sao letconst lại cần thiết.

Hiểu cách khai báo biến bằng let

Hiện tại, let đang là cách được ưu tiên sử dụng hơn khi khai báo biến. Điều này cũng dễ hiểu bởi nó là phiên bản cải tiến so với cách khai báo bằng var. Nó cũng giải quyết được vấn đề mà ta gặp trong phần trước. Hãy cùng tìm hiểu cách mà let hoạt động nhé.

let là 1 block scope

1 block có thể hiểu đơn giản nhất là 1 khối mã được bao bọc bởi dấu ngoặc nhọn {} ( for … loop, if, while … ). Bất cứ cái gì nằm trong dấu {} đều là 1 block. Vì vậy, biến được khai báo bằng let bên trong 1 block scope chỉ khả dụng bên trong block đó ( bên ngoài block sẽ không truy cập tới biến này được ). Hãy để tôi giải thích điều này qua ví dụ sau:

let greeting = "say Hi";
let times = 4;
if (times > 3) {
    let hello = "say Hello instead";
    console.log(hello) //"say Hello instead"
}
 console.log(hello) // hello is not defined

Chúng ta thấy rằng biến hello được truy cập bên ngoài block scope của nó( Ở đây là câu lệnh if, nơi mà biến hello được định nghĩa ) sẽ trả về lỗi. Điều này là do biến let là 1 block scope, tức là nó chỉ tồn tại bên trong block scope của nó ( giữa 2 dấu ngoặc nhọn {…} ).

Nếu biến được khai báo với let không nằm trong 1 block nào, tức là không được bao bọc bởi dấu ngoặc nhọn ( {…} ). Thì cách hành xử của biến đó cũng giống như khi ta khai báo với var, ngoại trừ ta không thể khai báo lại biến đó giống như var. Điều ngược lại thì không đúng. 

let a = 5
function newFunc(){
  a = 6
}
newFunc()
console.log(a)
// Xuất ra: 6

let có thể được cập nhập giá trị nhưng không thể khai báo lại

Giống như var, biến được khai báo với let có thể được cập nhập giá trị nhưng không thể khai báo lại bên trong scope của nó.

let greeting = "say Hi";
greeting = "say Hello instead";
// Cập nhập giá trị cho biến greeting bên trong scope của nó
let greeting = "say Hi";
let greeting = "say Hello instead";
//error: Identifier 'greeting' has already been declared
// Biến được khai báo với "let" không thể định nghĩa lại bên trong scope của nó

Tuy nhiên, nếu cùng tên biến nhưng khác scope sẽ không xảy ra lỗi

let greeting = "say Hi" // Biến greeting này thuộc globally scope
if (true) {
    let greeting = "say Hello instead" // Biến greeting này thuộc block scope
    console.log(greeting) // Xuất ra: "say Hello instead"
}
console.log(greeting) // Xuất ra: "say Hi"

Tại sao không có lỗi xảy ra? Đó là do cả 2 biến greeting này được chương trình xử lý như là 2 biến riêng biệt nhau vì chúng khác scope ( 1 biến thuộc globally scope, 1 biến thuộc block scope ).

Thực tế chứng minh rằng let là lựa chọn tốt hơn so với var. Khi sử dụng let, bạn không phải bận tâm rằng bạn đã sử dụng tên này cho một biến nào trước đó hay chưa, vì chỉ có 1 biến duy nhất tồn tại trong 1 scope.  Một biến không thể được khai báo nhiều hơn 1 lần trong cùng 1 scope, nên vấn đề xảy ra với var ở phần trên đã không xảy ra với let.

Hosting của let

Giống như var, biến khai báo với let cũng được đưa lên đầu đoạn mã . Nhưng với var, giá trị của biến sẽ được khởi tạo giá trị undefined, biến với let sẽ không được khởi tạo giá trị. Nếu bạn sử dụng biến với let trước khi khai báo, bạn sẽ thấy xuất hiện lỗi ReferenceError

if(true){
    console.log(newVar)
    let newVar = "hello everyone"
}
// ReferenceError: Cannot access 'newVar' before initialization

Hiểu cách khai báo biến bằng const

Biến được khai báo với const sẽ hoạt động giống như 1 hằng số. Bạn không thể thay đổi được giá trị của biến const.

Giống với let, const là 1 block scope, tức là chỉ được phép truy cập bên trong block mà nó định nghĩa ( Dấu ngoặc nhọn { … } )

Biến const không thể khai báo lại và cũng không thể cập nhập giá trị.

Khi khai báo biến với const, phải có giá trị khởi tạo kèm theo lúc được khai báo

// var, let khi khai báo biến không cần gán giá trị
var newVar2;
let newVar;

// const khi khai báo biến cần phải gán giá trị
const newVar3 = "hello everyone";

Giá trị của biến const sẽ không thể thay đổi bên trong scope của nó. Nếu chúng ta khai báo 1 biến với const, chúng ta không thể cập nhập lại giá trị của nó:

const greeting = "say Hi";
greeting = "say Hello instead";
//error : Assignment to constant variable.

Và chúng ta không được phép khai báo lại :

const greeting = "say Hi"
const greeting = "say Hello instead"
//error : Identifier 'greeting' has already been declared

Cách hành xử sẽ hơi khác 1 chút khi chúng ta khai báo 1 object với const. Khi đó, object được khai báo với const sẽ không thể được cập nhập, nhưng các thuộc tính của object đó thì có thể cập nhập  giá trị.

const greeting = {
    message : "say Hi",
    times : 4    
}

Chúng ta không thể cập nhập object greeting này:

const greeting = {
    words : "Hello",
    number : "five" 
}
//error :  Assignment to constant variable.

Nhưng chúng ta được phép cập nhập giá trị cho các thuộc tính của object greeting đó:

greeting.message = "say Hello instead";

Khi đó object greeting sẽ là:

const greeting = {
    message : " say Hello instead ",
    times : 4
}

Hosting của const

Giống như let, biến khai báo với const sẽ được đưa lên đầu đoạn mã nhưng không được khởi tạo giá trị.

Tóm tắt lại những điều khác biệt của var, letconst:

  1. Khai báo biến bằng var chỉ có 2 trường hợp là globally scope hoặc function scope. Nếu biến var được khai báo trong dấu { … } thì biến đó thuộc globally scope. letconstblock scoped, tức là chỉ tồn tại bên trong 1 block ( Dấu { .. })
  2. Biến var có thể được cập nhập lại hoặc định nghĩa lại bên trong scope của nó. let có thể được cập nhập lại nhưng không được phép khai báo lại trong block của nó, const thì không thể cập nhập giá trị và cũng không được phép khai báo lại.
  3. Tất cả chúng đều được đưa lên đầu đoạn mã bên trong scope của chúng, nhưng khác biệt ở chỗ, biến var sẽ khởi tạo giá trị undefined, letconst không được khởi tạo giá trị.
  4. Trong khi varlet có thể được khai báo mà không có cần giá trị khởi tạo, const phải có giá trị khởi tạo lúc khai báo biến.

Bài tập

Bài 1:
function newFunc(){
    var a = 5
    if(true){
        console.log(a)
        a = a + 15
    }
    console.log(a)
}
newFunc()
Bài 2:
let a = "hello Robin Hood"
function newFunc(){
    var a = "Not, i'm RobinSon"
    console.log(a)
}
newFunc()
console.log(a)
Bài 3:
console.log("a = " + a)
console.log("b = " + b)
var a = "hello"
let b = "hey hi"
do {
    console.log("c = " + c)
    let c = "boniour"
}
while(false)

Bài 4:
function newFunc(){
    var a = 5
    if(true){
        console.log(a)
        let a = 15
    }
    console.log(a)
    }
newFunc()
Bài 5: Tự giải
function newFunc(){
    var name = "Yasuo"
    if(true){
        var age = 7
    }
    console.log(name)
    console.log(age)
}
newFunc()
console.log(age)

Kết quả

Bài 1:
// 5
// 20
Bài 2:
// Not, i'm RobinSon
// hello Robin Hood
Bài 3:
// a = undefined
// Biến a được khai báo với var nên chương trình sẽ đưa biến a lên đầu đoạn mã và gán giá trị của nó bằng undefined
// b = undefined
// Biến b được khai báo với let và nằm trong globally scope nên chương trình xử lý biến b giống như với var
// ReferenceError: Cannot access 'c' before initialization
// Biến c được khai báo với let và nằm trong 1 block ( câu lệnh do ... while ) 
// nên chương trình sẽ đưa biến c lên đầu block scope của nó và không khởi tạo giá trị cho nó
Bài 4:
// ReferenceError: Cannot access 'a' before initialization
// Biến a được khai báo với let và nằm trong 1 block ( câu lệnh if )
// nên chương trình sẽ đưa biến a lên block scope của nó và không khởi tạo giá trị.

Cảm ơn bạn đã đọc bài viết này. Hẹn gặp lại ở những bài viết sau của tôi.

Link tham khảo: https://dev.to/sarah_chima/var-let-and-const–whats-the-difference-69e

LEAVE A REPLY

Please enter your comment!
Please enter your name here