Phạm vi biến toàn cục và biến cục bộ

Trong bài viết này, bạn sẽ tìm hiểu về phạm vi và cách hoạt động của phạm vi trong JavaScript.

Phạm vi là một khái niệm cơ bản trong JavaScript và các ngôn ngữ lập trình khác, giúp xác định ngữ cảnh mà các biến được truy cập và sử dụng. Việc này trở nên hữu ích và phù hợp hơn với mã của bạn khi bạn tiếp tục tìm hiểu JavaScript và xử lý nhiều biến hơn với các biến.

Phạm vi có thể giúp bạn:

  • Sử dụng bộ nhớ hiệu quả hơn: Phạm vi cho phép tải các biến chỉ khi cần. Nếu một biến nằm ngoài phạm vi, bạn không cần cung cấp biến đó cho mã đang thực thi.
  • Tìm và sửa lỗi dễ dàng hơn: Việc tách biệt các biến có phạm vi cục bộ giúp bạn dễ dàng khắc phục lỗi trong mã của mình hơn vì, không giống như các biến toàn cục, bạn có thể tin tưởng rằng mã từ phạm vi bên ngoài không thể thao tác với các biến trong phạm vi cục bộ.
  • Tạo các khối mã nhỏ có thể sử dụng lại: Ví dụ: bạn có thể viết một hàm thuần tuý không phụ thuộc vào phạm vi bên ngoài. Bạn có thể dễ dàng di chuyển một hàm như vậy đến nơi khác mà không cần thay đổi gì.

Phạm vi là gì?

Phạm vi của biến xác định vị trí bạn có thể sử dụng biến trong đoạn mã.

JavaScript xác định các biến có phạm vi toàn cục hoặc toàn phạm vi:

  • Bạn có thể sử dụng các biến có phạm vi toàn cục trong tất cả các phạm vi khác trong mã JavaScript.
  • Các biến có phạm vi cục bộ chỉ dùng được trong một ngữ cảnh địa phương cụ thể và được tạo bằng các từ khoá, chẳng hạn như var, letconst. Nếu bạn sử dụng từ khoá var, let hoặc const để tạo một biến trong một hàm, thì biến đó có phạm vi cục bộ.

Các phần sau của bài viết này thảo luận về phạm vi khối và từ vựng:

  • Biến phạm vi chặn có sẵn cục bộ cho một khối được xác định theo vị trí của dấu ngoặc nhọn nơi câu lệnh khối được xác định. Chỉ những biến được khai báo bằng từ khoá let hoặc const mới có phạm vi khối.
  • Phạm vi Lexical sử dụng vị trí mà biến được khai báo trong mã nguồn để xác định vị trí có thể sử dụng biến đó. Bạn sử dụng phương thức đóng để cấp cho một hàm kèm theo quyền truy cập vào các biến được tham chiếu trong phạm vi bên ngoài được gọi là môi trường từ vựng.

Khi biến được truy cập trong phạm vi của biến, JavaScript sẽ trả về giá trị được chỉ định hoặc tạo ra lỗi.

Cách khai báo biến:

  • Dùng từ khoá var, const hoặc let để khai báo biến trong phạm vi cục bộ hoặc biến trong phạm vi toàn cầu.
  • Dùng từ khoá const hoặc let để khai báo các biến trong phạm vi khối.

Khi bạn khai báo biến var trong một hàm, phần khai báo sẽ cung cấp biến cho hàm đóng gần nhất. Bạn không thể dùng từ khoá var để khai báo các biến có phạm vi khối.

Ví dụ về phạm vi

Ví dụ này minh hoạ phạm vi toàn cục vì biến greeting được khai báo bên ngoài bất kỳ hàm hoặc khối nào, do đó giá trị của biến đó được cung cấp cho tất cả mã trong tài liệu hiện tại:

const greeting = 'hello';
console.log(greeting); // 'hello'

Trong ví dụ về phạm vi toàn cầu, biến greeting được gán giá trị hello.

Ví dụ này minh hoạ phạm vi cục bộ vì phạm vi này khai báo biến greeting có từ khoá let trong một hàm. Biến greeting là biến trong phạm vi cục bộ và không có sẵn bên ngoài hàm.

function greet() {
  let greeting = 'Hello World!';
  console.log(greeting);
}

Ví dụ dưới đây minh hoạ phạm vi của khối vì phạm vi này khai báo biến greeting trong một khối sao cho chỉ có thể truy cập biến trong các dấu ngoặc nhọn:

if (true) {
   const greeting = 'hello';
}

console.log(greeting); // ReferenceError: greeting is not defined

