Tất cả về Function - JavaScript (p1)

23 tháng 04, 2018 - 10160 lượt xem

Mọi thứ bạn cần biết về Function - Javascript

Đệ quy

 Function là gì?

Function (hàm, chức năng), gọi chung là subprogram (chương trình con) có thể được gọi ở bên ngoài hoặc bên trong chính nó.

 

Nó bao gồm tập hợp các câu lệnh gọi là function body. Các giá trị có thể truyền đến một hàm, và một hàm có thể trả về giá trị. 

Bây giờ, với các ứng dụng hiện đại, các function có thể là một chương trình hoàn chỉnh, chứ không phải là khái niệm tổng quát như ''subprogram" nữa.

Có sự khác nhau giữa function và procedure (thủ tục) rằng sự lý tưởng của function nên trả về một giá trị còn procedure thì không ( bây giờ điều này có thể thay đổi theo ngôn ngữ lập trình).

Với tất cả mọi điều hãy viết một "function" in ra chữ "hello" ở console.

Function không có tham số và không trả về bất cứ giá trị gì.  

function sayHello () {
  console.log("Hello !");
}

 

Function ở trên không có một tham số nào, và không trả về một giá tri.

 

Function ở trên có thể được gọi hoặc được dẫn bởi câu lệnh dưới đây:

 

sayHello();

 

Hiện tại, bạn thích thì có thể sử dụng dấu chấm phẩy (;) hoặc có thể chọn bỏ qua nó (chúng tôi không tham gia vào cuộc tranh luận về việc sử dụng dấu ";"  hoặc không dùng trong JavaScript).

 

Output của đoạn mã trên sẽ được in ra ở màn hình console như sau:

 

 

Nếu bạn sử dụng chuẩn es6/es2015 cùng một chức năng các bạn có thể sử dụng arrow function:

 

const sayHello = () => {
  console.log("Hello !");
}

 

Sử dụng arrow function là một cách viết ngắn gọn để viết một function. Nó được gọi tương tự như trên:

 

sayHello();

 

Một arrow function có cú pháp ngắn hơn cú pháp function bình thường, nó có thể không có đối số, super hoặc new.target của nó.

 

Những function này phù hợp nhất cho các non-method function và chúng không thể sử dụng như các constructor.

 

Có điều gì khi tôi nói, function ở trên không trả về giá trị gì?

 

Nếu tôi cố gắng để lưu trữ kết quả của function được gọi ở trên vào một biến  nó sẽ nhận giá trị "undefined".

 

Ví dụ:

 

let message = sayHello();
// The below console.log will return undefined as the function 
// doesn't return any value.
console.log (message);

Function có tham số nhưng không trả về giá trị nào

Hãy viết một function có một tham số truyền vào nhưng không trả về giá trị nào như ví dụ dưới đây:

 

function log (message) {
  console.log (message);
}

Function ở trên có một tham số có tên là message và câu lệnh in ra giá trị của message trên màn hình console.

 

Bạn có thể gọi function ở trên như sau:
 

// The below call to log() function, logs the output to the 
// and returns undefined.
log ("Hello JavaScript!");

 

Nếu function không return bất cứ một giá trị nào một cách rõ ràng, thì mặc định khi gọi hàm sẽ trả về "undefined"

 Function có một tham số và trả về một giá trị cụ thể

Chúng ta cùng viết một function có một tham số tên là number và trả về bình phương của số đó như sau:

 

function square(number) {
 return number * number;
}
console.log(square(2));

 

Kết quả thực thi function ở trên được hiện ra dưới đây:

 

Các function là first-class-objects

Trong khoa học máy tính, một ngôn ngữ lập trình hỗ trợ các function có dạng first-class objects,. Cụ thể , điều này có nghĩa là ngôn ngữ đó hỗ trợ việc xây dựng mới các function trong quá trình thực thi chương trình, lưu trữ chúng trong cấu trúc dữ liệu, truyền chúng như là đối số cho các function khác, và trả về chúng như là các giá trị của function khác - theo wikipedia

Các Function là first-class objects có thể được gán cho một biến cũng có thể được truyền như một tham số. Chúng ta sẽ thấy điều này qua một ví dụ:

 

