Chức năng trả về cuộc gọi javascript

Chương hôm nay giới thiệu về hàm trong JavaScript, tưởng tượng đơn giản nhưng không đơn giản chút nào

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

A. Tổng quan về chức năng

1. Tổng quan

Hàm (hàm) là một nhóm các câu lệnh có liên quan, được gom lại với nhau, được đặt tên và thực hiện một công việc gì đó. Hàm sử dụng giúp chế độ hạn chế trùng lặp, tăng tính sử dụng lại và tổ chức mã nguồn tốt hơn

function has two section

  • Phần khai báo (declaration) hay còn gọi là định nghĩa (definition)
  • Phần gọi hàm (gọi hoặc gọi)

Hàm có thể là riêng lẻ hoặc thuộc về một hàm hay đối tượng nào đó (hàm bên trong đối tượng được gọi là phương thức - phương thức)

định nghĩa hàm

Cú pháp cơ bản định nghĩa một hàm

function func_name(param1, param2,...) {
    ...
}

Một hàm định nghĩa bao gồm từ khóa hàm, tên hàm, theo sau là danh sách tham số (tham số - viết tắt tham số) trong ngoặc kép. Cuối cùng là thân hàm bao gồm các câu lệnh trong cặp {}

Gọi hàm (gọi)

Hàm có thể chạy bằng cách gọi hàm (gọi - gọi) hoặc để hàm tự động chạy (tự gọi). Hàm tự động chạy sẽ được bàn sau

Hàm có thể sử dụng riêng lẻ như một câu lệnh hoặc sử dụng trong một biểu thức (khi hàm này phải trả về giá trị bất kỳ đó)

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression

Khi gọi hàm, cần truyền cho nó một số đối số. Các đối số này được đưa vào hàm và được chuyển vào các tham số tương ứng. Trong JS không bắt buộc, và cũng không kiểm tra truyền đối số có đầy đủ hay không, vậy bạn có thể truyền thiếu, thừa đối số cũng không sao. Các tham số bị thiếu thì sẽ có giá trị là không xác định, hoặc giá trị mặc định nếu có

Chú ý, khi gọi hàm cần có trích dẫn (), dù có truyền đối số hay không. Ví dụ như sau

sum();  // Ok
sum;  // Không có lỗi, nhưng hàm sum không được chạy

Nếu bạn chỉ viết

<button onclick="this.innerHTML = 'Clicked'">Click mebutton>
6 mà không phải là
<button onclick="this.innerHTML = 'Clicked'">Click mebutton>
7, thì thay vì hàm chạy, nó sẽ trả về hàm chính đó. Khi đưa ra đầu ra bên ngoài, thì nó sẽ trả về toàn bộ mã của hàm

Trả về một giá trị

Khi trả về câu lệnh, hàm sẽ thoát và trả về giá trị nếu có. Các lệnh còn lại sẽ không được thực hiện (ngoại trừ một số trường hợp như khối cuối cùng)

function ABC() {
    return 10;  // ABC() = 10
    return;  // ABC() = undefined
}

cẩu chức năng

Chức năng được nâng (kéo lên), tương tự các biến khai báo với var. Kỹ thuật nâng hạ đã được nói tới trong các chương trình trước, nên không bàn nhiều ở đây

let c = sum(10, 5);  // Sử dụng trước
function sum(a, b) { return a + b; }  // Khai báo sau

2. Chức năng & sự kiện

Sự kiện HTML là những sự kiện xảy ra đối với phần tử cụ thể, ví dụ như click vào nút thì sự kiện onclick của nút được gọi,. Toàn bộ tên sự kiện là chữ thường, và có dạng thuộc tính của thẻ

JS nội tuyến

Bên trong thuộc tính sự kiện của thẻ, có thể chứa các đoạn mã JS

<button onclick="alert('Hello');">Click mebutton>