Xin lưu ý rằng khi hàm console.log cố gắng xuất giá trị của biến greeting, JavaScript sẽ trả về thông báo lỗi ReferenceError thay vì thông báo hello như dự kiến. Tại sao?

Một lỗi được trả về vì biến greeting có phạm vi khối và khối gần nhất là một phần của câu lệnh có điều kiện if. Bạn không thể truy cập vào các biến letconst mà bạn khai báo bên trong một khối từ bên ngoài khối đó. Do đó, bạn chỉ có thể truy cập vào biến greeting trong dấu ngoặc nhọn. Biến này chỉ định phạm vi khối.

Ví dụ này sửa lỗi vì di chuyển phương thức console.log(message) bên trong dấu ngoặc nhọn. Mã cập nhật sẽ định vị phương thức console.log(message) bên trong khối.

if (true) {
   const greeting = 'hello';
   console.log(greeting);
}

Loại phạm vi

Phạm vi trên toàn cầu

Bạn có thể truy cập vào các biến có phạm vi toàn cục từ bất cứ đâu trong chương trình.

Hãy xem xét tệp HTML nhập hai tệp JavaScript: file-1.jsfile-2.js:

<script src="file-1.js"></script>
<script src="file-2.js"></script>

Trong ví dụ này, biến globalMessage có phạm vi toàn cục và được viết bên ngoài một hàm. Trong quá trình chạy và thực thi, bạn có thể truy cập vào giá trị của biến globalMessage từ bất cứ đâu trong chương trình JavaScript.

Bạn có thể xem nội dung của các tệp file-1.jsfile-2.js trong đoạn mã này. Hãy lưu ý rằng biến globalMessage có sẵn trong cả hai tệp.

// file-1.js
function hello() {
    var localMessage = 'Hello!';
}

var globalMessage = 'Hey there!';

// file-2.js
console.log(localMessage); // localMessage is not defined
console.log(globalMessage); // Hey there!

Có một loại phạm vi khác không được thảo luận nhiều trong bài viết này. Nếu bạn tạo một biến trong một mô-đun JavaScript nhưng bên ngoài một hàm hoặc khối, thì biến đó sẽ không có phạm vi toàn cục mà sẽ có phạm vi mô-đun. Các biến có phạm vi mô-đun có sẵn ở mọi nơi trong mô-đun hiện tại, nhưng không có trong các tệp hoặc mô-đun khác. Để có thể sử dụng biến trong phạm vi mô-đun cho các tệp khác, bạn phải xuất biến đó từ mô-đun mà biến đó được tạo, sau đó import biến đó từ mô-đun cần truy cập vào biến đó.

Phạm vi cục bộ và phạm vi hàm

Khi bạn tạo biến trong hàm JavaScript bằng từ khoá var, let hoặc const, các biến đó nằm trong hàm cục bộ, nên bạn chỉ có thể truy cập vào chúng từ trong hàm. Biến cục bộ được tạo khi một hàm bắt đầu và bị xoá một cách hiệu quả khi quá trình thực thi hàm kết thúc.

Ví dụ này khai báo biến total trong hàm addNumbers(). Bạn chỉ có thể truy cập vào các biến a, b,total trong hàm addNumbers().

function addNumbers(a, b) {
    const total = a + b;
}

addNumbers(3, 4);

Bạn có thể dùng từ khoá letconst để đặt tên cho biến. Khi bạn sử dụng từ khoá let, JavaScript có thể cập nhật biến. Tuy nhiên, với từ khoá const, biến này không đổi.

var variable1 = 'Declared with var';
var variable1 = 'Redeclared with var';
variable1; // Redeclared with var

let variable2 = 'Declared with let. Cannot be redeclared.';
variable2 = 'let cannot be redeclared, but can be updated';
variable2; // let cannot be redeclared, but can be updated

const variable3 = 'Declared with const. Cannot be redeclared or updated';
variable3; // Declared with const. Cannot be redeclared or updated

Chặn phạm vi

Chặn được dùng để nhóm một câu lệnh hoặc một tập hợp các câu lệnh với nhau. Bạn có thể sử dụng từ khoá const hoặc let để khai báo biến cục bộ ở phạm vi khối. Xin lưu ý rằng bạn không thể dùng từ khoá var để khai báo các biến có phạm vi khối.

Ví dụ: trong khối này, phạm vi của biến name và giá trị "Elizabeth" của biến đó nằm trong dấu ngoặc nhọn. Bạn sẽ không thể sử dụng các biến trong phạm vi khối đó bên ngoài khối đó.

{
    const name = "Elizabeth";
}

Bạn có thể sử dụng các biến trong phạm vi khối trong câu lệnh if, for hoặc while.

