JavaScript series
Chương hôm nay giới thiệu về function trong JavaScript, thứ tưởng đơn giản nhưng không đơn giản tí nào.
Nội dung chính
- JavaScript series
- A. Function overview
- 1. Overview
- 2. Function & events
- 3. This keyword
- B. Definition & invocation
- 1. Function definition [advanced]
- 2. Parameters & arguments
- 3. Function invocation
- C. Function features
- 1. Anonymous function
- 2. Function object
- 3. Arrow function
- D. Call, apply & closure
- 1. Call method
- 2. Apply method
- 3. Call, apply in strict mode
- 1. Overview
- 2. Function & events
- 3. This keyword
Nội dung chính
- JavaScript series
- A. Function overview
- 1. Overview
- 2. Function & events
- 3. This keyword
- B. Definition & invocation
- 1. Function definition [advanced]
- 2. Parameters & arguments
- 3. Function invocation
- C. Function features
- 1. Anonymous function
- 2. Function object
- 3. Arrow function
- D. Call, apply & closure
- 1. Call method
- 2. Apply method
- 3. Call, apply in strict mode
- 1. Overview
- 2. Function & events
- 3. This keyword
B. Definition & invocation
1. Function definition [advanced]
A. Function overview
1. Overview
2. Function & events
3. This keyword
- B. Definition & invocation
- 1. Function definition [advanced]
2. Parameters & arguments
3. Function invocation
C. Function features
function func_name[param1, param2,...] {
...
}
1. Anonymous function
2. Function object
3. Arrow function
D. Call, apply & closure
function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
1. Call method
2. Apply method
sum[]; // Ok
sum; // Không có lỗi, nhưng hàm sum không được chạy
3. Call, apply in strict mode
2. Cấu trúc một closure
3. Rút gọn closure
function ABC[] {
return 10; // ABC[] = 10
return; // ABC[] = undefined
}
Bài viết này là một phần của series JavaScript dành cho người không mới, giúp các bạn đã có kinh nghiệm code trong các ngôn ngữ khác nhanh chóng làm quen với JS.
Nếu được rất mong nhận được sự ủng hộ và đóng góp ý kiến của mọi người để hoàn thiện series.
let c = sum[10, 5]; // Sử dụng trước
function sum[a, b] { return a + b; } // Khai báo sau
2. Function & events
3. This keyword
B. Definition & invocation
1. Function definition [advanced]
Click me
2. Parameters & arguments
function hello[] {
alert["Hello"];
}
...
Click me
3. Function invocation
C. Function features
1. Anonymous function
Click me
2. Function object
3. Arrow function
function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
0D. Call, apply & closure
function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
1function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
21. Call method
3. This keyword
B. Definition & invocation
- 1. Function definition [advanced]
- Trong function: this là chủ sở hữu [owner] của function. Nếu function là method, nghĩa là thuộc object nào đó, thì this là object. Nếu function đứng riêng lẻ, thì this là object window [function riêng lẻ thì luôn thuộc object window].
- Trong function, khi gọi bằng call, apply: This là object truyền vào function dạng tham số ẩn [sẽ được trình bày trong phần sau].
- Trong strict mode: this trong function bị cấm [có giá trị undefined].
B. Definition & invocation
1. Function definition [advanced]
Function expression
Ngoài cách khai báo [declare] hoặc định nghĩa [define] function bình thường như sau.
function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
3Còn có cách viết khác là dùng function dạng biểu thức [expression]. Biểu thức function có thể gán vào một biến, và biến này sẽ có kiểu function.
function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
4Chú ý dòng 2, biểu thức gán cho biến
let c = sum[10, 5]; // Sử dụng trước
function sum[a, b] { return a + b; } // Khai báo sau
8 có dạng một anonymous function [hảm ẩn danh] là một function không có tên. Và sử dụng function trên tương tự như function bình thường.function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
5Function expression không được hoisting, vì bản thân nó là một value [vế phải dấu bằng], nên không được hosting. Thực ra biến vẫn được hoisting, nhưng chỉ là không dùng được như function [value là undefined], nên coi như nó không được hoisting.
Function constructor
Cách khác nữa để tạo hàm là dùng Function constructor.
function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
6Function constructor nhận vào nhiều tham số, trong đó tham số cuối là code body của function.
Không nên dùng function constructor, vì code rối rắm và không an toàn.
2. Parameters & arguments
Parameters & arguments
Tham số [parameter - param] là những biến trong cặp [] của function, đại diện cho những đối số được truyền vào trong hàm. Các param được coi như các biến cục bộ trong hàm, và bị hủy khi hàm thực hiện xong.
Đối số [argument] là những biến, giá trị thực sự được truyền vào hàm. Các đối số được chuyển vào bên trong hàm, biến thành các tham số theo đúng thứ tự.
function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
7Người ta nhắc tới tham số khi ở bên trong định nghĩa hàm, và gọi là đối số khi gọi hàm. Và param phải là biến [biến object, biến function cũng là biến], trong khi đối số có thể là bất cứ thứ gì có giá trị, như số, biến, hằng, biểu thức, hàm,...
Parameter rules
JS không định kiểu cho tham số, và cũng không kiểm tra số lượng đối số truyền vào. Do đó, số argument truyền cho hàm có thể nhiều hơn, hoặc ít hơn số lượng param cần có. Những param không nhận được argument [do truyền bị thiếu] thì sẽ có value là undefined.
function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
8ES6 [ECMAScript 2015] cho phép function có default value cho param. Khi truyền không đủ đối số, thì những param bị thiếu thay vì có value là undefined, thì nó sẽ sử dụng default value.
function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
9Trong đoạn code trên, số 10 truyền cho
let c = sum[10, 5]; // Sử dụng trước
function sum[a, b] { return a + b; } // Khai báo sau
9, Click me
0 không nhận được đối số nào, nhưng vì Click me
0 có default value nên giá trị lúc này của nó chính là default value 100. Còn Click me
2 thì không có đối số, cũng không có default value nên nó mang giá trị undefined.Argument passing
Đây là khái niệm cực quan trọng khi học về hàm, trong mọi ngôn ngữ lập trình.
Trong JS, nếu đối số là kiểu primitive thì được truyền pass by value [theo giá trị]. Một bản sao của argument được tạo ra và đưa vào param, mọi thao tác trong hàm đều thực hiện trên bản sao nên dữ liệu gốc không bị ảnh hưởng [khi hàm thực hiện xong].
Đối với đối số object, thì truyền kiểu pass by reference [tham chiếu]. Thực ra vẫn là pass by value, nhưng value ở đây là tham chiếu tới địa chỉ bộ nhớ, nên các thay đổi trên tham chiếu thì cũng ảnh hưởng tới dữ liệu gốc.
3. Function invocation
Invoking a function
Để gọi [call - invoke] một function, gọi tên nó và truyền cho nó danh sách các đối số. Số lượng đối số không cần thiết phải tương ứng với tham số.
sum[]; // Ok
sum; // Không có lỗi, nhưng hàm sum không được chạy
0Nếu function thuộc về một object, thì function gọi là method [phương thức]. Gọi method tương tự gọi hàm, nhưng phải có tên object và dấu chấm phía trước.
sum[]; // Ok
sum; // Không có lỗi, nhưng hàm sum không được chạy
1Thực ra mọi function trong JS đều là method, các function không thuộc object nào thực ra thuộc về object window. Do đó, ví dụ đầu tiên có thể viết lại như sau, cũng cho kết quả tương tự.
sum[]; // Ok
sum; // Không có lỗi, nhưng hàm sum không được chạy
2 Khi function thuộc object, chúng ta gọi object là owner [chủ sở hữu] của function đó.
Call function as a constructor
Có thể gọi hàm với từ khóa new, lúc này function được coi như là một constructor. Constructor thường dùng để khởi tạo một object mới.
sum[]; // Ok
sum; // Không có lỗi, nhưng hàm sum không được chạy
3This trong trường hợp này không phải đối tượng window, mà là object mới được tạo ra nhờ từ khóa new. Object này sẽ được gán tham chiếu tới biến
Click me
3, do đó Click me
3 là một object. Tuy nhiên, this lúc này không có giá trị [rỗng], và giá trị được thêm vào this sẽ dùng để tạo object mới.Vấn đề này sẽ được bàn kĩ hơn trong chương object.
Self invoking function [IIFE]
Một hàm có thể được tự động gọi mà không cần lời gọi hàm, chúng có tên là self invoking function, hoặc IIFE [Immediately invoke function expression].
sum[]; // Ok
sum; // Không có lỗi, nhưng hàm sum không được chạy
4Chú ý hai dòng trên, chúng ta có 2 cách để làm một function thành self invoking:
- Cách 1 đối với function expression: chỉ cần thêm một cặp ngoặc [] phía cuối của nó là được.
- Cách 2 đối với function bình thường: vì đây chỉ là định nghĩa, nên cần bọc lại toàn bộ bằng [], sau đó mới thêm cặp [] ở cuối.
Self invoking function tự động chạy khi được định nghĩa.
C. Function features
1. Anonymous function
Anonymous function [hàm ẩn danh] là một hàm không có tên, đơn giản vậy thôi.
sum[]; // Ok
sum; // Không có lỗi, nhưng hàm sum không được chạy
5Hàm ẩn danh thường được dùng trong function expression, để viết nhanh một hàm chỉ dùng một lần. Anonymous function cũng được sử dụng làm callback, truyền dưới dạng tham số cho một hàm khác và được chính hàm đó gọi lại [call back] sau một khoảng thời gian làm gì đó
2. Function object
Function cũng là một object trong JS, cũng có những thuộc tính [property] và phương thức [method]. Chúng ta chỉ quan tâm tới hai đối tượng cơ bản nhất.
Method toString[]
Dùng chuyển một function thành string, nghĩa là show toàn bộ code của function đó.
sum[]; // Ok
sum; // Không có lỗi, nhưng hàm sum không được chạy
6Arguments object
Bên trong mỗi function có một object ẩn là
Click me
5. Nó giống như một mảng các đối số được truyền vào, có thuộc tính Click me
6 để lấy độ dài. Chú ý arguments khác với các giá trị mà param nhận được, nó là các giá trị thực sự được truyền vào.3. Arrow function
Arrow function [hàm mũi tên] là một cách viết khác ngắn gọn hơn cho function, được giới thiệu từ phiên bản ES6. Ví dụ bên dưới, 3 hàm tương tự nhau nhưng có cách viết khác nhau.
sum[]; // Ok
sum; // Không có lỗi, nhưng hàm sum không được chạy
7Cách 2 là syntax chuẩn của arrow function, dạng
Click me
7, với dấu Click me
8 là dấu mũi tên [arrow].
Rút gọn return
Cách 3 rút gọn lệnh return. Nếu phần thân function chỉ có một lệnh return, thì có thể rút gọn theo cách bỏ ngoặc {}.
Rút gọn một lệnh
Nếu thân function chỉ có một lệnh [không phải return], thì có thể bỏ ngoặc {}.
sum[]; // Ok
sum; // Không có lỗi, nhưng hàm sum không được chạy
8Chú ý, nếu chỉ có một lệnh nhưng là return, thì phải bỏ return như cách 3 ở trên. Nếu để return luôn sẽ có lỗi.
sum[]; // Ok
sum; // Không có lỗi, nhưng hàm sum không được chạy
9Rút gọn tham số
Nếu function có 1 tham số, thì ngoặc [] có thể bỏ đi.
function ABC[] {
return 10; // ABC[] = 10
return; // ABC[] = undefined
}
0Nếu không có tham số nào, thì phải giữ lại cặp [], không được bỏ đi.
function ABC[] {
return 10; // ABC[] = 10
return; // ABC[] = undefined
}
1D. Call, apply & closure
1. Call method
Mỗi function đều có một method với tên
Click me
9, dùng để truyền một object vào function đó. Khi object vào function được gọi với Click me
9, thì object sẽ biến thành đối tượng this. Function sử dụng this, thực chất là sử dụng object được truyền vào.Ví dụ như đoạn code sau.
function ABC[] {
return 10; // ABC[] = 10
return; // ABC[] = undefined
}
2Code trên định nghĩa hai object:
1 chuyên dùng xử lý phân số, chứa methodfunction hello[] { alert["Hello"]; }
2 và có thể thêm các method khác. Object này chỉ xử lý phân số, không chứa bất kì dữ liệu nào.function hello[] { alert["Hello"]; }
3 một phân số vớifunction hello[] { alert["Hello"]; }
4 vàfunction hello[] { alert["Hello"]; }
5. Object này được truyền cho methodfunction hello[] { alert["Hello"]; }
6 bằng methodfunction hello[] { alert["Hello"]; }
9. Lúc này,Click me
3 đi vào bên trongfunction hello[] { alert["Hello"]; }
2, biến thànhfunction hello[] { alert["Hello"]; }
0 và được in ra ngoài console.... Click me
Bạn sẽ đặt câu hỏi "tại sao không truyền thẳng object vào method như một param?". Thực ra câu hỏi đó hoàn toàn có lý, và như vậy code chúng ta sẽ trông như sau.
function ABC[] {
return 10; // ABC[] = 10
return; // ABC[] = undefined
}
3Hai đoạn code trên tương tự nhau, cho ra kết quả giống nhau.
Why call?
Sự khác biệt nằm ở chỗ context của function thay đổi. Khi gọi function với
Click me
9 và một object, thì giống như việc đưa function ấy vào trong object kia. Đối với function là method, thì owner của method bị thay đổi thành object được gọi bởi Click me
9. Ví dụ như code trên thực ra như sau.function ABC[] {
return 10; // ABC[] = 10
return; // ABC[] = undefined
}
4 Nói chung phần này hơi trừu tượng, và thực tế không dùng nhiều như cách truyền object như param.
Call and params
Function được gọi với
Click me
9 có thể có thêm các param khác.function ABC[] {
return 10; // ABC[] = 10
return; // ABC[] = undefined
}
5Lúc này khi gọi
Click me
9 thì truyền đúng theo param, nhưng đối số đầu tiên phải là object truyền vào. Các đối số thứ 2 sẽ thành param 1, đối 3 thành tham 2,... cứ như vậy. Nghĩa là đối số đầu tiên bị mất và trở thành
...
Click me
0 trong function.Như trên code trên, thì khi gọi
Click me
9 đối số function hello[] {
alert["Hello"];
}
3 thành
...
Click me
0 trong function
...
Click me
9, đối số chuỗi thứ 2 "Phan so test" được truyền vào tham số Click me
0 thứ nhất.2. Apply method
Method
Click me
1 tương tự như Click me
9, nhưng sự khác biệt ở chỗ các tham số bổ sung được truyền dưới dạng mảng.function ABC[] {
return 10; // ABC[] = 10
return; // ABC[] = undefined
}
6Function được
Click me
1 vẫn giữ nguyên các tham số riêng lẻ, nhưng khi gọi với Click me
1 như trên thì truyền vào là mảng. Từng phần tử của mảng này sẽ pass vào từng tham số tương ứng cho phù hợp.ES6 cung cấp một cách khác là spread operator khá giống
Click me
1, nhưng chưa phổ biến lắm.Sử dụng
Click me
1 rất tiện khi các đối truyền vào có dạng mảng, sẽ dễ hơn trong các thao tác.3. Call, apply in strict mode
Bình thường, nếu gọi
Click me
9 hoặc Click me
1 với đối số đầu tiên không phải object, hoặc không truyền đối objec, thì khi vào function được gọi
...
Click me
0 sẽ là đối tượng window.Nhưng trong strict mode, điều này bị cấm. Khi dùng
Click me
9 hoặc Click me
1, đối số đầu tiên bắt buộc phải là object, và không được bỏ qua.E. Closure
1. Overview
Closure là một trong những khái niệm trừu tượng nhất trong JS, và rất khó để hiểu được và định nghĩa chính xác nó là gì.
A closure is the combination of a function bundled together [enclosed] with references to its surrounding state [the lexical environment]. In other words, a closure gives you access to an outer function’s scope from an inner function.
Theo định nghĩa của MDN về closure, thì có thể hiểu như sau [tạm dịch].
Closure là kết hợp của một function được gói lại và tham chiếu tới môi trường xung quanh [nơi chứa function đó, nơi nó được tạo ra].
Có vẻ khái niệm closure vẫn còn khá trừu tượng, nhưng đừng lo. Hãy đi vào các đoạn code ví dụ để hiểu hơn.
function ABC[] {
return 10; // ABC[] = 10
return; // ABC[] = undefined
}
7Bên trên là cấu trúc cơ bản của một closure. Theo định nghĩa phía trên, chúng ta áp dụng vào code để hiểu rõ hơn.
Ở đây function
function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
2 được gói lại bên trong function function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3. Function function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
2 này, tương tự function khác, có thể truy cập vào các function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
5 [biến cục bộ của chính nó], hoặc function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
6 [biến toàn cục của chương trình]. Điểm đặc biệt là function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
2 cũng có thể truy cập vào function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
8, vì nó giữ tham chiếu tới function function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
9, là nơi nó được tạo ra.Closure & scopes
Khi nhắc tới closure phải nhắc tới scope. Như phân tích ở trên, function bình thường chỉ có hai scope, nhưng closure tạo ra tới 3 scope. Chính scope mới này tạo nên các tính chất đặc biệt của closure.
Đặc điểm closure
Một closure có hai đặc điểm sau:
- Lưu trữ value của biến qua nhiều lần chạy: đặc điểm này giống với global var. Nghĩa là khi chạy function nhiều lần, thì giá trị lần trước của function vẫn giữ như vậy, không bị mất đi.
- Chỉ cho phép truy cập nội bộ: đặc điểm này giống local var. Đối với closure, chỉ cho phép những thành viên nội bộ của nó mới có thể truy cập vào được.
2. Cấu trúc một closure
Closure có cấu trúc gồm 2 phần:
- Phần định nghĩa closure: là một function return một function con bên trong nó.
- Phần sử dụng closure: Function trên được tự động chạy 1 lần đầu tiên để khởi tạo giá trị [bằng self invoking hoặc function expression].
Ví dụ như sau là một closure đầy đủ nhất.
function ABC[] {
return 10; // ABC[] = 10
return; // ABC[] = undefined
}
8Câu đố dành cho bạn đây. Hàm
function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3 phía trên được gọi mấy lần?Nếu câu trả lời là 3, hoặc 4 lần thì bạn nhầm rồi. Hàm
function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3 chỉ được gọi duy nhất một lần ở câu lệnh let, trong function expression có dấu [] nghĩa là chạy function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3 một lần.Bạn sẽ bảo "chạy
function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
03 cũng giống như chạy function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3 vì được gán vào rồi". Vâng, nhưng thứ gán vào biến function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
05 ở đây không phải bản thân function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3, mà là cái function "bé bé xinh xinh" được function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3 return ra đấy. Chính là thằng ml function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
2 đấy.Để tớ phân tích kĩ hơn câu lệnh
function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
09. Đầu tiên, function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3 sẽ chạy trước vì có self invoking []. Kết quả function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3 trả về tham chiếu của thằng function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
2. Biến function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
05 phải nhận tham chiếu này, và kể từ đó function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
03 là function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
2.Code trên nên viết lại với những comment như sau cho dễ hiểu.
function ABC[] {
return 10; // ABC[] = 10
return; // ABC[] = undefined
}
9Yeah, đó chính là cách hoạt động của closure.
Question 1
Câu hỏi đầu tiên được đặt ra ở đây là "code trên liên quan vẹo gì tới closure?".
Hãy nhớ lại đặc điểm scope của một closure:
- Lưu trữ giá trị biến qua nhiều lần chạy.
- Chỉ cho phép truy cập nội bộ trong closure.
Rồi, bây giờ quay lại code phía trên. Để thực hiện 2 đặc điểm trên, đơn giản chỉ cần khai báo biến bên trong
function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3.let c = sum[10, 5]; // Sử dụng trước
function sum[a, b] { return a + b; } // Khai báo sau
0Như trên đã phân tích, thì
function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3 chỉ chạy 1 lần. Mục đích lần chạy duy nhất này là khởi tạo giá trị cho biến function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
18. Và các lần gọi tiếp theo thì không phải gọi function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3 mà trở thành gọi function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
2.Function
function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
2 cùng cấp với biến function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
18 do đó chúng có thể truy cập lẫn nhau. Và bắt đầu ở đây, hai đặc điểm của closure được thực thi.Đặc điểm 1:
function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
2 có thể chạy nhiều lần, nhưng giá trị của function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
18 vẫn giữ lại, vì JS chỉ xóa những gì khai báo trong function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
2 khi nó chạy xong, function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
18 khai báo bên ngoài nên không bị xóa khi function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
2 chạy xong.Đặc điểm 2:
function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
2 và function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
18 cùng thuộc trong function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3, là local var của function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3 nên các đối tượng bên ngoài không truy cập được. Nghĩa là chỉ trong function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3, đồng nghĩa chỉ các member của closure mới có thể truy cập lẫn nhau.Đấy, bắt đầu thấy liên quan chưa.
Question 2
Câu hỏi thứ 2 liên quan tới câu hỏi 1 mà các bạn có thể nghĩ đến, là "làm sao biến trong outer có thể được giữ lại khi outer chỉ thực hiện một lần rồi thôi?"
Thực ra
function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3 chưa bao giờ bị dừng lại cả, do đó các biến khai báo trong function function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3 vẫn giữ được giá trị, không bị xóa đi. Chỉ tới khi function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
2 hoặc function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
03 không còn dùng nữa thì JS mới loại bỏ luôn function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3.Có thể hiểu như nếu còn
function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
2 thì function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3 đang trong trạng thái chờ. Chờ cho tới khi không còn dùng tới function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
2 nữa thì function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3 mới kết thúc.Nếu đi sâu vào thêm nữa thì nó liên quan tới cách JS thực hiện các function trong execution context, rồi call stack đủ thứ nên thôi dừng lại ở đây.
3. Rút gọn closure
Closure như ví dụ trên vẫn khá dài, nên được rút gọn lại như sau.
let c = sum[10, 5]; // Sử dụng trước
function sum[a, b] { return a + b; } // Khai báo sau
1Phiên bản đầu tiên rút gọn đi hàm
function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
2 mà trực tiếp return một hàm ẩn danh luôn, đỡ phải viết nhiều.Tiếp theo, chúng ta thay vì viết hàm
function change[] {
this.innerHTML = "Clicked";
// This ở đây mang ý nghĩa khác
// Không còn là element được nhận event nữa
// Nên code không hoạt động
}
3 như bình thường [dạng function sum[a, b] { return a + b; }
sum[10, 5]; // As a statement
let c = sum[10, 5]; // In expression
44] thì đổi lại viết dạng function expression và cho nó tự chạy [self invoking] luôn.let c = sum[10, 5]; // Sử dụng trước
function sum[a, b] { return a + b; } // Khai báo sau
24. Example
Ví dụ trên về closure của mình có lẽ không rõ nghĩa lắm, tuy có phân tích khá chi tiết nhưng thực sự nó không có nghĩa trong thực tế.
Do đó, mình khuyến khích các bạn tìm hiểu thêm một số ví dụ về closure khác nữa, để nắm rõ hơn về closure vì đây là một khái niệm cực kì quan trọng.