Javascript có phạm vi không?

Đối với những bạn lập trình Web nói chung và lập trình Javascript nói riêng thì những kiến ​​thức về Scope, Closure là cần phải nắm rõ. Với mình thì Javascrip là một ngôn ngữ lập trình khó, nếu không nắm rõ cách tổ chức bộ nhớ thì sẽ gặp phải những Bug rất khó giải. Mời bạn đoc Bizfly Cloud tham khảo ngay thông tin về điều này qua bài viết dưới đây nhé

Nhiều người mới bắt đầu sử dụng Javascript sẽ thấy rất dễ dàng, càng đi sâu hơn sẽ thấy phức tạp dần với các vấn đề khó khăn trong Javascript như Phạm vi, Đóng cửa hay từ khóa này. Về bản chất, các bạn chỉ cần nắm rõ phạm vi và một số lưu ý là sẽ tránh được một số lỗi mà không biết vì sao lỗi

Sau đây mình xin đi vào cụ thể từng mục

1. Phạm vi trong javascript là gì?

Phạm vi là một Bộ nhớ khối để lưu trữ các biến cụ thể có thể sử dụng được. Nếu ai đã từng lập trình C thì sẽ biết rằng trong C, Scope được tạo ra khi sử dụng toán tử {}, được gọi là block scope. Mỗi khi trích dẫn được khai báo, trình biên dịch [trình biên dịch] sẽ tạo ra một phạm vi

Javascript cũng sử dụng toán tử {} nhưng lại không sử dụng phạm vi mỗi khi có toán tử đó khai báo giống như trong C

Ví dụ một đoạn mã cơ bản trong C như sau

