Có hai loại scope (phạm vi) chính của biến trong JavaScript, là globallocal. Ngoài ra còn có phạm vi window.

Global Scope

Là phạm vi toàn cục, biến có phạm vi này có thể được truy cập từ bất cứ đâu trong file code.

Vòng đời của biến global chỉ kết thúc khi chương trình bị thoát. Điều này đồng nghĩa với việc khi chúng ta refresh hay tắt trang web, vùng nhớ của biến global sẽ bị mất.

Function Scope

Khi một hàm (function) được gọi thực thi, một phạm vi mới sẽ được tạo ra, có tên là function scope. Đoạn code dưới đây có hai loại phạm vi: global và function.

var a = 10 // global scope
 
function f() {
	// inside function scope
} // global scope
 
f() // function scope was created

Hàm sẽ có global scope hoặc function scope tùy vào vị trí khai báo. Ví dụ:

{
	function f() {} // global scope
}
 
function g() {
	function f() {} // function scope
}

Các hàm có thể truy cập đến các biến nằm bên ngoài phạm vi của nó:

var b = 2 // global scope
 
function mod(a) {
	// inside function scope
	return a % b
}
 
console.log(mod(7)) // 1
console.log(mod(10)) // 0

Nếu một biến a được khai báo bên trong hàm f và được tham chiếu bởi một hàm g khác cũng bên trong hàm f, thì biến a sẽ không kết thúc vòng đời sau khi f được gọi.

Ví dụ:

function createCounter() {
	let count = 0
 
	function increase() {
		return ++count
	}
	
	return increase
}
 
const counter = createCounter()
 
console.log(counter()) // 1
console.log(counter()) // 2
console.log(counter()) // 3

Seealso

Tham khảo thêm về tính chất này ở khái niệm Closure.

Declare a Variable with var Keyword

Nếu dùng var để khai báo biến thì biến sẽ có global scope hoặc function scope tùy vào vị trí khai báo:

  • Khai báo bên ngoài hàm thì biến mang global scope.
  • Khai báo bên trong hàm thì biến mang function scope.

Ví dụ 1:

var a = 10 // global scope
 
{
	var b = 11 // global scope
}

Ví dụ 2:

function f() {
	var a = 9 // function scope
	
	console.log(a) // 9
}
 
console.log(a) // a is not defined

Khi dùng từ khóa var, ta có thể tái khai báo một biến nhiều lần mà không bị lỗi, kể cả khi sử dụng strict mode.

var a = 1
var a = 2
console.log(a) // 2
var a
console.log(a) // 2; not undefined

Nếu lần khai báo sau không gán giá trị thì giá trị của biến không thay đổi.

Xét trường hợp ta khai báo một biến có giá trị khởi tạo và một hàm trùng tên:

var a = 1
function a() {}
console.log(a) // 1

Biến a có giá trị là 1 là bởi vì khai báo hàm bằng từ khóa function được hoist1 lên trước phép gán giá trị. Đoạn code trên tương đương với:

var a
function a() {}
a = 1
console.log(a) // 1

Biến khai báo bằng var trùng tên với tham số sẽ override giá trị của đối số:

function foo(a) {
  var a = 1
  console.log(a)
}
 
foo(2) // Logs 1

Seealso

Xem thêm var - MDN Web Docs

Local Scope

Là phạm vi cục bộ, các biến mang phạm vi này không thể truy cập được từ bên ngoài khối lệnh.

  • Khối lệnh có thể là hàm, vòng lặp, câu lệnh điều kiện. Cụ thể hơn, khối lệnh bắt đầu và kết thúc bởi cặp dấu ngoặc nhọn ({}).
  • Vòng đời của biến local chỉ kết thúc khi ra khỏi khối lệnh, hoặc sau khi kết thúc function scope, tức là sau khi hàm được gọi.
  • Khai báo bằng letconst ở bên trong khối lệnh mang phạm vi block scope (hay local scope). Nếu khai báo let hoặc const ở bên ngoài khối lệnh, chúng sẽ mang global scope.
let a = 10 // global scope
 
{
	let a = 11 // local scope
	console.log(a) // 11
}
 
console.log(a) // 10

Các biến thuộc các scope khác nhau có thể trùng tên với nhau. Khi xử lý logic, sẽ ưu tiên dùng biến có phạm vi gần nhất. Trong đoạn code trên, hàm console.log() sẽ ưu tiên sử dụng biến a = 11.

Khi nào thì cần sử dụng let hoặc const?

  • let dùng khi khai báo những tên biến mà chúng ta muốn reassign, tức gán lại cho chúng bằng cách dùng các toán tử gán, chẳng hạn như =, +=, -=, etc.
  • const dùng khi khai báo những tên biến không nên gán lại, chẳng hạn như một mảng, hàm hoặc object (các đối tượng).

Ví dụ:

let num = 10
let str = 'Hello world'
let bool = false
 
const arr = [1, 2, 3]
const sum = function(a, b) {
	return a + b
}
const object = {
	prop: 'value'
}

Comparison

Bảng so sánh các loại khai báo biến:

varletconst
Có thể khai báo lạiKhông thể khai báo lạiKhông thể khai báo lại
Có thể gán lại giá trịCó thể gán lại giá trịKhông thể gán lại giá trị
Chỉ có phạm vi toàn cục và phạm vi hàmCó thể có phạm vi khốiCó thể có phạm vi khối
Biến được đưa lên đầu và có thể sử dụng ở bất kỳ đâuBiến phải được khởi tạo trước khi sử dụngBiến phải được khởi tạo trước khi sử dụng
Có thể khai báo lại ở bất kỳ đâu trong chương trìnhCó thể khai báo lại trong một khốiKhông bao giờ được khai báo lại

Window Scope

Khi khai báo biến bằng console của trình duyệt mà không sử dụng từ khóa khai báo (var, let, const), ta nhận được các biến có phạm vi cửa sổ.

Về bản chất, các biến này sẽ là thuộc tính của đối tượng window. Mà đối tượng này là một đối tượng toàn cục, nên các biến này cũng được xem là các biến toàn cục.

Các biến này chỉ hoạt động trong cửa sổ trình duyệt, không thể hoạt động trong môi trường khác, chẳng hạn như NodeJS.

// Browser console
a = "Hello world"
b = 10
 
console.log(a) // Hello world
console.log(b) // 10

Hai dòng khai báo ở trên tương đương với:

window.a = "Hello world"
window.b = 10
table tags as Tags, file.cday as Created
from [[JS Scopes]]
sort file.ctime asc

Resources

Footnotes

  1. xem thêm Hoisting