Khi sự kiện cụ thể được kích hoạt, thì mã trong sự kiện đó được chạy. Vì mã JS thường dài, nếu đặt hết vào thuộc tính sự kiện sẽ rắc rối, nên tốt nhất là sử dụng sự kiện để gọi một chức năng đã được định nghĩa từ trước

function hello() {
    alert("Hello");
}
<script src="script.js">script>
...
<button onclick="hello();">Click mebutton>

Về các sự kiện, các bạn sẽ được tìm hiểu trong DOM DOM

từ khóa này

Để biết được phần tử nào được nhận sự kiện nào, thì hãy sử dụng từ khóa this. Điều này trong một sự kiện thuộc tính mang ý nghĩa là phần tử nhận được thuộc tính đó

<button onclick="this.innerHTML = 'Clicked'">Click mebutton>

Thuộc tính

<button onclick="this.innerHTML = 'Clicked'">Click mebutton>
8 cơ bản được sử dụng để thay đổi nội dung của phần tử dừng

Chú ý, khi gọi hàm trong sự kiện, thì cái này không sử dụng được trong hàm. Ví dụ như after will not run

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
2____10

Giải pháp cho vấn đề này là truyền đối tượng này cho hàm

<button onclick="this.innerHTML = 'Clicked'">Click mebutton>
9 như một tham số. Mã sửa lại như sau

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
1
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
2

Sự kiện sẽ truyền điều này thành một tên tham số là

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
20. Khi vào hàm thì sử dụng tham số này thay vì thế này, nên kết quả mã chạy tốt

3. từ khóa này

Từ khóa này trong JS thực sự có nhiều nghĩa, nhưng cũng dễ dàng để nắm bắt được

  • Thuộc tính sự kiện HTML. đây là sự kiện nhận được phần tử
  • Trọng hàm. đây là chủ sở hữu (chủ sở hữu) của chức năng. If function is method, mean is property any object that, then this is object. If function đứng riêng lẻ, thì this is object window (function riêng lẻ thì luôn thuộc object window)
  • Trong function, when call by call, apply. Đây là đối tượng được truyền vào hàm dưới dạng tham số ẩn (sẽ được trình bày trong phần sau)
  • Trong chế độ nghiêm ngặt. chức năng này bị cấm (có giá trị không xác định)

B. Định nghĩa & gọi

1. Định nghĩa hàm (nâng cao)

biểu thức hàm

Ngoài ra, chức năng khai báo (declare) hoặc định nghĩa (define) 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
3

Còn có cách viết khác là sử dụng hàm dạng biểu thức (biểu thức). Chức năng biểu thức có thể gán vào một biến, và biến này sẽ có kiểu chức năng

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
4

Chú ý dòng 2, biểu thức gán cho biến

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
21 có dạng một hàm ẩn danh (ẩn danh) là một hàm không có tên. Và sử dụng chức năng trên các chức năng tương tự như bình thường

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
5

Biểu thức hàm không được lưu trữ, vì bản thân nó là một giá trị (vế phải có dấu bằng), nên không được lưu trữ. Thực hiện biến vẫn được cẩu, nhưng chỉ là không được sử dụng như chức năng (giá trị là không xác định), nên coi như nó không được cẩu

Hàm tạo hàm

Cách khác nữa để tạo hàm là sử dụng Hàm tạo

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
6

Hàm khởi tạo hàm nhận vào nhiều tham số, trong đó tham số cuối cùng là phần thân mã của hàm

Không nên sử dụng hàm tạo chức năng, vì mã lỗi và không an toàn

2. Tham số và đối số

Tham số và đối số

Tham số (tham số - param) là các biến trong cặp () của hàm, đại diện cho các tham số được truyền vào trong hàm. Các tham số được coi như các biến cục bộ trong hàm và bị hủy khi hàm thực thi xong

Đối số (argument) là những biến, giá trị thực đượ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
7