// Bạn có thể sử dụng từ khóa var hoặc let.  Tôi sử dụng const để cho biết 
// rằng đây là function không thể khai báo lại
const square = function (number) {
  return number * number;
}
console.log(square(2));

 

Viết lại function phía trên sử dụng arrow function.

 

const square = (number) => {
  return number * number;
}
console.log(square(2)); // Outputs: 4

 Function có thể có nhiều đối số (thực tế có thể có 'n' đối số)

Trên lý thuyết không có giới hạn đối số, nhưng thực tế thì có.

 

Tham khảo tài liệu của stack overflow theo link dưới đây để biết thêm một số thông tin

 Stackoverflow: javascript functions, maximum no. of arguments

Làm thế nào để viết một function có thể truyền vào 'n' đối số?

Viết một function có tên là sum() có thể truyền 'n' đối số và trả về tổng của các đối đã truyền đó

 

// Cách cũ
const sum = function () {
  let result = 0;
  for(let i = 0; i < arguments.length; i++) {
    result += arguments[i];
  }
  return result;
}

 

Chúng ta có thể gọi function ở trên bằng các cách dưới đây:

 

console.log(sum(1,2));
console.log(sum(1,2,3,4));
console.log(sum(1,3,5,7,9));

Nó hoạt động như thế nào?

Nếu bạn nhìn vào function sum, nó không cần một tham số rõ ràng nào. Bây giờ, hãy tưởng tượng bạn đang thực hiện function sum() này một cách rõ ràng, điều gì là khó khăn để xác định các tham số trước đó. Nếu bạn không biết hãy nhìn các kết quả khi gọi function sum().

 

Nếu truyền vào 1 tham số tương ứng như sum(1) nó sẽ trả về 1.

 

Nếu truyền vào 2 tham số tương ứng như sum(1,2) nó sẽ trả về 3.

 

Nếu truyền vào 100 tham số tương ứng như sum(1,2,3,4..,99,100) nó sẽ trả về 5050.

 

Vì vậy, JavaScript đã cung cấp một object bí mật là "arguments", nó chứa toàn bộ tham số và có thể sử dụng trong bất kỳ function nào.

 

Lưu ý, object "arguments" không phải là một Array mà là một Array like object. Có nghĩa là bạn không thể gọi bất kỳ phương thức áp dụng cho mảng trên đối tượng arguments (Nếu bạn tò mò, hãy nghiên cứu thêm về vấn đề này).

 

Hoạt động của function sum() được minh họa dưới đây.

 

 

Khi tôi nó rằng "arguments" là một array like object, hãy xem điều này ở hình dưới đây qua một lời gọi hàm. Quan sát console.log của arguments. Bạn thấy gì?

 

 

Hình ảnh ở trên minh họa rất rõ ràng rằng các đối số là một đối tượng tương ứng với các key là các value là các tham số được truyền vào.

 

Object này cũng giống như bất kỳ các đối tượng khác, ví dụ:

 

{
   name:  "Rajesh",
   hobbies: ["writing","programming"]
}

Các key ở đối tượng argument giống như index của một mảng

{
   0:  "rajesh",
   1:  ["writing","programming"]
}

cả hai đều là đối tượng

Chú ý: JavaScript hiện đại không khuyến khích sử dụng đối tượng "arguments" . Chúng ta sử dụng một khái niệm mới gọi là REST parameters

 

Hãy xem cách sử dụng REST parameters để đạt được kết quả như trên mà không cần sử dụng đối tượng arguments:

 

// Cách mới sử dụng REST parameter
const sum = function (...args) {
  let result = 0;
  for(let i = 0; i < args.length; i++) {
    result += args[i];
  }
  return result;
}

 

 Trong cả hai function, mọi thứ đều giống nhau ngoại trừ đối tượng "arguments" được thay thế bằng "REST parameters"(...args). Bây giờ bạn có thể gọi nó là bất cứ thứ gì, như một quy ước ở đây tôi gọi nó là "args".

...args làm cái gì?

...args lấy mọi tham số truyền vào cho function và làm cho nó tồn tại dưới một mảng. Hãy nhớ rằng arguments là một đối tượng (array like object) còn ...args là một mảng.

 

