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. Phạm vi là một khái niệm xác định ngữ cảnh mà các biến được truy cập và sử dụng. Điều này sẽ trở nên hữu ích và dễ áp dụng hơn cho mã của bạn khi bạn tiếp tục tìm hiểu JavaScript và làm việc nhiều 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 thì 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 với phạm vi cục bộ giúp bạn dễ dàng khắc phục lỗi trong mã hơn vì không giống như 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úng các biến có 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 dựa 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 sang nơi khác mà không cần thay đổi gì nhiều.

Phạm vi là gì?

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

JavaScript xác định các biến thuộc phạm vi toàn cầu hoặc cục bộ:

  • Các biến có phạm vi toàn cục có sẵn từ 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 ngữ cảnh cục bộ 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 các 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 trong 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 khối 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 được xác định trong câu lệnh khối. Chỉ các biến được khai báo bằng từ khoá let hoặc const mới có phạm vi chặn.
  • Phạm vi phạm vi sử dụng vị trí mà một biến được khai báo trong mã nguồn để xác định nơi biến đó có sẵn. Bạn sử dụng hàm đó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 ở phạm vi bên ngoài được gọi là môi trường từ vựng.

Khi một 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 một biến:

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

Khi bạn khai báo biến var trong một hàm, nội dung khai báo sẽ cung cấp biến đó cho hàm bao quanh 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 hàm hoặc khối bất kỳ. Điều này giúp tất cả mã trong tài liệu hiện tại có thể sử dụng giá trị của biến này:

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ì khai báo biến greeting bằng từ khoá let trong một hàm. Biến greeting là một biến có phạm vi cục bộ và không sử dụng được bên ngoài hàm.

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

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

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

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

Lưu ý 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?

Hệ thống trả về lỗi 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 các dấu ngoặc nhọn. Mã đã cập nhật sẽ chuyển phương thức console.log(message) vào trong khối.

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

Loại phạm vi

Phạm vi 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 kỳ đâ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 khi 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 kỳ vị trí nào trong chương trình JavaScript.

Bạn có thể xem nội dung của tệp file-1.jsfile-2.js trong đoạn mã này. Hãy lưu ý sự sẵn có của biến globalMessage 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 đó không có phạm vi toàn cầu mà có phạm vi mô-đun. Các biến có phạm vi mô-đun có sẵn ở bất cứ đâu trong mô-đun hiện tại, nhưng không có trong các tệp hoặc mô-đun khác. Để cho phép các tệp khác truy cập vào một biến ở phạm vi mô-đun, 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 các biến trong hàm JavaScript có từ khoá var, let hoặc const, các biến này sẽ nằm trong hàm này nên bạn chỉ có thể truy cập vào chúng từ bên trong hàm. Các biến cục bộ được tạo khi hàm bắt đầu và sẽ bị xoá ngay 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ể sử dụng các 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 này. Tuy nhiên, với từ khoá const, biến này vẫn 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

Phạm vi chặn

Các quy tắc chặn được dùng để nhóm một câu lệnh hoặc một tập hợp câu lệnh với nhau. Bạn có thể dùng từ khoá const hoặc let để khai báo biến cục bộ trong 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 đó được đặt trong dấu ngoặc nhọn. Các biến trong một phạm vi khối không được sử dụng bên ngoài khối.

{
    const name = "Elizabeth";
}

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

Hãy lưu ý hai vòng lặp for trong đoạn mã này. Một vòng lặp for dùng từ khoá var để khai báo biến khởi tạo, biến này tăng lên qua các số 0, 12. Vòng lặp for còn lại sử dụng từ khoá let để khai báo biến trình 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ã trước đó, bạn có thể nhận thấy biến i trong vòng lặp for đầu tiên bị rò rỉ bên ngoài vòng lặp for và 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 một biến trong 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 một phạm vi khác.

Ví dụ nà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();

Các 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.

Phạm vi từ vựng và từ khoá kết thúc

Đóng đề cập đến một hàm đóng, trong đó hàm bên trong có thể truy cập vào phạm vi hàm bên ngoài, còn được gọi là môi trường từ vựng. Do đó, trong JavaScript, bạn sử dụng hàm đó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ởi một hàm, sau đó hàm này được gọi bởi một hàm khác.

Trong ví dụ này, mã tạo thành một đóng bằng môi trường từ vựng được tạo khi hàm outer() được gọi, đóng qua 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, xem phần Phạm vi và đóng phạm vi.

Mô-đun

Các mô-đun JavaScript giúp sắp xếp mã JavaScript. Khi được sử dụng đúng cách, các phần tử này sẽ tạo nên cấu trúc hiệu quả cho cơ sở mã của bạn và giúp tái sử dụng mã. Thay vì sử dụng các biến toàn cục để chia sẻ biến trên nhiều tệp, mô-đun JavaScript cung cấp kỹ thuật để xuấtimport các 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 nên nắm được. Để hiểu rõ hơn về hệ thống phạm vi, bạn có thể thử tự viết mã bằng Công cụ hiển thị phạm vi JS. Bản minh hoạ sử dụng màu sắc trong mã để giúp bạn trực quan hoá các 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, thật tuyệt khi bạn đã đọc qua nội dung này và dành thời gian để hiểu chủ đề này.

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