for [i=0; i < 4; i ] { //Outer loop for [i=0; i < 2; i ] { //Inner loop document.write["Hello World"]; } }

Đoạn mã trên thông thường sẽ xuất hiện 8 lần từ "Xin chào thế giới" trong C hoặc 1 số ngôn ngữ lập trình khác, nhưng trong JS nếu bạn viết đoạn mã trên thì vòng lặp sẽ chạy mãi không dừng

You have know because sao không? . Do đó, vòng lặp Inner loop luôn luôn đặt lại biến tôi dẫn tới vòng lặp Outer loop có biến tôi không thể đạt tới giá trị = 4 để dừng

Những người phát triển Javascrip đã nhận ra sự thiếu sót đó dẫn đến nhiều lập trình khá lúng túng khi tiếp cận nên trong các phiên bản JS sau này, họ đã cung cấp thêm từ khóa let để tạo phạm vi khối

for [let i=0; i < 4; i ] { //Outer loop for [let i=0; i < 2; i ] { //Inner loop document.write["Hello World"]; } }

Javascript không phải là không sử dụng Phạm vi mà chúng ta chỉ tạo ra Phạm vi khi nó là một hàm, hay còn gọi là phạm vi chức năng

2. Phạm vi chức năng

Phạm vi chức năng là Phạm vi được tạo ra chỉ cho chức năng đó sử dụng. Nó chính là mọi thứ trong dấu {} của hàm

var foo = "Goodbye"; var message = function[] { var foo = "Hello"; document.write[foo]; } message[]; //Hello document.write[foo]; //Goodbye

Đoạn mã trên có 1 phạm vi chức năng được sử dụng cho thông báo hàm. Biến foo trong hàm messagevà biến foo ngoài hàm hoàn toàn khác nhau

Nếu bạn sử dụng một biến trong phạm vi chức năng ở bên ngoài chức năng đó thì sẽ báo lỗi ngay

var message = function[] { var foo = "Hello"; document.write[foo]; } message[]; //Hello console.log[foo]; //error here

Nhưng nếu bạn khai báo biến trong Scope mà không có từ var thì js sẽ hiểu biến đó chính là global nên bạn vẫn có thể truy cập được bên ngoài phạm vi

var message = function[] { foo = "Hello"; document.write[foo]; } message[]; //Hello document.write[foo]; //Hello

Nếu bạn đã từng đọc best practice trong jQuery thì các chuyên gia khuyên bạn nên khai báo jQuery như sau

[function[$] { //Do things here - they are scoped }[JQuery]] hoặc: [function[$] { //Do things here - they are scoped }][JQuery]

Bạn đã từng thắc mắc tại sao họ lại khuyến khích code như thế nào bao giờ chưa?

Hai đoạn mã trên giống nhau và nó được gọi là Biểu thức hàm được gọi ngay lập tức [IIFE] tức là nó được gọi là thực thi ngay sau khi hàm được khai báo. Bản chất của 2 đoạn mã trên là

var rootFunction = function[$] {// $ là tham số truyền vào function //Do things here - they are scoped } rootFunction[JQuery] // lời gọi function ở đây, JQuery là đối số tryền vào

Quay lại 2 đoạn mã IIFE phía trên, phía cuối cùng họ sử dụng dấu [] để gọi thực thi hàm, với đối số truyền vào là JQuery. Trong JS, dấu [] để gọi thực thi hàm

Có dấu [] chỗ cho hàm khai báo ngày trước sẽ được thực thi ngay. Trong JQuery, ký hiệu $ là kiểu viết rút gọn của hàm JQuery. Nhưng $ cũng là cách viết rút gọn của nhiều thư viện JS khác [vd ProtoTypeJS]. Để tránh nhầm lẫn giữa các biến $ của Jquery khai báo toàn cầu và tránh xung đột giữa các biến $ của thư viện JS khác nếu bạn sử dụng nhiều thư viện JS cùng một lúc thì bạn nên đặt mọi thứ vào phạm vi. Đoạn mã trên đã làm thế cho chúng ta

- Gọi hàm [Gọi thực thi hàm]

- Gọi hàm xảy ra khi gọi bất kỳ hàm nào bằng cách sử dụng dấu []. Cách gọi thực thi hàm như thế được gọi là Mẫu lệnh gọi hàm

Ví dụ

var add = function[num1, num2] { console.log[num1 num2]; } add[2, 3]; // 5

Xem lại ví dụ sau

________số 8

Nhớ là gọi người gọi 1 đến người gọi 5 từng như một chứ không phải gọi cùng lúc như trên nhé

Theo bạn tương ứng với mỗi câu lệnh người gọi 1 -> người gọi 5 thì kết quả trong bảng điều khiển ra là gì?. Nếu bạn thử kiểm tra từng câu lệnh trên trình duyệt thì hãy gọi từng câu lệnh một chứ đừng gọi cả 5 câu lệnh giống như trên vì js là lý do bất đồng bộ [đồng bộ] nên sẽ không xử lý theo đúng thứ tự cho

• caller 1 sẽ không biết ra cho bạn cái gì cả, vì hàm createCallBack đã được gọi là thực thi đâu.

• người gọi 2 sẽ ra cho bạn "Đây là chức năng đầu tiên"

• người gọi 3 sẽ ra cho bạn "Đây là chức năng đầu tiên". Nhiều người sẽ thắc mắc vì sao người gọi 1 và người gọi 3 giống nhau mà kết quả lại khác nhau?

Trình gọi 3 là hàm createCallBack được truyền vào dạng gọi lại dưới dạng của sự kiện [sự kiện trên trình duyệt] nên khi sự kiện đó [mở vd trên cửa sổ. onload] được kích hoạt thì hàm gọi lại truyền vào sẽ tự động được thực thi

Mọi hàm gọi lại được truyền vào sự kiện thì sẽ được tự động thực thi ngay khi sự kiện đó kích hoạt

• caller 4 will in ra 2 dòng "Here is in first function" và "Here is in second function". Như bạn thấy caller 4 chỉ là thêm dấu [] vào sau caller 3, nghĩa là nó thực thi caller 3 xong rồi thực thi tiếp hàm mà caller 3 trả về [vd trên là hàm thứ hai]

• caller 5 giải thích tương tự caller 4, kết quả trong ra là "Đây là chức năng thứ nhất", "Đây là chức năng thứ hai" và "Đây là chức năng thứ ba"

Thế còn thế này thì sao?

var createCallBack = function[] { //First function console.log["first function"]; return function[] { //Second function console.log["second function"]; return function[] { //Third function console.log["third function"]; } } } window { createCallBack; }; // caller 6 window { createCallBack[]; }; // caller 7

Move back is all function Gọi lại được truyền vào sự kiện thì sẽ tự động được thực thi ngay khi sự kiện đó kích hoạt

Nghĩa là 2 chức năng người gọi 6 và người gọi 7 trên đều được thực thi ngay khi cửa sổ tải, thực thi ngay ở đây có nghĩa là thực thi nội dung trong dấu {} của 2 chức năng truyền vào cửa sổ sự kiện. đang tải

• người gọi 6 sẽ không ra cái gì cả vì hàm createCallBack không được gọi thực thi

• người gọi 7 sẽ ra "hàm đầu tiên" vì hàm createCallBack được gọi thực thi bằng cách thêm dấu []

3. Closures trong javascript là gì?

Hàm đóng là hàm có tham chiếu tới biến nằm trong phạm vi ngoài hàm đó

Ví dụ

for [let i=0; i < 4; i ] { //Outer loop for [let i=0; i < 2; i ] { //Inner loop document.write["Hello World"]; } }0

Khi bạn khai báo một hàm trong hàm mà hàm đó có biến tham chiếu tới phạm vi cha, ông thì hàm đó được gọi là bao đóng. Ví dụ trên có hàm thứ hai có biến đầu tiênVar là biến nằm trong phạm vi của hàm cha là hàm đầu tiên nên hàm thứ hai là bao đóng. Tương tự chức năng thứ ba cũng là một bao đóng

Khi một bao đóng được tạo, nó sẽ có 2 thành phần là nội dung hàm [thân hàm] và bối cảnh [bối cảnh], bối cảnh chính là nơi mà bao đóng được tạo ra

If closure tham chiếu tới biến ở hàm cha thì context chính là phạm vi của hàm cha, if closure tham chiếu tới hàm ông thì context chính là phạm vi của hàm ông

Việc sử dụng biến đóng cửa là con trỏ tới biến thuộc phạm vi cha chứ không phải sao chép biến của phạm vi cha vào phạm vi của mình.  

Ví dụ

for [let i=0; i < 4; i ] { //Outer loop for [let i=0; i < 2; i ] { //Inner loop document.write["Hello World"]; } }1

Đoạn mã trên sẽ ghi 43 chứ không phải 42 chương trình đóng sử dụng biến số chính là biến số phạm vi hàm say[] thứ nhất. Khi biến số ngữ cảnh này thay đổi, kết quả trong màn hình cũng thay đổi theo

Từ khóa "this" với Gọi hàm

Selected ví dụ

for [let i=0; i < 4; i ] { //Outer loop for [let i=0; i < 2; i ] { //Inner loop document.write["Hello World"]; } }2

Bạn nghĩ đoạn mã trên sẽ cảnh báo kết quả nào?

Gọi hàm kiểu gọi hàm [ call trực tiếp bằng cách thêm dấu [ ] ] thì từ khóa this trong hàm luôn luôn là đối tượng toàn cục [cửa sổ]

Vì vậy đoạn mã trên sẽ cảnh báo ra 500 chứ không phải 1 như nhiều người nhầm lẫn

Để khắc phục lỗi trên, có một cách đơn giản là sao chép biến this kia khi khai báo hàm và sử dụng biến that này

for [let i=0; i < 4; i ] { //Outer loop for [let i=0; i < 2; i ] { //Inner loop document.write["Hello World"]; } }3

4. Mẫu lời gọi hàm tạo

Mẫu lệnh gọi hàm tạo là cách gọi hàm bằng cách thêm từ khóa mới phía trước

Ví dụ

for [let i=0; i < 4; i ] { //Outer loop for [let i=0; i < 2; i ] { //Inner loop document.write["Hello World"]; } }4

Ví dụ trên giống với ví dụ ở phía trên với lời gọi người gọi 3. Nhưng lời gọi này chỉ ở phía trên trong "chức năng đầu tiên" còn ở ví dụ này là "chức năng đầu tiên" và "chức năng thứ hai". Vì sao?

Câu trả lời nằm ở từ khóa new set before second function

Từ khóa mới chứng tỏ hàm thứ hai đã được gọi là thực thi ngay [Constructor Invocation Pattern]

Ví dụ trên có thể được viết lại như sau

for [let i=0; i < 4; i ] { //Outer loop for [let i=0; i < 2; i ] { //Inner loop document.write["Hello World"]; } }5

Hàm được gọi bằng kỹ thuật Constructor Invocation Pattern [sử dụng từ khóa new] sẽ trả về

  • Nếu hàm trả về các kiểu đơn như số, chuỗi, boolean, null hoặc không xác định thì giá trị trả về sẽ bị loại bỏ và trả về điều này [là đối tượng được tạo ra từ từ khóa mới]
  • Nếu hàm có return là một đối tượng [là mọi thứ trừ các kiểu đơn], thì đối tượng này sẽ được trả về thay vì this

Với ví dụ trên thì hàm thứ hai sẽ trả về hàm thứ ba

Một số ví dụ kiểm tra

Ví dụ 1

for [let i=0; i < 4; i ] { //Outer loop for [let i=0; i < 2; i ] { //Inner loop document.write["Hello World"]; } }6

Ví dụ 2

for [let i=0; i < 4; i ] { //Outer loop for [let i=0; i < 2; i ] { //Inner loop document.write["Hello World"]; } }7

Ví dụ 3

for [let i=0; i < 4; i ] { //Outer loop for [let i=0; i < 2; i ] { //Inner loop document.write["Hello World"]; } }8

Ví dụ 4

for [let i=0; i < 4; i ] { //Outer loop for [let i=0; i < 2; i ] { //Inner loop document.write["Hello World"]; } }9

Ví dụ 5

var foo = "Goodbye"; var message = function[] { var foo = "Hello"; document.write[foo]; } message[]; //Hello document.write[foo]; //Goodbye0

Đoạn mã trên sẽ ra "mục2 không xác định" 3 lần vì cả 3 lần đóng đều sử dụng chung một tham chiếu tới mục và biến i [Lúc đó mục này là mục 2 và tôi đã có giá trị là 3]

Để đoạn mã trên chạy theo ý muốn của bạn, chỉ cần đơn giản sửa đóng sao cho mỗi lần đóng sử dụng một phạm vi hoặc ngữ cảnh riêng. Một trong vài cách đó là sử dụng từ khóa let help variable was closed in scope of the sign {} which don't required in function

var foo = "Goodbye"; var message = function[] { var foo = "Hello"; document.write[foo]; } message[]; //Hello document.write[foo]; //Goodbye1

Ví dụ 6.  

var foo = "Goodbye"; var message = function[] { var foo = "Hello"; document.write[foo]; } message[]; //Hello document.write[foo]; //Goodbye2

sayAlice[][];// logs "Xin chào Alice"Mọi biến trong js khi khai báo sẽ được đưa lên phạm vi ban đầu [giá trị ban đầu là không xác định ] và được gán giá trị tại lệnh gán của biến đó [khai thác biến]. Ví dụ

var foo = "Goodbye"; var message = function[] { var foo = "Hello"; document.write[foo]; } message[]; //Hello document.write[foo]; //Goodbye3

Ví dụ 7

var foo = "Goodbye"; var message = function[] { var foo = "Hello"; document.write[foo]; } message[]; //Hello document.write[foo]; //Goodbye4

Nguồn. Bizfly Cloud chia sẻ

BizFly Cloud là nhà cung cấp dịch vụ điện toán đám mây đám mây với chi phí thấp, được vận hành bởi VCCorp.

BizFly Cloud là một trong 4 doanh nghiệp nòng cốt trong "Chiến dịch kết thúc chuyển đổi số bằng công nghệ điện toán đám mây Việt Nam" của Bộ TT&TT;

Chủ Đề