Vinova tuyển lập trình viên Mobile & Web ở Hà Nội, lương $300-1000

Article: [JavaScript] Function scope and Closures 2897

huynhchau.myopenid.com 1
Updated over 3 years ago

Phần này sẽ giải thích về những gì liên quan đến scope thực thi một function trong JavaScript, bao gồm closure.

http://www.odetocode.com/aimages/200707/071007_0345_Closures1.png

Trước hết cho cái hình cho nó dễ hình dung {#emotions_dlg.sealed}.

Như trong hình thì ngoặc vuông màu đỏ là scope, vòng tròn màu đỏ là call object và những mũi tên màu xanh là scope chain.

1. Scope

Trong JS, scope được tính bên trong hàm chứ không phải trong block (if, while, ...).

Khi một hàm được định nghĩa, scope chain hiện tại được lưu và trở thành một phần trạng thái nội tại của hàm. Tại mức độ cao nhất, scope chain bao gồm global object và lexical scope không phải là sự liên quan đặc biệt (particularly relevant ?!). Điều này có nghĩa là nội hàm có thể truy xuất tất cả những tham số và biến cục bộ của hàm cha.

2. The Active Object (hay call object)

Để nói về Active Object, ta sẽ bắt đầu từ Excution context.

Execution context là một khái niệm trừu tượng được đặc tả bởi ECMAScript. Không có một mô tả chi tiết nào về cách thức implemented của execution context nhưng execution context có những thuộc tính có thể tham chiếu đến những cấu trúc mà chúng có thể hiểu (và thậm chí là implement) ví dụ như đối tượng với những thuộc tính không phải public.

Tất cả javascript code được thực thi trong một execution context. Global code (code thực thi inline, JS file, hoặc HTML page, ...) được thực thi trong global execution context và mỗi lời gọi hàm sẽ có một execution context đi kèm với nó.

Khi một hàm javascript được gọi, nó được đưa vào một execution context, nếu một hàm khác được gọi (hoặc là gọi lại chính nó theo cách gọi đệ qui) một execution context mới được tạo ra và lời gọi hàm được đưa vào context đó cho đến khi nó trả về kết quả. Vì vậy, trong quá trình thực thi, javascript code tạo nên một stack những execution context.

Khi đã tạo execution context cho một lời gọi hàm, trình tự các bước tiếp theo sẽ diễn ra như sau:

  • Đầu tiên, trong execution context của một hàm, một Activation object được tạo ra. Activation object này là một cơ chế đặc tả khác, nó có thể được xem như là một đối tượng vì nó có các property có thể truy xuất được. Nhưng nó không phải là một object bình thường vì nó không có prototype và không thể được reference trực tiếp bằng javascript code.
  • Kế đến là việc tạo ra argument object, đây là một object giống như array với index là các số interger tương ứng với thứ tự của các parameter được truyền vào. Nó cũng có thuộc tính length và callee. Một property của Activation object được tạo ra với tên là "arguments" và trỏ đến object này.
  • Bước tiếp theo, execution context được gán vào một scope. Scope bao gồm một danh sách (hoặc một dây - chain) các objects. Mỗi function object có một property bên trong tên là [[scope]] chứa một danh sách các object. Scope này được gán cho execution context của một lời gọi hàm bao gồm một danh sách được tham khảo bởi [[scope]] property của function object đó với một chút thay đổi là Activation object sẽ được thêm vào vị trí đầu tiên của danh sách.
  • Quá trình khởi tạo biến sử dụng một đối tượng gọi là "Variable". Thật sự thì Activation object và Variable object là một. Những thuộc tính của Variable được tạo ra cho mỗi function là những parameter. Và nếu những argument trong lời gọi hàm tương ứng với những parameter này thì giá trị của nó được đưa vào những thuộc tính tương ứng, ngược lại thì thuộc tính đó mang giá trị undefined. Đối với nội hàm, những function object tương ứng với những nội hàm sẽ được tạo ra và các function object này được gán thành những property của Variable với tên tương ứng là tên của những nội hàm đó. Bước cuối cùng của quá trình khởi tạo biến là tạo các property trong Variable tương ứng với các local variable được khai báo bên trong function.
  • Những thuộc tính được tạo trong Variable object - tương ứng với những biến khai báo cục bộ trong thân hàm - đầu tiên nó sẽ được gán giá trị undefined, việc khởi tạo thực sự xảy ra trong quá trình evaluation của assignment expression tương ứng khi hàm được thực thi.
  • Thực tế thì Activation object, với thuộc tính arguments của nó, và Variable object, với những thuộc tính có tên tương ứng với biến cục bộ của hàm, là cùng một đối tượng, cho phép định danh arguments được đối xử như là biến cục bộ của hàm.
  • Cuối cùng, nếu một giá trị được gán-tham-chiếu đến một đối tượng mà sau đó các thao tác truy xuất property đều có prefixed là từ khóa this thì nó sẽ dùng giá trị của object đó. Nếu giá trị được gán là null (tức là không nằm trong một object nào đó) thì từ khóa this sẽ tham chiếu đến global object.

Global excution context có một vài khác biệt nhỏ vì nó không có arguments vì vậy nó không cần định nghĩa một Activation object tham chiếu đến nó. Global excution context cần một scope và scope chain của nó chỉ chứa một object, global object. Global object được dùng như là Variable object, đó là lý do tại sao những hàm (hoặc biến) khai báo toàn cục trở thành thuộc tính của global object.

3. Closures

Closures là cách thức một inner function có thể tham khảo đến các biến trong một function cha, ngay cả khi function cha đã bị terminated. Ví dụ:

function parent(arg) {
var parentVar = "I am a variable of parent";

function child() {
alert(arg + parentVar);
}

child();
}

parent("hello, "); // alert message "hello, I am a variable of parent"

Hàm child trong ví dụ trên gọi là nested function, được khai báo bên trong hàm cha. Ta thấy nó có thể truy xuất các biến định nghĩa bên ngoài function body của nó. Điều này bình thường. Chuyện gì xảy ra nếu ta thay đổi như sau:

function parent(arg) {
var parentVar = "I am a variable of parent";

function child() {
alert(arg + parentVar);
}

return child;
}

var theChild = parent("yes, ");
theChild(); // alert message "yes, I am a variable of parent"

Rõ ràng, sau khi parent được gọi và terminated, các biến của nó vẫn còn tồn tại và có thể được truy xuất bởi inner function. Đây gọi là closure.

Trong JavaScript, nếu bạn sử dụng từ khóa function bên trong một hàm khác, bạn đang tạo ra một closure.

Có 2 phát biểu ngắn gọn về closure như sau:

  • Closure là nơi giữ những biến cục bộ của một hàm, những biến này vẫn còn hiệu lực sau khi hàm đã được return hoặc,
  • Closure là một stack - frame không được giải phóng khi hàm đã terminated. (Trong C và trong hầu hết các ngôn ngữ lập trình khác, sau khi một hàm đã terminated, các biến cục bộ của nó cũng ko thể truy xuất được nữa bởi stack - frame đã bị huỷ.)

Trong đoạn code trên, một lập trình viên JavaScript sẽ hiểu biến theChild giữ một tham chiếu tới parent. Một lập trình viên C sẽ nghĩ là theChildchild đều là con trỏ trỏ đến một hàm. Sử dụng khái niệm con trỏ trong C, bạn có thể nghĩ rằng, trong JavaScript, một biến tham chiếu đến một hàm cũng giống như có một con trỏ trỏ đến một hàm và một con trỏ ẩn trỏ đến closure - là nơi lưu giữ biến cục bộ của hàm đó.

(Em đang trong giai đoạn tìm hiểu về JS nên post lên đây những gì dịch được, hy vọng nhận được sự chỉ bảo của mọi người)

[Tham khảo từ

1. Bài báo của anh Nguyễn Thế Vinh

2.Section 8.8, JavaScript - The Definitive Guide, 5th Edition

3. Chapter 3, Asp.net Ajax in Action

4. Bài báo của Morris Johns

5. Bài báo của Richard Cornford]

                                                                        - End of document -

Comments

alide.myopenid.com 32
over 3 years ago

Dịch quả là khó, bạn cố gắng nhé. Nên để ảnh minh hoạ vào phần giới thiệu ở đầu bài, như vậy nhìn từ trang chính sẽ thấy ngay tấm ảnh, cuốn hút sự chú ý của người đọc hơn. Những trang báo điện tử như vnexpress, bao giờ ở đầu bài viết cũng có ảnh minh hoạ{#emotions_dlg.wink}.

ngocdaothanh.myopenid.com 172
over 3 years ago

Bài này giải thích quan hệ giữa một closure nhất định và các biến nó truy xuất, rất quan trọng vì các framework như YUI dùng closure rất nhiều. Sẽ rõ ràng hơn nếu bạn huynhchau giải thích thêm về closure (có lẽ nên tìm thuật ngữ tiếng Việt nào đó gần nghĩa?). Xin gợi ý:

  • Trong C có thuật ngữ con trỏ hàm hoặc callback, trong Ruby và nhiều ngôn ngữ khác có thuật ngữ closure, lambda, hàm không tên. Chúng đều dùng để chỉ việc truyền mã từ nơi này đến nơi khác.
  • Trong JavaScript, closure cho phép đóng gói cả mã và dữ liệu, nên có thể coi nó là đối tượng (như theChild trong ví cụ). Trong JavaScript có nhiều cách khai báo lớp, closure là ngắn nhất nên được nhiều người ưa dùng, cách dài nhất là dùng this ở mọi chỗ nên rất dễ sai sót.
huynhchau.myopenid.com 1
over 3 years ago

Cảm ơn gợi ý của ngocdaothanh {#emotions_dlg.smile}.

Về việc tìm thuật ngữ tiếng Việt gần nghĩa với closure thì huynhchau sợ rằng người đọc lại phải mất công tra từ điển để tìm từ tiếng anh của nó cho dễ hiểu nữa nên quyết định để "quyên xi" vậy luôn :D.

alide.myopenid.com 32
over 3 years ago

Phần tham khảo có đề cập đến bài của anh Vinh và các bài khác có vẻ hấp dẫn, bạn huynhchau cho bọn tớ link được không?

huynhchau.myopenid.com 1
over 3 years ago

Ngoại trừ những cuốn sách ko có link để đưa {#emotions_dlg.cry} thì những gì có thể đưa được mình đã cập nhật hết rồi đó bạn {#emotions_dlg.sealed}.

ngocdaothanh.myopenid.com 172
over 3 years ago

Phần tham khảo nào nằm trong ebook, thì chắc đều có thể download từ flazx.com. Bạn huynhchau giới thiệu sơ qua về anh Nguyễn Thế Vinh và bài của anh được không nhỉ?

huynhchau.myopenid.com 1
over 3 years ago

Anh Vinh là đồng nghiệp và là đàn anh của mình. Một phần trong đoạn closure ở trên là tìm hiểu của anh í, mình đọc rồi bổ sung thêm. Ảnh chỉ mình bài báo của Richard Cornford và giúp mình nhiều trong việc dịch thuật {#emotions_dlg.smile}.

jishin.myopenid.com 18
over 3 years ago

Bài này giải thích rõ về khái niệm block/closure trong Ruby.

http://rubylearning.com/blog/2007/11/30/akitaonrails-on-anatomy-of-ruby-blocksclosures/

ngocdaothanh.myopenid.com 172
over 3 years ago

Đoạn giải thích

Sử dụng khái niệm con trỏ trong C, bạn có thể nghĩ rằng, trong JavaScript, một biến tham chiếu đến một hàm cũng giống như có một con trỏ trỏ đến một hàm và một con trỏ ẩn trỏ đến closure - là nơi lưu giữ biến cục bộ của hàm đó.

không thực dụng cho việc lập trình lắm. Khi viết theChild = parent("yes, "), thì theChild chứa giá trị trả về từ hàm parent, còn giá trị trả về này liên quan gì với parent lại là vấn đề khác, nên tách thành vấn đề khác, nếu không dễ tẩu hoả nhập ma. Ví dụ từ việc anh A nợ chị B, chị B nợ em C, em C nợ cháu D, nếu anh A kết luận là mình nợ cháu D thì anh A sẽ rắc rối cái đầu. Anh A cứ biết mình nợ chị B đã, còn chị B nợ ai thì kệ mẹ {#emotions_dlg.laughing} con nhà chị ta.

You must login to be able to comment

Uploaded files

No file uploaded yet

You must login to be able to upload

Nhà tài trợ:

Mọi người đều tự do viết bài, sửa bài của người khác, và bình luận ở trang web này. Bạn muốn chủ động tạo bài mới để chia sẻ kinh nghiệm với mọi người? Xin click link ở dưới.

Create new content