Skip to content
Home
Go back

Scope trong JavaScript: Global, Function và Block

Mục lục

Xem mục lục

Scope là gì

Scope là phạm vi mà một biến có thể được truy cập trong code. Một biến chỉ tồn tại và dùng được trong đúng phạm vi nó được khai báo, cộng với các phạm vi con lồng bên trong đó.

JavaScript có ba loại scope chính: Global Scope, Function Scope, và Block Scope.

Global Scope

Global Scope là phạm vi truy cập được từ bất kỳ đâu trong chương trình. Biến khai báo ngoài mọi function và block thuộc phạm vi này:

const appName = "MyApp";

function show() {
  console.log(appName); // truy cập được từ bên trong function
}

show();

Lạm dụng Global Scope dễ gây ô nhiễm global namespace: nhiều phần code không liên quan cùng đọc/ghi chung một biến toàn cục, dẫn tới xung đột tên hoặc thay đổi giá trị ngoài ý muốn. Nguyên tắc chung là khai báo biến ở scope hẹp nhất đủ dùng, tránh đẩy biến lên Global Scope khi không cần thiết.

Function Scope

Function Scope là phạm vi giới hạn trong một function, biến khai báo bên trong chỉ truy cập được từ function đó trở xuống.

Biến khai báo bằng var thuộc loại Function Scope, bị giới hạn trong function chứa nó, nhưng không bị chặn bởi block như if, for, while:

function example() {
  if (true) {
    var x = 10;
  }
  console.log(x); // 10, vẫn truy cập được dù khai báo trong block if
}

x được khai báo bên trong khối if, nhưng vì var chỉ tôn trọng ranh giới function, không tôn trọng ranh giới block, nên x vẫn truy cập được ở ngoài khối if, miễn còn trong cùng function.

Block Scope

Block Scope là phạm vi giới hạn trong một khối lệnh, tức cặp dấu ngoặc {} gần nhất, như thân của if, for, while, hoặc một khối {} đứng độc lập. Biến khai báo bên trong khối chỉ truy cập được trong chính khối đó, ra khỏi khối là hết phạm vi.

let và const thuộc loại Block Scope, giới hạn trong cặp {} gần nhất, đây là khác biệt cốt lõi so với var:

function example() {
  if (true) {
    let y = 10;
  }
  console.log(y); // ReferenceError: y is not defined
}

y chỉ tồn tại bên trong khối if, ra khỏi khối đó là hết phạm vi truy cập. Block scope áp dụng cho mọi cặp {}: if, for, while, hoặc một khối {} đứng độc lập không gắn với cấu trúc điều khiển nào.

Temporal Dead Zone (TDZ)

Temporal Dead Zone (TDZ) là khoảng thời gian từ đầu scope cho tới dòng khai báo biến, trong đó biến let/const đã tồn tại nhưng chưa thể truy cập.

var bị hoisting lên đầu function và được gán giá trị mặc định undefined, nên truy cập trước dòng khai báo không gây lỗi, chỉ trả về undefined:

console.log(a); // undefined, không lỗi
var a = 5;

let và const cũng bị hoisting, nhưng không được gán undefined ngay, nên truy cập trong TDZ gây lỗi:

console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 5;

TDZ giúp phát hiện sớm lỗi dùng biến trước khi khai báo, thay vì để chương trình chạy tiếp với giá trị undefined một cách âm thầm như var.

Scope Chain

Khi engine cần tìm giá trị của một biến, nó tìm theo thứ tự từ scope trong cùng ra ngoài dần, gọi là Scope Chain, và dừng lại ngay khi tìm thấy biến ở scope gần nhất:

const outerVar = "outer";

function outer() {
  const middleVar = "middle";

  function inner() {
    const innerVar = "inner";
    console.log(innerVar); // tìm thấy ngay trong scope của inner
    console.log(middleVar); // không có trong inner, tìm tiếp ra outer
    console.log(outerVar); // không có trong outer, tìm tiếp ra Global Scope
  }

  inner();
}

outer();

Nếu đi hết Scope Chain tới Global Scope mà vẫn không tìm thấy biến, engine mới báo ReferenceError.

Ví dụ minh họa Scope Chain và Shadowing

Khi một biến ở scope trong trùng tên với biến ở scope ngoài, biến ở scope trong che khuất (shadowing) biến ở scope ngoài, trong phạm vi scope đó:

const value = "global";

function outer() {
  const value = "outer";

  function inner() {
    const value = "inner";
    console.log(value); // "inner"
  }

  inner();
  console.log(value); // "outer"
}

outer();
console.log(value); // "global"

Mỗi scope có bản khai báo value riêng. Scope Chain luôn dừng lại ở bản khai báo gần nhất, nên inner không thể truy cập trực tiếp value của outer hay Global Scope một khi đã tự khai báo value của chính nó.

Vì sao nên ưu tiên let/const hơn var

Tổng hợp các vấn đề của var đã nêu ở trên:

var count = 1;
var count = 2; // không lỗi, count bị ghi đè âm thầm

let total = 1;
let total = 2; // SyntaxError: Identifier 'total' has already been declared

let và const khắc phục cả ba vấn đề trên: có block scope rõ ràng, có TDZ để báo lỗi sớm, và không cho khai báo trùng tên trong cùng scope. Khuyến nghị thực tế là mặc định dùng const, chỉ chuyển sang let khi biến cần được gán lại giá trị, và tránh dùng var trong code mới.

Tổng kết


Share this post:

Previous Post
Higher-Order Function trong JavaScript
Next Post
Container và Presentational Components trong React