Scope (phạm vi) và Scope Chain (chuỗi phạm vi) là những khái niệm quan trọng trong JavaScript để quản lý biến và cách chúng có thể được truy cập tại các vị trí khác nhau trong chương trình. Hiểu rõ hai khái niệm này giúp bạn tránh các lỗi liên quan đến biến và có thể tối ưu hóa mã của mình một cách hiệu quả.
Scope là ngữ cảnh hoặc phạm vi mà các biến, hàm, và đối tượng có thể được truy cập. Trong JavaScript, có ba loại phạm vi chính:
Các biến được khai báo ở ngoài tất cả các hàm hoặc khối mã thuộc về phạm vi toàn cục (global scope). Các biến toàn cục có thể được truy cập ở bất kỳ đâu trong mã.
var globalVar = "I'm global"; function showGlobal() { console.log(globalVar); // Có thể truy cập biến toàn cục } showGlobal(); // "I'm global" console.log(globalVar); // "I'm global"
Trong ví dụ này, globalVar
là biến toàn cục và có thể được truy cập từ bất kỳ đâu.
Các biến được khai báo bên trong một hàm chỉ có thể được truy cập từ bên trong hàm đó. Đây là phạm vi hàm, và các biến này được gọi là biến cục bộ (local variables).
function myFunction() { var localVar = "I'm local"; console.log(localVar); // Có thể truy cập biến cục bộ } myFunction(); // "I'm local" console.log(localVar); // Lỗi: localVar không được định nghĩa
Ở đây, localVar
là biến cục bộ và chỉ có thể được truy cập bên trong hàm myFunction
. Bên ngoài hàm, nó không tồn tại.
Với từ khóa let
và const
(giới thiệu trong ES6), các biến khai báo bên trong một cặp dấu {}
(block) có phạm vi chỉ tồn tại trong khối đó. Điều này khác với var
, vì var
không có phạm vi khối, mà chỉ có phạm vi hàm.
if (true) { let blockScopedVar = "I'm block-scoped"; console.log(blockScopedVar); // "I'm block-scoped" } console.log(blockScopedVar); // Lỗi: blockScopedVar không được định nghĩa
Trong ví dụ này, blockScopedVar
chỉ tồn tại trong khối if
và không thể được truy cập từ bên ngoài.
var
, let
và const
:var
: Có phạm vi hàm hoặc phạm vi toàn cục. Không có phạm vi khối.let
và const
: Có phạm vi khối và không bị nâng (hoisting) giống như var
.Scope Chain là cơ chế cho phép JavaScript tra cứu các biến trong các phạm vi khác nhau. Khi một biến được truy cập, JavaScript sẽ kiểm tra phạm vi hiện tại để xem biến đó có tồn tại hay không. Nếu không tìm thấy, nó sẽ tiếp tục kiểm tra các phạm vi bao quanh (gọi là chuỗi phạm vi) cho đến khi tìm thấy biến hoặc gặp phải phạm vi toàn cục.
Nói cách khác, Scope Chain là chuỗi các đối tượng phạm vi mà JavaScript sử dụng để tìm kiếm các biến. Nó bắt đầu từ phạm vi hiện tại và tiếp tục đến phạm vi toàn cục.
var globalVar = "I'm global"; function outerFunction() { var outerVar = "I'm outer"; function innerFunction() { var innerVar = "I'm inner"; console.log(innerVar); // Có thể truy cập innerVar console.log(outerVar); // Có thể truy cập outerVar qua scope chain console.log(globalVar); // Có thể truy cập globalVar qua scope chain } innerFunction(); } outerFunction();
innerFunction
có thể truy cập được biến innerVar
từ phạm vi cục bộ (phạm vi hàm).outerFunction
, để truy cập biến outerVar
.globalVar
.let a = 'global'; function outer() { let b = 'outer'; function inner() { let c = 'inner'; console.log(a); // Tìm trong phạm vi hiện tại (inner), không có, tìm tiếp ở outer, vẫn không có, tìm ở global -> 'global' console.log(b); // Tìm trong phạm vi hiện tại (inner), không có, tìm tiếp ở outer -> 'outer' console.log(c); // Tìm thấy ở phạm vi hiện tại (inner) -> 'inner' } inner(); } outer();
JavaScript sử dụng Lexical Scoping, nghĩa là phạm vi của các biến được xác định dựa trên vị trí của chúng trong mã nguồn. Khi JavaScript được biên dịch, các phạm vi được xác định theo thứ tự của mã. Điều này có nghĩa là phạm vi không thay đổi khi chương trình chạy, nó đã được xác định từ trước dựa trên cấu trúc của mã.
Hoisting là một khái niệm trong JavaScript mà các khai báo biến (var
, let
, const
) và hàm được “đưa lên đầu” (hoisted) phạm vi hiện tại của chúng trước khi mã được thực thi. Tuy nhiên, chỉ có phần khai báo được hoisting, không phải phần gán giá trị.
Ví dụ với var
:
console.log(x); // undefined var x = 5;
Ở đây, biến x
được hoisting, nhưng giá trị của nó chưa được gán, vì vậy kết quả là undefined
.
Với let
và const
, hoisting vẫn xảy ra, nhưng chúng không được khởi tạo cho đến khi lệnh gán giá trị được thực hiện:
console.log(y); // Lỗi: y không được định nghĩa let y = 10;
var
, let
, và const
.