ES6: var, let and const — The battle between function scope and block scope
Bài đăng này đã không được cập nhật trong 6 năm
Như chúng ta đã biết, trước thời kỳ của ES6, chỉ có 1 cách duy nhất để khai báo biến trong Javascript - đó là sử dụng var
.
var
luôn có hào quang của 1 quan niệm sai lầm - điều này có lẽ do các biến khai báo với var
khác biệt ở hầu hết các ngôn ngữ lập trình. Với điều này, tất cả đều được giải thích với 1 thuật ngữ khá tự nhiên - scope.
var
là function scoped. Loại scope này có đôi chút khác biệt với loại scope được sử dụng nhiều hơn là block scoped
Chúng ta hãy xem xét xem điều này có ý nghĩa gì nhé!
var
— function scope
Như đã đề cập, một biến được khai báo sử dụng var
sẽ được gọi là function scoped, có nghĩa là nó sẽ tồn tại trong phạm vi của function được khai báo.
function myFunc() {
var name = 'Luke'
console.log(name); // 'Luke'
}
myFunc();
console.log(name); // name is not defined
Như bạn thấy, biến khai báo với var
bên trong hàm myFunc
, không thể truy cập từ bên ngoài.
Với điều đó, các loại khối khác - chẳng hạn như câu lệnh if, vòng ... sẽ không được coi là scope.
if(true) {
var name = 'Luke'
}
console.log(name); // 'Luke'
Sử dụng var
, chúng ta có thể truy cập biến name
bên ngoài if
trong khi nó được khai báo bên trong if
. Điều này là do chúng nằm trong cùng scope.
Tuy nhiên, với việc giới thiệu ES6, hiện nay chúng ta đã có thêm hai cách khai báo biến mới.
let
and const
— the introduction of block scope
Trong ES6, let
và const
đã được giới thiệu như là các cách khác để khai báo các biến - cả hai đều bị hạn chế trong phạm vi scope.
Điều này có thể sẽ giúp tiếp cận tốt hơn đối với những bạn đã quen với bất kỳ ngôn ngữ nào khác ngoài JavaScript.
Trong block scope, bất kỳ block
nào sẽ là một scope
. Điều này sẽ cung cấp cho một hành vi nhất quán hơn.
const
const
dùng để khai báo một hằng số - là một giá trị không thay đổi được trong suốt quá trình chạy.
const A = 5;
A = 10; // Lỗi Uncaught TypeError: Assignment to constant variable
let
let
tạo ra một biến chỉ có thể truy cập được trong block
bao quanh nó, khác với var
- tạo ra một biến có phạm vi truy cập xuyên suốt function
chứa nó.
function myFunc() {
let name = 'Luke'
console.log(name); // 'Luke'
}
myFunc();
console.log(name); // name is not defined
Khi sử dụng let
các block
khác cũng được coi là scope
- ví dụ như câu lệnh if
if(true) {
let name = 'Luke'
}
console.log(name); // name is not defined
Ngoài ra, khi ở global scope
(tức là không nằm trong một function
nào cả), từ khóa var
tạo ra thuộc tính mới cho global object (this
), còn let
thì không:
var x = 'global';
let y = 'global';
console.log(this.x); // "global"
console.log(this.y); // undefined
When function scope gets confusing
Bây giờ chúng ta đã nhận được sự khác biệt giữa phạm vi của function scope
và block scope
- chúng ta hãy xem tại sao điều này nhanh chóng có thể gây nhầm lẫn.
Hãy đặt 1 câu hỏi như thế này: Sẽ như thế nào nếu chúng ta có 1 biến local trong phạm vi scope
trùng tên với biến bên ngoài scope
đó ? Để trả lời hãy cùng xem ví dụ dưới đây:
var name = 'Luke';
const func = () => {
var name = 'Phil';
console.log(name); // 'Phil'
}
func();
console.log(name); // 'Luke'
Như mong đợi, biến name
trong scope
bên ngoài giữ giá trị khai báo ban đầu 'Luke'
ngay cả sau khi hàm func
chứa một biến cục bộ được đặt tên giống nhau - đã được thực thi.
Tuy nhiên vấn đề là vì function scope
chỉ bao gồm các function
chứ không phải các loại block
khác, chúng ta sẽ có một kết quả
khá khác khi thực hiện ví dụ này với các block
khác.
var name = 'Luke';
if (true) {
var name = 'Phil';
console.log(name); // 'Phil'
}
console.log(name); // 'Phil'
Trong trường hợp này biến name
sẽ bị ghi đè trong cậu lệnh if
và kết quả là ở cả 2 scope
ta đều nhận được kết quả là 'Phil'
.
Như bạn có thể tưởng tượng, với sự phức tạp ngày càng tăng có thể nhanh chóng trở thành một vấn đề thực sự đau đầu.
Bringing consistency with blocked scope
Vẫn là ví dụ như trên nhưng thay việc dùng var
bằng cách dùng let
để khai báo biến name
thì vấn đề đã được giải quyết và clear hơn rất nhiều.
let name = 'Luke';
const func = () => {
let name = 'Phil';
console.log(name); // 'Phil'
}
func();
console.log(name); // 'Luke'
let name = 'Luke';
if (true) {
let name = 'Phil';
console.log(name); // 'Phil'
}
console.log(name); // 'Luke'
Callback và let
Có một trường hợp dùng let
rất hiệu quả đó là sử dụng callback trong một vòng lặp.
Nếu dùng var
:
for (var i = 0; i < 5; i++) {
setTimeout(function(){
console.log('Yo! ', i);
}, 1000);
}
Kết quả sẽ ra gì nào?
Yo! 5
Yo! 5
Yo! 5
Yo! 5
Yo! 5
Giá trị của biến i
bên trong hàm callback luôn là giá trị cuối cùng của i
trong vòng lặp.
Để giải quyết vấn đề này, chúng ta thay var bằng let
:
for (let i = 0; i < 5; i++) {
setTimeout(function(){
console.log('Yo! ', i);
}, 1000);
}
Và kết quả sẽ đúng như mong đợi:
Yo! 0
Yo! 1
Yo! 2
Yo! 3
Yo! 4
Kết luật
Qua bài viết này chúng ta rút ra 3 điều (và chỉ áp dụng với ES6 thôi nhé ^^):
- Không dùng
var
trong bất kì mọi trường hợp - Thay vào đó thì dùng
let
- Dùng
const
khi cần định nghĩa một hằng số
Tham khảo
All rights reserved