Hướng dẫn self-invoking function javascript - chức năng tự gọi javascript
JavaScript seriesChươ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. Show
Nội dung chính
Nội dung chính
B. Definition & invocation 1. Function definition (advanced) A. Function overview1. Overview2. Function & events 3. This keyword
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 2. Cấu trúc một closure 3. Rút gọn closure
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.
2. Function & events3. 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
0D. Call, apply & closure 1 21. Call method 3. This keywordB. Definition & invocation
B. Definition & invocation1. 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. 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. 4Chú ý dòng 2, biểu thức gán cho biến 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. 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. 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 & argumentsParameters & 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ự. 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. 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. 9Trong đoạn code trên, số 10 truyền cho 9, 0 không nhận được đối số nào, nhưng vì 0 có default value nên giá trị lúc này của nó chính là default value 100. Còn 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 invocationInvoking 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ố. 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. 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ự. 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. 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 3, do đó 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). 4Chú ý hai dòng trên, chúng ta có 2 cách để làm một function thành self invoking:
Self invoking function tự động chạy khi được định nghĩa. C. Function features1. Anonymous functionAnonymous function (hàm ẩn danh) là một hàm không có tên, đơn giản vậy thôi. 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ì đó . Ngoài ra nó còn được dùng trong closure hoặc cho self invoking function.2. Function objectFunction 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 đó. 6Arguments object Bên trong mỗi function có một object ẩn là 5. Nó giống như một mảng các đối số được truyền vào, có thuộc tính 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 functionArrow 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. 7Cách 2 là syntax chuẩn của arrow function, dạng 7, với dấu 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 {}. 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. 9Rút gọn tham số Nếu function có 1 tham số, thì ngoặc () có thể bỏ đi. 0Nếu không có tham số nào, thì phải giữ lại cặp (), không được bỏ đi. 1D. Call, apply & closure1. Call methodMỗi function đều có một method với tên 9, dùng để truyền một object vào function đó. Khi object vào function được gọi với 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. 2Code trên định nghĩa hai object:
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. 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 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 9. Ví dụ như code trên thực ra như sau. 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 9 có thể có thêm các param khác. 5Lúc này khi gọi 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 0 trong function.Như trên code trên, thì khi gọi 9 đối số 3 thành 0 trong function 9, đối số chuỗi thứ 2 "Phan so test" được truyền vào tham số 0 thứ nhất.2. Apply methodMethod 1 tương tự như 9, nhưng sự khác biệt ở chỗ các tham số bổ sung được truyền dưới dạng mảng. 6Function được 1 vẫn giữ nguyên các tham số riêng lẻ, nhưng khi gọi với 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 1, nhưng chưa phổ biến lắm.Sử dụng 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 modeBình thường, nếu gọi 9 hoặc 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 0 sẽ là đối tượng window.Nhưng trong strict mode, điều này bị cấm. Khi dùng 9 hoặc 1, đối số đầu tiên bắt buộc phải là object, và không được bỏ qua.E. Closure1. OverviewClosure 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ì.
Theo định nghĩa của MDN về closure, thì có thể hiểu như sau (tạm dịch).
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. 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 2 được gói lại bên trong function 3. Function 2 này, tương tự function khác, có thể truy cập vào các 5 (biến cục bộ của chính nó), hoặc 6 (biến toàn cục của chương trình). Điểm đặc biệt là 2 cũng có thể truy cập vào 8, vì nó giữ tham chiếu tới function 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:
2. Cấu trúc một closureClosure có cấu trúc gồm 2 phần:
Ví dụ như sau là một closure đầy đủ nhất. 8Câu đố dành cho bạn đây. Hàm 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 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 3 một lần.Bạn sẽ bảo "chạy 03 cũng giống như chạy 3 vì được gán vào rồi". Vâng, nhưng thứ gán vào biến 05 ở đây không phải bản thân 3, mà là cái function "bé bé xinh xinh" được 3 return ra đấy. Chính là thằng ml 2 đấy.Để tớ phân tích kĩ hơn câu lệnh 09. Đầu tiên, 3 sẽ chạy trước vì có self invoking (). Kết quả 3 trả về tham chiếu của thằng 2. Biến 05 phải nhận tham chiếu này, và kể từ đó 03 là 2.Code trên nên viết lại với những comment như sau cho dễ hiểu. 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:
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 3. 0Như trên đã phân tích, thì 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 18. Và các lần gọi tiếp theo thì không phải gọi 3 mà trở thành gọi 2.Function 2 cùng cấp với biến 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: 2 có thể chạy nhiều lần, nhưng giá trị của 18 vẫn giữ lại, vì JS chỉ xóa những gì khai báo trong 2 khi nó chạy xong, 18 khai báo bên ngoài nên không bị xóa khi 2 chạy xong.Đặc điểm 2: 2 và 18 cùng thuộc trong 3, là local var của 3 nên các đối tượng bên ngoài không truy cập được. Nghĩa là chỉ trong 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 3 chưa bao giờ bị dừng lại cả, do đó các biến khai báo trong function 3 vẫn giữ được giá trị, không bị xóa đi. Chỉ tới khi 2 hoặc 03 không còn dùng nữa thì JS mới loại bỏ luôn 3.Có thể hiểu như nếu còn 2 thì 3 đang trong trạng thái chờ. Chờ cho tới khi không còn dùng tới 2 nữa thì 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 closureClosure như ví dụ trên vẫn khá dài, nên được rút gọn lại như sau. 1Phiên bản đầu tiên rút gọn đi hàm 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 3 như bình thường (dạng 44) thì đổi lại viết dạng function expression và cho nó tự chạy (self invoking) luôn. 24. ExampleVí 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. |