Chúng ta tiếp tục lấy ví dụ, ở đây chúng ta gọi hàm sum() một lần nữa và trong hàm sum() chúng ta đặt một câu lệnh console.log như đoạn code dưới đây:

 

const sum = function (...args) {
  console.log(args);
  let result = 0;
  result = args.reduce((current, prev) => {
    return current + prev;
  });
  return result;
}
sum(1,2,3,4,5);

 

Output khi gọi hàm sum() cùng với console.log được hiển thị bên dưới

 

 

Trong đoạn log ở trên vì args là một mảng, và vì là một mảng nên chúng ta có thể sử dụng phương thức reduce để tính tổng.

...args cũng sử dụng được một phần trong nhiều tham số truyền vào

 Bây giờ chúng ta sẽ gọi hàm sum() với 3 tham số sum(1,2,3) và output nhận được là:

 

Function lấy function như một tham số

Như chúng ta đã đề cập trước đó function là first-class object do đó chúng có thể được truyền như là tham số/ đối số trong một hàm.

 

Chúng ta sẽ viết một function truyền vào đó một tham số là function

 

function dispatch (fn) {
  fn();
}

 

Trong đoạn mã phía trên, chúng ta định nghĩa một hàm tên là dispatch để nhận một hàm làm tham số truyền vào. Lưu ý rằng tên "fn" chỉ là một quy ước, bạn có thể sử dụng nhiều tên khác. Tên thông dụng khác thường là callback nhưng được sử dụng trong một ngữ cảnh khác.

NOTE: chúng tôi sử dụng thuật ngữ "callback" khi chúng tôi đề cập đến những function có tham số là function

Bây giờ, làm thế nào để sử dụng các function trên?

 

Hãy gọi hàm ở trên theo ví dụ dưới đây. Bạn có thể sử dụng cú pháp function bình thường hoặc dùng arrow function, ở đây tôi sẽ dùng arrow function:

 

//Cách 1: khai báo một function để làm tham số.
var fn = () => { console.log( "Hello !"); }
// gọi function dispatch()
dispatch(fn);  // Outputs "Hello !"

//Cách 2:  Khai báo một anonymous function bên trong
dispatch (function () {
  console.log("Hello !");
});

//METHOD 3:  Định nghĩa một arrow function bên trong
dispatch (() => { console.log ("Hello !") });

 

 Chú ý: 3 cách trên đều tương tự nhau

 

Callback function có thể truyền tham số vào và trả về giá trị. Hãy xem ví dụ ở dưới đây.

 

function dispatch(fn) {    // Truyền vào một function như là tham số
  return fn("hello");   
}

 

Function dispatch ở trên có đối số là một function và trả về giá trị trả về từ hàm được truyền vào. Nó cũng được gọi là hàm truyền vào với đối số là một function.

 

Làm thế nào để gọi hàm này?

 

let result = dispatch(function (p1) {
  return `My message and ${p1}`;
});

Hãy nghĩ về điều này một chút.

Ứng dụng thực tế của callback function

Giả sử chúng ta thực hiện một số method sau khoảng 1 giây và không phải là ngay lập tức. Ở đây chúng ta có thể sử dụng ham setTimeout.

 

setTimeout(function () {
  console.log('kiểm tra trạng thái của vài server');
}, 1000);

 

Phương thức trên sẽ đợi tối thiểu 1 giây trước khi thực thi. Chú ý rằng thời gian ở setTimeout và setInterval tính theo mili giây.

 

Nếu bạn muốn thực hiện mỗi method sau mỗi khoảng thời gian là 5 giây, bạn có thể dùng hàm setInterval.

 

setInterval(()=> {
   console.log("Mỗi 5s sẽ in ra nội dung này");
}, 5000);

 

Trong các hàm này bạn có thể viết bất kỳ đoạn mã nào, có thể thực hiện một số cuộc goi Ajax.

Chú ý: Tôi đang sử dụng cả hai cách viết là function bình thường và arrow function để kết thúc bài đọc, họ cảm thấy thoải mái với hai cách viết này

Function có thể gọi chính nó (giống như đệ quy) 

