Article:
[JavaScript] Function scope and Closures
2897
huynhchau.myopenid.com 1Updated 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
.
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à theChild và child đề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 -
1

over 3 years ago
over 3 years ago
over 3 years ago
over 3 years ago
over 3 years ago
over 3 years ago
over 3 years ago
over 3 years ago
over 3 years ago