Ghi lại 2 vòng lặp for trong đoạn mã này. Một vòng lặp for sử dụng từ khoá var để khai báo biến khởi tạo. Biến này sẽ tăng lên qua các chữ số 0, 12. Vòng lặp for khác dùng từ khoá let để khai báo biến khởi tạo.

for (var i = 0; i < 2; i++) {
    // ...
}

console.log(i); // 2

for (let j = 0; j < 2; j++) {
    // ...
}

console.log(j); // The j variable isn't defined.

Trong ví dụ về mã nêu trên, bạn có thể nhận thấy biến i trong vòng lặp for đầu tiên bị rò rỉ ra bên ngoài vòng lặp for nhưng vẫn giữ lại giá trị 2 vì từ khoá var không sử dụng phạm vi khối. Vấn đề này được khắc phục trong vòng lặp for thứ hai, trong đó biến j được khai báo bằng từ khoá let nằm trong phạm vi khối của vòng lặp for và không tồn tại sau khi vòng lặp for kết thúc.

Sử dụng lại tên biến trong một phạm vi khác

Phạm vi có thể tách riêng biến trong một hàm, ngay cả khi bạn sử dụng lại cùng một tên biến ở nơi khác trong phạm vi khác.

Ví dụ dưới đây cho bạn thấy cách sử dụng phạm vi cho phép bạn sử dụng lại cùng một tên biến trong nhiều hàm:

function listOne() {
    let listItems = 10;
    console.log(listItems); // 10
}

function listTwo() {
   let listItems = 20;
   console.log(listItems); // 20
}

listOne();
listTwo();

Biến listItems trong hàm listOne()listTwo() được gán giá trị dự kiến và do đó không xung đột với nhau.

Lệnh đóng và phạm vi từ vựng

Trường hợp đóng đề cập đến một hàm kèm theo, trong đó hàm bên trong có thể truy cập vào phạm vi hàm bên ngoài, còn gọi là môi trường từ vựng. Do đó, trong JavaScript, bạn sử dụng phương thức đóng để cho phép các hàm tham chiếu đến môi trường từ vựng bên ngoài, cho phép mã bên trong một hàm tham chiếu các biến được khai báo bên ngoài hàm. Trên thực tế, bạn có thể mã hoá một chuỗi tham chiếu đến các môi trường từ vựng bên ngoài để một hàm được gọi bằng một hàm khác, rồi lại được gọi bằng một hàm khác.

Trong ví dụ này, mã tạo thành một trạng thái đóng bằng môi trường từ vựng được tạo khi hàm outer() được gọi, hàm này đóng trên biến hello. Do đó, biến hello được dùng trong hàm callback setTimeout.

function outer() {
    const hello = 'world';

    setTimeout(function () {
        console.log('Within the closure!', hello)
    }, 100);
}

outer();

Với phạm vi từ vựng, phạm vi được xác định trong quá trình biên dịch mã nguồn, chứ không phải trong thời gian chạy. Để tìm hiểu thêm về môi trường từ vựng, hãy xem bài viết Phạm vi từ vựng và đóng.

Mô-đun

Các mô-đun JavaScript giúp sắp xếp mã JavaScript. Được sử dụng đúng cách, chúng tạo ra một cấu trúc hiệu quả cho cơ sở mã của bạn và giúp bạn sử dụng lại mã. Thay vì sử dụng biến toàn cục để chia sẻ biến trên các tệp khác nhau, mô-đun JavaScript cung cấp kỹ thuật xuấtimport biến.

// hello.js file
function hello() {
  return 'Hello world!';
}

export { hello };

// app.js file
import { hello } from './hello.js';

console.log(hello()); // Hello world!

Bản minh hoạ trình hiển thị phạm vi

Phạm vi là một khái niệm cơ bản mà mọi nhà phát triển JavaScript đều phải hiểu. Để hiểu rõ hơn về hệ thống phạm vi, bạn có thể thử viết mã của riêng mình bằng JS Scope Visualizer. Bản minh hoạ sử dụng tính năng tô màu trong mã để giúp bạn trực quan hoá phạm vi JavaScript.

Kết luận

Bài viết này giới thiệu nhiều loại phạm vi. Phạm vi JavaScript là một trong những khái niệm nâng cao hơn trong lĩnh vực phát triển web, vì vậy, bạn nên đọc nội dung này và dành thời gian để hiểu rõ chủ đề này.

Phạm vi không phải là một tính năng dành cho người dùng. Phạm vi này chỉ ảnh hưởng đến nhà phát triển web viết mã, nhưng kiến thức về cách hoạt động của phạm vi có thể giúp bạn sửa lỗi khi chúng phát sinh.