Đệ quy là một khái niệm thú vị trong đó một hàm gọi chính nó. Bây giờ nếu bạn không xử lý điều kiện dừng đệ quy, function có thể chạy vô hạn và cuối cùng trình duyệt có thể ném ra ngoại lệ tràn call stack như 'Maximum call stack size exceeded'

 

Hãy xem một ví dụ:

 

function runForEver() {
    runForEver();
}
// Bạn có thể gọi hàm trên bằng dòng lệnh ở dưới
runForEver();   // Bạn sẽ nhận được lỗi về call stack

Hàm trên là một biểu diễn đơn giản của hàm đệ quy. Như tên function cho thấy, chức năng này sẽ chạy mãi mãi, cho đến khi lỗi trên được trình duyệt đưa ra.

 

OK, đó là một function khá vô dụng.

 

Bây giờ hãy viết một hàm hữu ích hơn:

 

function countDown(n) {
  console.log (n);
  if (n >= 1) {  //Điều kiện để kết thúc
    countDown(n-1);
  }
}

 

Bạn có thể chạy function ở trên nhờ câu lệnh dưới đây.

 

countDown(5);  // -> Output sẽ là 5, 4, 3, 2, 1

 

Function đệ quy hoạt động như thế nào?

 

 

Từ sơ đồ trên, rất rõ ràng các cuộc gọi đệ quy tạo ra một ngăn xếp như thế nào và trong trường hợp chúng ta quên đặt điều kiện để kết thúc thì stack sẽ phát triển vô hạn và cuối cùng bạn sẽ thấy lỗi “Maximum call stack size exceeded”.

 

Hãy để chúng tôi xây dựng một ví dụ thực tế hơn. Giả sử bạn có cấu trúc dữ liệu bên dưới.

 

let data = [
  {
    title: "menu 1",
    children :[
      { title: "menu 1.1"},
      { 
        title: "menu 1.2",
        children: [
          {title: "menu 1.2.1"},
          {title: "menu 1.2.2"},          
        ]
      },
    ]
  },
  {
    title: "menu 2",
    children :[
      { title: "menu 2.1"},
      { title: "menu 2.2"},
    ]
  } 
]

 

Ở trên, chúng tôi có một cấu trúc phân cấp, có thể đại diện cho một menu hoặc bất cứ điều gì bạn muốn.

 

Chúng tôi muốn lấy điều này làm đầu vào và tạo danh sách không theo thứ tự với cấu trúc phân cấp chính xác.

 

 

 

Hãy viết code cho function đệ quy của chúng ta để biến đổi dữ liệu thành các cặp <ul> ở trên.

 

Đầu tiên, hãy xem chúng ta định sử dụng function như thế nào.

 

let uls = buildTree(data);
// Output data to console
console.log(uls);
// Render on the window
// Only for demo. In real case append it to any parent element
// instead of using document.write
document.write(uls);  

 

Bây giờ chúng ta hãy thực hiện function buildTree.

 

// Chấp nhận 2 đối số
// data-> data cần chuyển đổi
// isChild -> mặc định false, được sử dụng để cho biết liệu nút được render là phần tử con hay không

function buildTree(data, isChild = false) {
  let html = '<ul>';   // khởi tạo biến html
  // chạy vòng lặp for qua tất cả data
  data.forEach((d) => {
    // Mỗi phần tử data render ra một thẻ <li>
    html += `<li>${d.title}</li>`;
    // Nếu phần tử hiện tại của data có phần tử con thì hãy gọi buildTree một lần nữa qua các thẻ children và isChild =true
    
    if (d.children) {
      html += buildTree(d.children, true);
    }
  });
  // tạo thẻ đóng </ul>
  html += '</ul>';
  return html;  //trả về chuỗi html
}

Còn tiếp...

Bài viết được dịch từ codeburst.io

Đăng ký thực tập Web front-end tại:  http://bit.ly/2GTgkky

Bình luận

avatar
Bùi Hiên 2020-01-02 11:50:43.799157 +0000 UTC
Dễ hiểu
Avatar
* Vui lòng trước khi bình luận.
Ảnh đại diện
  +2 Thích
+2