01/10/2018, 16:15

Vấn đề closure trong javascript

mọi người giải thích cho em về closure với. em đi pv toàn bị hỏi về closure trong javascript mà ko biết trả lời như nào. em có đọc tài liệu rồi mà không vào a.

Hung viết 18:21 ngày 01/10/2018

Định nghĩa closure thế này

Closure is when a function is able to remember and access its lexical scope even when that function is executing outside its lexical scope.


Đầu tiên bạn phải hiểu “lexical scope” là gì? Nếu không xét cú pháp ES6 thì chỉ có 1 cách tạo scope trong JavaScript là sử dụng function definition để tạo scope.

Ban đầu có 2 block như thế này:

{ // scope 1
  var a = 2;
  { // scope 2
    var b = 1;
    console.log(a + b);
  }
}

JavaScript không hỗ trợ lexical scope theo block scope, mà chỉ có function mới tạo được scope. Để tạo scope thì với mỗi block được bọc lại bằng 1 function definition. Cách này đi ngược lại tư duy thông thường là tạo function trước rồi viết block cho function sau. Bây giờ tạo block trước rồi viết function sau.

Với block { ... }, thêm đằng trước { bằng chuỗi function function_name() { ...

function outer() { // scope 1
  var a = 2;
  function inner() { // scope 2
    var b = 1;
    console.log(a + b);
  }
}

inner() tạo scope cho scope 2, outer() tạo scope cho scope 1

Giả sử Lúc JavaScript engine thực thi (chạy) câu lệnh console.log(a + b) thì trình tự như thế này, như đoạn hội thoại giữa 2 người: Engine (thực thi lệnh) và Compiler (lưu trữ tất cả các variable declaration)

Bước đầu: Compiler đọc từ nguyên tất cả các code 1 lần từ trên xuống dưới, Compiler lưu variable declaration cho từng biến như sau:

  • b thuộc scope 2, inner scope.
  • a thuộc scope 1, outer scope.
  • console thuộc global scope.

Bước thứ hai: Engine chạy lại code 1 lần nữa từ đầu đến cuối, nhưng hiện tại chỉ xét Engine đang thực hiện lệnh console.log(a + b). Lúc này bắt đầu cuộc hội thoại (conversation):

  • Engine: Chào compiler, cho tôi giá trị biến console!
  • Compiler: Ok, biến console trong scope 2? Không có. Để mình tìm tiếp scope 1 là outer scope của scope 2. Hmm, scope cũng không có console. Thôi ra global scope vậy. A ha, tìm thấy rồi, giá trị console đây Engine
  • Engine: Cám ơn Compiler, chạy tiếp .log, à, property accessor, mò object thôi, có log (mình skip phần này). Tìm tiếp. Biến a? Engine ơi? Biến a có giá trị gì vậy?
  • Compiler: Để tôi tìm, scope 2 không có, tìm scope ngoài vậy? scope 1 có định nghĩa biến a. Giá trị a đây Engine.
  • Engine: Mình lấy được rồi, kí tự tiếp theo b. b là gì vậy Compiler?
  • Compiler: b á. May quá, b ở ngay scope 2 luôn, giá trị của b nè.
  • Engine: Mình lấy được giá trị của b rồi. Thanks, thế là trong câu lệnh console.log. Chạy lệnh tiếp theo thôi.

Trở lại định nghĩa, trong định nghĩa có 2 ý, ý đầu tiên là:

Closure is when a function is able to remember and access its lexical scope

that function is executing outside its lexical scope.

Theo định nghĩa (1):

  • Closure là 1 function
  • Closure truy xuất “outside lexical scope”

Từ 2 yếu tố trên closure phải là có dạng như function inner(), định nghĩa trong function definition, hoặc là top-function có 1 outer scope duy nhất là global.

Nhưng theo định nghĩa (2), global luôn luôn thực thi (execution) nên trường hợp top-function bị loại.

Vậy closure phải là 1 inner function trong 1 function khác, như inner().


Để thoả mãn định nghĩa (2) thì phải có cách để khi Engine thực hiện function call inner() thì lexical scope lúc thực hiện function call không thấy variable declaration biến b của outer()

// before ...
console.log(b); // Reference Error
{
  console.log(b); // Reference Error
  inner(); // Engine is running the function call
  console.log(b); // Reference Error
}
// after ...
console.log(b); // Reference Error

Để giải quyết được thì xem lại về Types của JavaScript. function là subtype của object. Những gì làm được với object thì cũng làm được với function

function returnObject() {
  return { a: 2 };
}

function returnFunction() {
  return function() {};
}

function returnNamedFunction() {
  function name() {
    // ...
  }
  return name;
}

Trở lại ví dụ outer và inner, bây giờ outer() trả về function inner(), hay trả function name inner. Return type của outer() là function object, không phải là return type của inner()undefined

Lúc này inner() là closure theo định nghĩa

function outer() { // scope 1
  var a = 2;
  function inner() { // scope 2
    var b = 1;
    console.log(a + b);
  }

  return inner;
}

Sau đó Engine gọi function call tới outer(), nhận giá trị function object gán vào biến closure. Sau đó Engine gọi function call đến closure. Vì closure là function object, hay callble object nên có thể đặt () vào sau tên của closure thành closure() để tạo function call.

var closure = outer();
closure();

Engine chạy outer() gán inner vào closure. outer() đã chạy xong.

Lúc này closure ở global scope, chỉ thấy outer, closure, nhưng không thấy a ở scope 1, outer scope

Lúc gọi closure(). Do closure là reference (pointer) đến inner, nên thực chất lệnh closure() thành inner(). Engine thực hiện các lệnh trong inner() vẫn thấy aouter() (theo lexical scope), nhưng closure thì không thấy a(cũng theo lexical scope).

Closure is when a function is able to remember and access its lexical scope even when that function is executing outside its lexical scope.


Bây giờ tiếp tục Engine thực hiện closure() theo đoạn code sau:

'use strict';

function outer() { // scope 1
  var a = 2;
  function inner() { // scope 2
    var b = 1;
    console.log(a + b);
  }

  return inner;
}

var closure = outer();
closure(); // <-- Engine
a = 2;
b = 2;

Lúc này, Compiler có các variable declarations:

  • scope 2: b
  • scope 1: a
  • global: outer, closure

Engine bắt đầu cuộc hội thoại, vị trí Engine thực hiện ở global scope:

  • Engine: Thực thi closure() nào, biến closure đâu?
  • Compiler: Tôi tìm thấy closure ở global scope, giá trị là function object đây Engine.
  • Engine:
    • gán function object từ Compiler vào closure, có function call () kế bên. closure là function object, thực thi function call thôi.
    • Xong closure() rồi, chạy lệnh tiếp theo, a = 2. Compiler ơi? a là gì vậy?
  • Compiler: Hmm, a không có trong global scope, để tôi tạo biến a cho. Ôi thôi, ‘use strict’ mode, không tạo được rồi. Engine ơi, tôi không tìm được, tôi gửi giá trị undefined (Trường hợp không có strict mode xem tiếp phần bonus)
  • Engine:
    • a nhận undefined, báo lỗi cho user biến, Reference Error nè.
    • (Giả sử) chạy tiếp lệnh b = 2. Compiler ơi, b là gì vậy?
  • Compiler: b không thấy trong global scope. Tôi trả tiếp undefined vậy.
  • Engine: Lại là undefined!! báo lỗi cho user tiếp, Reference Error.

Bonus:
Giả sử Engine đang bắt đầu thực thi lệnh a = 2 sau lệnh closure(), nhưng lệnh use strict; bị xoá. Nghĩa là Engine đang thực thi lệnh ở chế độ non-strict mode.

  • Engine: thực thi a = 2. Biến a? Cho mình thông tin biến a đi Compiler
  • Compiler: Ok Engine. Oops, không thấy biến a ở global scope, để mình tạo biến global tên a vậy. … Engine ơi, mình tạo biến a rồi nè.
  • Engine:
    • Cám ơn Compiler, có biến a rồi, gán giá trị 2 vào a thôi. (a do Compiler tạo là global variable, không liên quan đến biến a trong outer scope).
    • Chạy tiếp lệnh b = 2. Compiler, biến b đâu rồi?
  • Compiler: Biến b? Không tìm thấy biến b ở global scope, thôi tạo tiếp vậy… Mình tạo biến b rồi nè, b ở global scope nhé Compiler
  • Engine: Mình nhận được biến b rồi, bán giá trị 2 vào b. (b do Compiler tạo cũng là global variable, không liên quan đến b của inner scope).

Rút gọn từ You don’t know JS: Scope and Closures
Nếu có thể bạn đọc nguyên cuốn sách để hiểu toàn diện.

Sáng Béo viết 18:26 ngày 01/10/2018

đoạn code cuối mình chạy thử vẫn ra đúng kết quả chứ có báo lỗi đâu nhỉ?

Hung viết 18:29 ngày 01/10/2018

Thank bạn, do mình để use strict sai chỗ, chính xác là ở đầu file hoặc script.
Trường hợp không có strict mode, thì sau 2 lệnh assignment, ab đều trở thành global variables.

Sáng Béo viết 18:20 ngày 01/10/2018

à, mình hiểu rồi, bạn đang để 2 biến a, b ở dưới kia để chỉ rằng nó không liên quan gì đến biến a, b trong closure đúng không.
vậy mà nãy giờ mình cứ nghĩ bạn để 2 biến đó để xem nó ảnh hưởng đến kết quả của closure() (yaoming)

Hung viết 18:17 ngày 01/10/2018

Yeah, nó không có liên quan. Mình cũng thêm phần xử lý khi không có “strict mode”, chương trình kết thúc không báo lỗi.

Thực ra định viết ngắn thôi, ôn lại những gì đã học. Ai ngờ nó dài thế này luôn

Wayne Myan viết 18:18 ngày 01/10/2018

đùa chứ bác đọc kiến thức bác chia sẻ lại càng hoang mang. em tưởng closure là func khai báo trong 1 scope nào đó có tác dụng để func đó sử dụng các biến của scope bao nó. mà h đọc của bác thấy nó rộng lớn quá

Sáng Béo viết 18:17 ngày 01/10/2018

thế thì bạn đang “tưởng” sai rồi, có ng chữa cho bạn rồi đó

Bài liên quan
0