Views: 708
Mục tiêu
Xây dựng một ứng dụng tính toán nhỏ, mục đích chính là giúp hiểu hơn về hai khái niệm khá là phổ biến trong Javascript, đó là Closure và IIFE.
Định nghĩa
Closure
Được hiểu là khi một hàm được viết lồng vào bên trong một hàm khác (hàm cha), nó có thể sử dụng biến toàn cục, biến cục bộ của hàm cha và biến cục bộ của chính nó (lexical scoping)
Ví dụ đơn giản
1 2 3 4 5 6 7 8 9 |
function hello(greeting) { return function (who) { console.log(greeting + ' ' + who); } } hello('Hello')('Dat'); // Hello Dat hello('Good morning')('Matianda'); // Good morning Matianda |
Ví dụ mở rộng
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function printInterviewQuestionFor(job) { return function (name) { var name = (name !== undefined && name != "") ? name : "..."; if (job === "developer") { console.log("Can you tell me about latest software you have built, " + name + "?"); } else if (job === "designer") { console.log("Can you please explain about UX design, " + name + "?"); } else { console.log("Hello " + name + ", what do you do?"); } } } printInterviewQuestionFor("developer")("Matianda"); //Can you tell me about latest software you have built, Matianda? printInterviewQuestionFor(); // nothings happen printInterviewQuestionFor("designer")(); //Can you please explain about UX design, ? printInterviewQuestionFor("")(); |
IIFE (Immediate Invoke Function Expression)
Nội dung thực thi script được viết theo hình thức này sẽ được chạy ngay lập tức, ngay sau khi đoạn script được load lên thành công, không cần thông qua bất kỳ lời gọi hàm nào.
Về syntax cho kiểu viết, có một mẹo nhỏ gồm các bước giúp dễ nhớ hơn cách khai báo hàm theo hình thức IIFE như sau:
1 2 3 4 5 6 7 8 9 10 |
// step 1, định khung ()(); // step 2, thêm mắm "function(){}" (function(){})(); // step 3, thêm muối "enter" (function() { // do somethings })(); |
Ví dụ đơn giản
1 2 3 4 5 |
(function(greeting, who) { console.log(greeting + ' ' + who); })("Hello", "Matianda"); // Console print: "Hello Matianda" |
Ví dụ mở rộng
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
var Printer = (function () { // private var hello = "Hello, "; // public return { greeting: function() { return hello; } } })(); var PrinterController = (function(instancePrinter) { // public return { init: function() { var text = instancePrinter.greeting() + "Matianda"; console.log(text); } } })(Printer); PrinterController.init(); // will print "Hello, Matianda" |
Triển khai
Khởi tạo ban đầu
Trước tiên, chúng ta cần phác thảo khung sườn trước cho ứng dụng tính toán.
Trong ví dụ này sẽ bao gồm hai phần: xử lý user inputs, xử lý tính toán.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/** * Handle inputs */ var HandleInputs = (function() { // })(); /** * Main app */ var Calculator = (function(ctrlInputs) { // })(HandleInputs); |
Một trong những lợi ích của cách viết chia ra theo từng Object như này là giúp chúng ta tách bạch phần xử lý ở đơn vị hàm (đơn vị nhỏ nhất trong lập trình ứng dụng), giúp tăng tính tái sử dụng hàm và ứng dụng dễ bảo trì hơn.
Ngoài ra, với cách viết này, chúng ta còn có thể đảm bảo được tính đóng gói dữ liệu (áp dụng tư duy OOP) cho ứng dụng nữa, nghĩa là chỉ cho phép các Object (hiểu như Class trong OOP) truy cập lẫn nhau những gì mình muốn, tùy ý người lập trình.
Triển khai xử lý Inputs trong HandleInputs
Khởi tạo biến cục bộ trong HandleInputs
, ở đây chúng ta dùng prompt
để nhận giá trị user nhập vào:
1 2 3 4 5 6 |
// Get input from user var inputs = { a: prompt("Input a: "), b: prompt("Input b: "), operator: prompt("Input operator(+,-,*,/): "), }; |
Tiếp theo, chúng ta cần có một hàm xử lý giá trị input từ user, đảm bảo rằng input là định dạng số.
1 2 3 4 5 6 |
// make sure input is number var assertNumber = function (value) { var pattern = /^\d+$/; return pattern.test(value); }; |
Kế đến, đây là phần khá đặc biệt trong cách viết ứng dụng đảm bảo được tính đóng gói dữ liệu, mục đích là chỉ public những gì mình muốn để các Object khác tái sử dụng.
1 2 3 4 5 6 7 8 9 |
return { // if inputs is not number, set to 0 as default value get: function() { inputs.a = assertNumber(inputs.a) ? Number(inputs.a) : 0; inputs.b = assertNumber(inputs.b) ? Number(inputs.b) : 0; return inputs; } }; |
Trong phần xử lý hàm get
ở trên, ở đây mình chủ ý muốn kiểm tra nếu giá trị nhập vào không phải là số, thì lấy mặc định bằng 0
.
Và phần hàm để trong return
như này, ngầm hiểu là đang public ra cho các Object khác có thể truy cập và thực thi xử lý từ Object này. Ở phần sau chúng ta sẽ tìm hiểu kỹ hơn việc các Object khác truy cập vào đây như nào, cụ thể là Object Calculator
mà chúng ta sắp triển khai bên dưới đây.
Triển khai xử lý tính toán trong Calculator
Đầu tiên, chúng ta dùng cách viết IIFE để khởi tạo Object Calculator
, và import Object HandleInputs
đã hoàn chỉnh ở trên vào:
1 2 3 4 5 6 |
/** * Main app */ var Calculator = (function(ctrlInputs) { // do somethings })(HandleInputs); |
Trong phần code phía trên, chúng ta để ý HandleInputs
chính là Object chúng ta đã tạo, và giờ import vào sử dụng. ctrlInputs
chính là instance cho Object đó.
Bắt đầu triển khai các phần trong Object Calculator
, trước tiên là import và khởi tạo biến nhận kết quả:
1 2 |
var inputs = ctrlInputs.get(); var result = 0; |
Trong phần khai báo trên, để ý chỗ dùng hàm ctrlInputs.get()
, đó chính là cách áp dụng kế thừa hàm public từ Object khác.
Kế đến là việc khai báo các hàm tính toán cộng, trừ, nhân, chia (chúng ta sẽ giữ nguyên cấu trúc tựa như ở bài viết trước mình giới thiệu về Javascript Object):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// phép cộng var sumNumbers = function() { return inputs.a + inputs.b; }; // phép trừ var subNumbers = function() { return inputs.a - inputs.b; }; // phép nhân var multiNumbers = function() { return inputs.a * inputs.b; }; // phép chia var divNumbers = function() { return inputs.a / inputs.b; }; |
Tiếp đến, là phần điều hướng phép tính dựa theo operator
mà user nhập vào:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// điều hướng phép tính tương ứng var calculate = function() { switch (inputs.operator) { case '+': result = sumNumbers(); break; case '-': result = subNumbers(); break; case '*': result = multiNumbers(); break; case '/': result = divNumbers(); break; } return result; }; |
Bước quan trọng tiếp theo là public phần xử lý thông qua hàm init()
, để thực thi việc tính toán và xuất ra kết quả ở màn hình Console:
1 2 3 4 5 6 |
return { init: function() { calculate(); console.log(result); } } |
Cuối cùng, chạy thôi:
1 |
Calculator.init(); |
Và đây là toàn bộ phần source cho demo xây dựng ứng dụng tính toán bằng Javascript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
/** * Handle inputs */ var HandleInputs = (function() { // Get input from user var inputs = { a: prompt("Input a: "), b: prompt("Input b: "), operator: prompt("Input operator(+,-,*,/): "), }; // make sure input is number var assertNumber = function (value) { var pattern = /^\d+$/; return pattern.test(value); }; return { // if inputs is not number, set to 0 as default value get: function() { inputs.a = assertNumber(inputs.a) ? Number(inputs.a) : 0; inputs.b = assertNumber(inputs.b) ? Number(inputs.b) : 0; return inputs; } }; })(); /** * Main app */ var Calculator = (function(ctrlInputs) { var inputs = ctrlInputs.get(); var result = 0; // phép cộng var sumNumbers = function() { return inputs.a + inputs.b; }; // phép trừ var subNumbers = function() { return inputs.a - inputs.b; }; // phép nhân var multiNumbers = function() { return inputs.a * inputs.b; }; // phép chia var divNumbers = function() { return inputs.a / inputs.b; }; // điều hướng phép tính tương ứng var calculate = function() { switch (inputs.operator) { case '+': result = sumNumbers(); break; case '-': result = subNumbers(); break; case '*': result = multiNumbers(); break; case '/': result = divNumbers(); break; } return result; }; return { init: function() { calculate(); console.log(result); } } })(HandleInputs); Calculator.init(); |
Kết
Trong các dự án thực tế, dĩ nhiên chúng ta sẽ còn có phần xử lý inputs từ phía frontend riêng (HTML form), và tùy vào mỗi yêu cầu ứng dụng chúng ta sẽ có cách xử lý phù hợp khác nhau.
Trên đây là hướng dẫn cơ bản việc xây dựng ứng dụng tính toán bằng javascript, tất nhiên có thể vẫn còn nhiều thiếu sót, tuy nhiên mục đích chính là giới thiệu về cách viết mà mình học hỏi được. Qua đó giúp hiểu thêm về hai khái niệm: Closure và IIFE.
Tham khảo
- Closure definition: https://developer.mozilla.org/vi/docs/Web/JavaScript/Closures