Người ta nhắc tới tham số khi ở bên trong định nghĩa hàm, và gọi là tham số khi gọi hàm. Và tham số phải là biến (đối tượng biến, hàm biến 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,

Quy tắc tham số

JS không định kiểu cho tham số, và cũng không kiểm tra lượng đối số truyền vào. Do đó, đối số truyền cho hàm có thể nhiều hơn, hoặc ít hơn số lượng tham số cần có. Những tham số không nhận được đối số (do thiếu phương tiện truyền tải) thì sẽ có giá trị là không xác định

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
8

ES6 (ECMAScript 2015) cho phép hàm có giá trị mặc định cho tham số. Khi truyền không đủ đối số, thì những tham số bị thiếu thay vì có giá trị là không xác định, thì nó sẽ sử dụng giá trị mặc định

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
9

Trong đoạn mã trên, số 10 truyền cho

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
22,
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
23 không nhận được đối số nào, nhưng vì
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
23 có giá trị mặc định nên giá trị lúc này của nó chính là giá trị mặc định 100. Còn
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
25 thì không có đối số, cũng không có giá trị mặc định nên nó mang giá trị không xác định

Đối số đi qua

Đây là khái niệm cực kỳ 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 nguyên thủy thì được truyền theo giá trị (theo giá trị). Một bản sao của đối số được tạo ra và đưa vào tham số, mọi thao tác trong hàm đều được 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 tượng đối số, thì truyền kiểu truyền theo tham chiếu (tham chiếu). Thực ra vẫn là truyền theo giá trị, nhưng giá trị ở đây là tham chiếu tới địa chỉ bộ nhớ, nếu các thay đổi trên tham chiếu thì ảnh hưởng tới dữ liệu gốc

3. lời gọi hàm

Gọi một chức năng

Để gọi (gọi - gọi) một chức năng, hãy đặt tên cho nó và truyền cho nó danh sách các đối số. The number of argument not started must be match with the tham số

sum();  // Ok
sum;  // Không có lỗi, nhưng hàm sum không được chạy
0

Nếu hàm thuộc về một đối tượng, thì hàm gọi là phương thức (phương thức). Gọi phương thức tương tự như hàm gọi, nhưng phải có tên đối tượng 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
1

Thực hiện mọi chức năng trong JS đều là phương thức, các chức năng không thuộc đối tượng nào thực hiện thuộc về cửa sổ đối tượng. 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 hàm thuộc đối tượng, chúng ta gọi đối tượng là chủ sở hữu (chủ sở hữu) của hàm đó

Gọi hàm như một hàm tạo

Có thể gọi hàm bằng từ khóa mới, lúc này hàm được coi như là một hàm tạo. Constructor thường được sử dụng để khởi tạo một đối tượng mới

sum();  // Ok
sum;  // Không có lỗi, nhưng hàm sum không được chạy
3

Điều này trong trường hợp này không phải cửa sổ đối tượng, mà là đối tượng mới được tạo ra nhờ từ khóa mới. Đối tượng này sẽ được gán tham chiếu tới biến

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
26, do đó
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
26 là một đối tượng. Tuy nhiên, điều này lúc này không có giá trị (rỗng), và giá trị được thêm vào điều này sẽ được sử dụng để tạo đối tượng mới

Vấn đề này sẽ được thảo luận kỹ hơn trong đối tượng chương trình

Chức năng tự gọi (IIFE)

Một hàm có thể được gọi tự động mà không cần lời gọi hàm, chúng có tên là hàm tự gọi, hoặc IIFE (Biểu thức hàm gọi ngay lập tức)

sum();  // Ok
sum;  // Không có lỗi, nhưng hàm sum không được chạy
4

Chú ý hai dòng trên, chúng ta có 2 cách để thực hiện một chức năng thành tự gọi

  • Cách 1 đối với biểu thức hàm. chỉ cần thêm một cặp dấu ngoặc () cuối của nó là được
  • Cách 2 đối với hàm 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 () vào cuối

Hàm tự gọi tự động chạy khi được định nghĩa

C. tính năng chức năng

1. chức năng ẩn danh

Anonymous function (hàm hide list) is a function no name, đơ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
5

Hàm ẩn danh thường được dùng trong biểu thức hàm, để viết nhanh một hàm chỉ dùng một lần. Hàm ẩn danh cũng được sử dụng để gọi lại, truyền dưới dạng tham số cho một hàm khác và hàm chính được gọi lại (gọi lại) sau một khoảng thời gian làm gì đó

Chức năng trả về cuộc gọi javascript
. Ngoài ra nó còn được sử dụng trong chức năng đóng hoặc cho chức năng tự gọi.

2. đối tượng chức năng

Hàm cũng là một đối tượng trong JS, cũng có các thuộc tính (thuộc tính) và phương thức (phương thức). Chúng ta chỉ quan tâm tới hai đối tượng cơ bản nhất

Phương thức toString()

Use convert a function to string, mean is show full code of function that

sum();  // Ok
sum;  // Không có lỗi, nhưng hàm sum không được chạy
6

Đối số đối tượng

Bên trong mỗi chức năng có một đối tượng ẩn là

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
28. Nó giống như một mảng các đối số được truyền vào, có thuộc tính
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
29 để lấy độ dài. Chú ý các đối số khác với các giá trị mà thông số nhận được, nó là các giá trị thực sự được truyền vào

3. chức năng mũi tên

Hàm mũi tên (hàm mũi tên) là một cách viết khác rút gọn hơn cho hàm, đượ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
7

Cách 2 là cú pháp chuẩn của hàm mũi tên, dạng

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
00, với dấu
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
01 là dấu mũi tên (mũi tên)

Rút gọn trả về

Cách 3 Rút gọn lệnh return. If body function only have a command return, then could Rút gọn theo cách bỏ trích dẫn {}

Rút gọn một lệnh

If body function only have a command (not must be return), then could reject quote {}

sum();  // Ok
sum;  // Không có lỗi, nhưng hàm sum không được chạy
8

Chú ý, if only have a command but is return, then must bỏ return as way 3 at on. If to return always will have error

sum();  // Ok
sum;  // Không có lỗi, nhưng hàm sum không được chạy
9

Rút gọn tham số

If function has 1 tham số, thì cú pháp () có thể bỏ qua

function ABC() {
    return 10;  // ABC() = 10
    return;  // ABC() = undefined
}
0

If no tham số nào, then must keep cặp (), không được bỏ qua

function ABC() {
    return 10;  // ABC() = 10
    return;  // ABC() = undefined
}
1

D. Gọi, áp dụng và đóng cửa

1. Phương thức gọi

Mỗi hàm đều có một phương thức với tên

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
02, dùng để truyền một đối tượng vào hàm đó. Khi đối tượng vào hàm được gọi với
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
02, thì đối tượng sẽ biến thành đối tượng this. Hàm sử dụng cái này, thực chất là sử dụng đối tượng được truyền vào

Ví dụ như đoạn mã sau

function ABC() {
    return 10;  // ABC() = 10
    return;  // ABC() = undefined
}
2

Mã trên định nghĩa hai đối tượng

  • function sum(a, b) { return a + b; }
    sum(10, 5);  // As a statement
    let c = sum(10, 5);  // In expression
    
    04 chuyên dùng xử lý phân số, bao gồm phương thức
    function sum(a, b) { return a + b; }
    sum(10, 5);  // As a statement
    let c = sum(10, 5);  // In expression
    
    05 và có thể thêm các phương thức khác. Đối tượng này chỉ xử lý các phân số, không chứa bất kỳ dữ liệu kỳ hạn nào
  • function sum(a, b) { return a + b; }
    sum(10, 5);  // As a statement
    let c = sum(10, 5);  // In expression
    
    06 a number of
    function sum(a, b) { return a + b; }
    sum(10, 5);  // As a statement
    let c = sum(10, 5);  // In expression
    
    07 and
    function sum(a, b) { return a + b; }
    sum(10, 5);  // As a statement
    let c = sum(10, 5);  // In expression
    
    08. Đối tượng này được truyền cho phương thức
    function sum(a, b) { return a + b; }
    sum(10, 5);  // As a statement
    let c = sum(10, 5);  // In expression
    
    09 bằng phương thức
    function sum(a, b) { return a + b; }
    sum(10, 5);  // As a statement
    let c = sum(10, 5);  // In expression
    
    02. Lúc này,
    function sum(a, b) { return a + b; }
    sum(10, 5);  // As a statement
    let c = sum(10, 5);  // In expression
    
    06 đi vào bên trong
    function sum(a, b) { return a + b; }
    sum(10, 5);  // As a statement
    let c = sum(10, 5);  // In expression
    
    05, biến thành
    function sum(a, b) { return a + b; }
    sum(10, 5);  // As a statement
    let c = sum(10, 5);  // In expression
    
    13 và nằm trong bảng điều khiển bên ngoài

Bạn sẽ đặt câu hỏi "tại sao không truyền thẳng đối tượng vào phương thức như một tham số?". Thực hiện câu hỏi đó hoàn toàn có lý do, và mã như vậy chúng ta sẽ trông giống như sau

function ABC() {
    return 10;  // ABC() = 10
    return;  // ABC() = undefined
}
3

Hai đoạn mã trên tương tự nhau, cho ra kết quả giống nhau

Tai sao goi?

Sự khác biệt nằm ở vị trí bối cảnh của chức năng thay đổi. Khi gọi hàm với

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
02 và một đối tượng, thì giống như việc đưa hàm vào trong đối tượng kia. Đối với hàm là phương thức, thì chủ sở hữu của phương thức bị thay đổi thành đối tượng được gọi bởi
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
02. Ví dụ như mã trên thực tế như sau

function ABC() {
    return 10;  // ABC() = 10
    return;  // ABC() = undefined
}
4

Nói chung phần này hơi ảo, và thực tế không sử dụng nhiều đối tượng như cách truyền thông như tham số

Cuộc gọi và thông số

Chức năng được gọi với

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
02 có thể có thêm các thông số khác

function ABC() {
    return 10;  // ABC() = 10
    return;  // ABC() = undefined
}
5

Lúc này khi gọi

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
02 thì truyền đúng theo tham số, nhưng đối số đầu tiên phải là đối tượng được truyền vào. Các đối số thứ 2 sẽ thành tham số 1, đối số 3 thành tham số 2,. cứ như vậy. Nghĩa là đối số đầu tiên bị mất và trở thành
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
13 trong hàm

As on code on, then when call

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
02 đối số
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
06 thành
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
13 in function
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
22, chuỗi đối số thứ 2 "Phan so test" được truyền vào tham số thứ nhất
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
23

2. Áp dụng phương pháp

Phương pháp

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
24 tương tự như
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
02, nhưng sự khác biệt ở các vị trí tham số bổ sung được truyền dưới dạng mảng

function ABC() {
    return 10;  // ABC() = 10
    return;  // ABC() = undefined
}
6

Hàm được

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
24 vẫn giữ nguyên các tham số riêng lẻ, nhưng khi gọi với
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
24 như trên thì truyền vào là mảng. Từng phần tử của mảng này sẽ chuyển vào từng tham số tương ứng để phù hợp

ES6 cung cấp một cách khác là toán tử trải rộng tương tự như

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
24, nhưng không phổ biến lắm

Sử dụng

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
24 rất tiện lợi khi các đối tượng truyền vào có dạng mảng, sẽ dễ dàng hơn trong các thao tác

3. Gọi, áp dụng trong chế độ nghiêm ngặt

Thông thường, nếu gọi

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
02 hoặc
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
24 với đối số đầu tiên không phải đối tượng hoặc không truyền đối tượng, thì khi vào chức năng được gọi
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
13 sẽ là cửa sổ đối tượng

Nhưng trong chế độ nghiêm ngặt, điều này bị cấm. Khi sử dụng

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
02 hoặc
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
24, đối số đầu tiên bắt buộc phải là đối tượng và không được bỏ qua

E. Khép kín

1. Tổng quan

Closure là một trong những khái niệm đáng nhớ nhất trong JS, và rất khó hiểu và định nghĩa chính xác nó là gì

Bao đóng là sự kết hợp của một chức năng được nhóm lại với nhau (kèm theo) với các tham chiếu đến trạng thái xung quanh của nó (môi trường từ vựng). Nói cách khác, một bao đóng cho phép bạn truy cập vào phạm vi của chức năng bên ngoài từ một chức năng bên trong

Theo định nghĩa của MDN về việc đóng cửa, thì có thể hiểu như sau (tạm dịch)

Việc đóng cửa là kết quả của một chức năng được gói lại và tham chiếu tới môi trường xung quanh (nơi chứa chức năng đó, nơi nó được tạo ra)

Có vẻ như khái niệm đóng vẫn còn khá đối tượng, nhưng đừng lo. Vui lòng đi vào đoạn mã ví dụ để hiểu rõ hơn

function ABC() {
    return 10;  // ABC() = 10
    return;  // ABC() = undefined
}
7

Bên trên là cấu trúc cơ bản của một bao đóng. Theo định nghĩa phía trên, chúng ta áp dụng vào mã để hiểu rõ hơn

Ở đây hàm

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
35 được đóng gói lại bên trong hàm
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36. Chức năng
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
35 này, chức năng tương tự khác, có thể truy cập vào các
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
38 (biến cục bộ của chính nó), hoặc
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
39 (biến toàn cục của chương trình). Điểm đặc biệt là
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
35 cũng có thể truy cập vào
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
41, vì nó liên tục tham chiếu đến hàm
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
42, là nơi nó được tạo ra

Đóng cửa & phạm vi

Khi nhắc tới việc đóng phải nhắc tới phạm vi. Như phân tích ở trên, chức năng thường chỉ có hai phạm vi, nhưng đóng lại tạo ra tới 3 phạm vi. Chính phạm vi mới này được tạo ra nên các tính chất đặc biệt của việc đóng cửa

Đặc điểm đóng cửa

Một lần đóng cửa có hai đặc điểm sau

  • Lưu trữ giá trị của biến qua nhiều lần chạy. special point này giống với global var. Nghĩa là khi chạy hàm nhiều lần, thì giá trị lần trước của hàm vẫn giữ nguyên như vậy, không bị mất đi
  • Chỉ cho phép truy cập nội bộ. special point this same var cục bộ. Đối với việc đóng cửa, chỉ cho phép những thành viên nội bộ mới của nó có thể truy cập được

2. Constructor a closure

Closure has cấu trúc bao gồm 2 phần

  • Định nghĩa đóng phần định nghĩa. is a function return a function con side in it
  • Phần sử dụng đóng. Hàm trên được tự động chạy 1 lần đầu tiên để khởi tạo giá trị (bằng cách tự gọi hoặc biểu thức hàm)

Ví dụ như sau là một lần đóng cửa đầy đủ nhất

function ABC() {
    return 10;  // ABC() = 10
    return;  // ABC() = undefined
}
8

Câu đố dành cho bạn đây. Hàm

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36 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 sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36 chỉ được gọi duy nhất một lần trong câu lệnh let, trong biểu thức hàm có dấu () nghĩa là chạy
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36 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
46 cũng giống như chạy
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36 vì đã được gán vào rồi". Vâng, nhưng thứ được 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
48 ở đây không phải bản thân
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36, mà là cái hàm "bé bé xinh xinh" được
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36 return ra đấy. Chính là thằng ml
Chức năng trả về cuộc gọi javascript
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
35 đấy.

Để phân tích kỹ năng 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
52. Đầu tiên,
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36 sẽ chạy trước vì có tự gọi (). Kết quả
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36 trả về tham chiếu của anh
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
35. Biến
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
48 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
46 là
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
35

Mã trên nên viết lại với những nhận xét như sau cho dễ hiểu

function ABC() {
    return 10;  // ABC() = 10
    return;  // ABC() = undefined
}
9

Vâng, đó chính là cách hoạt động của việc đóng cửa

Câu hỏi 1

Câu hỏi đầu tiên được đặt ra ở đây là "mã trên liên quan còn gì để đóng?"

Hãy nhớ lại phạm vi đặc điểm của một lần đóng cửa

  • Lưu trữ biến giá trị qua nhiều lần chạy
  • Chỉ cho phép truy cập nội bộ trong quá trình đóng

Rồi, bây giờ quay lại mã phía trên. Để thực hiện 2 đặc điểm trên, đơn giản chỉ cần khai báo các biến bên trong

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36

let c = sum(10, 5);  // Sử dụng trước
function sum(a, b) { return a + b; }  // Khai báo sau
0

Như đã phân tích, thì

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36 chỉ chạy 1 lần. Mục đích của 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
61. Và các lần gọi tiếp theo thì không phải gọi
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36 mà trở thành gọi
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
35

Hàm

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
35 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
61 để chúng có thể truy cập lẫn nhau. Và bắt đầu ở đây, hai đặc điểm của việc đóng cửa được thực thi

Đặc điểm 1.

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
35 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
61 vẫn được giữ lại, vì JS chỉ xóa những gì khai báo trong
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
35 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
61 khai báo bên ngoài không nên bị xóa khi
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
35 chạy xong

Đặc điểm 2.

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
35 và
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
61 cùng thuộc về
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36, là local var của
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36 nên các đối tượng bên ngoài không truy cập được. Nghĩa là chỉ trong
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36, đồng nghĩa với việc chỉ các thành viên của close mới có thể truy cập lẫn nhau

À, bắt đầu thấy liên quan chưa

Câu hỏi 2

Câu hỏi thứ 2 liên quan đến câu hỏi 1 mà các bạn có thể nghĩ đến, là "làm sao biến bên ngoài có thể được giữ lại khi bên ngoài chỉ thực hiện một lần rồi thôi?"

Thực ra

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36 chưa bao giờ bị dừng lại cả, do đó các biến khai báo trong hàm
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36 vẫn giữ nguyên giá trị, không bị xóa đi. Only to when
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
35 or
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
46 not also used nữa thì JS newloại bỏ luôn
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36

Có thể hiểu như nếu còn

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
35 thì
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36 đang ở trạng thái chờ. Chờ cho tới khi không còn dùng tới
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
35 nữa thì
function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36 mới kết thúc

Nếu đi sâu vào thêm nữa thì nó liên quan đến cách JS thực thi các hàm trong ngữ cảnh thực thi, rồi gọi ngăn xếp đủ thứ nên dừng lại ở đây

3. Rút gọn đóng cửa

Closure as example on vẫn cò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
1

Phiên bản đầu tiên Rút gọn đi hàm

function sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
35 mà trực tiếp trả về 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 sum(a, b) { return a + b; }
sum(10, 5);  // As a statement
let c = sum(10, 5);  // In expression
36 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
87) nên đổi lại biểu thức hàm viết dạng và để nó tự chạy (tự gọi) luôn

let c = sum(10, 5);  // Sử dụng trước
function sum(a, b) { return a + b; }  // Khai báo sau
2

4. Thí dụ

Ví dụ về việc đóng cửa của mình có thể không rõ nghĩa lắm, tuy có phân tích khá chi tiết nhưng thực tế 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ề sự đóng cửa khác nữa, để nói rõ hơn về sự đóng cửa vì đây là một khái niệm cực kỳ quan trọng