Function trong JavaScript

bởi Phạm Công Thành - 2 tháng trước

Giống như nhiều ngôn ngữ lập trình khác, Function (hàm) trong JavaScript được sử dụng rất nhiều trong quá trình code. Nó là tập hợp nhiều câu lệnh để thực hiện một tác vụ (hoặc tính toán giá trị nào đó) được lặp lại một hay nhiều lần trong chương trình.

Việc sử dụng Function giúp cho code ngắn gọn hơn (một vài trường hợp có thể dài hơn), sạch hơn, dễ debug và quản lý hơn đặc biệt đối với một đoạn code được lặp đi lặp lại nhiều lần thì Function rất hữu dụng.

Khai báo Function trong JavaScript

Trong JavaScript để khai báo hàm chúng ta dùng từ khóa function và theo sau nó bao gồm:

  • Tên của funtion.
  • Danh sác các tham số (parameter) truyền vào function, chúng được đặt trong dấu ngoặc đơn và cách nhau bởi dấu phẩy.
  • Các câu lệnh JavaScript được đặt trong dấu ngoặc nhọn {...} (Function Scope).
function square(number) {
  return number * number;
}
value_return = square(2)

Hàm square nhận 1 tham số, có tên là number. Hàm này bao gồm một câu lệnh mà nó sẽ trả về tham số number nhân với chính nó.

Hàm có return là hàm có sử dụng từ khóa return để đặt ở cuối hàm với mục đích trả kết quả về để sử dụng tiếp ở những đoạn code bên ngoài. Trong trường hợp value_return = square(2) thì value_return = 4.

Hàm không có return là hàm không có sử dụng từ khóa return đặt trong hàm và nó không trả về kết quả nào khi bạn gọi hàm bằng một giá trị nào đó. Ví dụ:

function print_text (text) {
    console.log(text);
}

Khi truyền một tham số có giá trị vào hàm, nếu hàm thay đổi giá trị của tham số đó, nó sẽ không thay đổi giá trị của tham số được truyền vào ở phạm vi bên ngoài hàm.

Nếu truyền vào hàm một tham số là object , ví dụ như một mảng Array hoặc một object được user tự định nghĩa, và bên trong hàm thay đổi các thuộc tính của object, thay đổi đó sẽ có hiệu lực ngay cả ở phạm vi bên ngoài của hàm, giống như ví dụ dưới đây:

function changeColor(Object_Car, model) {
  Object_Car.color = "black";
  model = 'Civic';
}

var mycar = {manufacturer: "Honda", model: "City", color: 'white'};
var color_now, color_change;

color_now = mycar.color;    // color_now nhận giá trị "white"
model_now = mycar.model;    // model_now nhận giá trị "City"

changeColor(mycar, model_now);
model_now;                  // model_now không bị thay đổi giá trị
color_change = mycar.color; // color_change nhận giá trị "black"

// (thuộc tính 'color' đã bị thay đổi bởi hàm changeColor)

Biểu thức hàm (hàm trong biến)

Các hàm cũng có được khởi tạo bằng một biểu thức hàm (function expression). Một hàm như vậy có thể nặc danh, nó không cần phải có tên, nó được gán trực tiếp vào biến. Ví dụ, hàm square có thể được khai báo như sau:

const square = function(number) { 
    return number * number 
}; 
// square lúc này là một hằng giúp ẩn danh cho hàm gán cho nó

var x = square(4) // x nhận giá trị 16

Chúng ta vẫn có thể đặt tên cho một biểu thức hàm, việc đặt tên cho phép hàm có thể chạy chính nó bên trong function scope của nó.

const fibonacci = function fibonacci_numbers(n) { 
    if (n <= 2) {
        return 1;
    } else {
        return fibonacci_numbers(n-1) + fibonacci_numbers(n-2);
    }
};
console.log(fibonacci(4));

Việc sử dụng biểu thức hàm cũng giúp chúng ta định nghĩa được hàm dựa trên một điều kiện nhất định. Ví dụ, hàm changeColor được định nghĩa chỉ khi cost nhỏ hơn 2000.

var changeColor;
if (cost < 2000){
    changeColor = function(Car) {
        Car.color = "Black"
    }
}

Gọi hàm

Việc định nghĩa một hàm sẽ không thực thi nó, Định nghĩa một hàm đơn giản chỉ là đặt tên cho hàm và chỉ định những việc cụ thể sẽ làm khi hàm đó được gọi. Gọi hàm thực chất là thi hành các câu lệnh trong hàm với các tham số được truyền vào hàm thông qua các parameter.

function square(number) {
    return number * number;
}

square(2)
// Hàm square được gọi với tham số truyền vào là 2

Function Scope (Phạm vi hàm)

Phạm vi (scope) của một hàm là khoảng không gian bên trong hàm mà nó được khai báo (hoặc là cả chương trình nếu nó không nằm trong hàm nào khác).

function myFunction (prams) {
    // Function Scope (Phạm vi hàm)
}

Một biến được định nghĩa bên trong một hàm không thể truy suất được từ bên ngoài phạm vi của hàm, nó chỉ sử dụng được ở bên trong hàm mà nó được khai báo. Tuy nhiên, hàm có thể truy suất đến mọi biến và mọi hàm khác trong phạm vi chúng được khai báo.

Hiểu đơn giản, nếu một hàm được khai báo trong phạm vi global thì nó có thể truy suất đến các biến và các hàm khác được khai báo trong phạm vi global. Một hàm được khai báo bên trong phạm vi một hàm khác thì nó có thể truy suất đến các biến ở bên trong phạm vi hàm mà nó được khai báo.

// Các biến và hàm được định nghĩa trong phạm vi global
var number = 2;
function square() {
    return number * number;
}
square() // Trả về 4

// Hàm lồng nhau (hàm trong hàm)
function Caculate() {
   let number_1 = 5;
   let number_2 = 6;

   // Hàm được khai báo bên trong hàm
   function add() {
       return number_1 + number_2;
   }
   return add()
}

Caculate() // Trả về 11

Function Hoisting

Bạn còn nhớ về Variable Hoisting trong bài viết về Biến trong JavaScript không? Giống như biến, functiong cũng có thể hoisting. Tuy nhiên việc hoisting chỉ được áp dụng khi hàm được khai báo theo cách thức thông thường, việc khai báo bằng các biểu thức hàm hoisting không hoạt động.

square(4)     // Hàm square vẫn được thức thi khi được gọi trước khi khai báo hàm
square_exp(4) // ReferenceError: Cannot access 'square_exp' before initialization

function square (number) { 
    return number * number 
}; 
const square_exp = function(number) {
   return number * number 
}

Hàm đệ quy (recursive function)

Hàm đệ quy là hàm mà bên trong phạm vi của nó được gọi chính nó. Ví dụ về dãy Fibonacci như sau:

const fibonacci = function fibonacci_numbers(n) { 
    if (n <= 2) {
        return 1;
    } else {
        return fibonacci_numbers(n-1) + fibonacci_numbers(n-2);
    }
};
console.log(fibonacci(4));

Có 3 cách để một hàm có thể tham chiếu đến chính nó:

  1. Dùng tên của hàm
  2. Sử dụng arguments.callee.
  3. Một biến in-scope mà có tham chiếu đến hàm.
var foo = function bar() {
   // Các cách gọi sau là tương tự như nhau
    bar()
    arguments.callee()
    foo()
};

Trong một số cách hiểu, thì đệ quy (recursion) cũng tương tự như một vòng lặp. Cả hai đều là thực thi một đoạn code lặp đi lặp lại nhiều lần, và cả hai đều yêu cầu điều kiện xác định để chạy (để tránh lặp vô tận, hoặc recursion vô tận).

Bạn có thể chuyển đổi bất kỳ thuật toán đệ quy nào sang một dạng non-recursive bằng các vòng lặp, nhưng logic thường sẽ phức tạp hơn rất nhiều, và làm như vậy cũng đòi hỏi sử dụng một ngăn xếp (a stack).

Thực tế, khi đệ quy có sử dụng một ngăn xếp: gọi là ngăn xếp hàm (function stack). Cách thực thi dạng ngăn xếp này có thể được tìm thấy trong ví dụ dưới đây:

function foo(i) {
    if (i < 0) {
        return;
    }
    console.log('begin:' + i);
    foo(i - 1);
    console.log('end:' + i);
}
foo(3);

// Output:

// begin:3
// begin:2
// begin:1
// begin:0
// end:0
// end:1
// end:2
// end:3

Closures

JavaScript cho phép lồng các function vào nhau và cấp quyền cho function con, để function con có toàn quyền truy cập vào tất cả các biến và function được định nghĩa bên trong function cha (và tất cả biến và function mà function cha được cấp quyền truy cập đến).

Tuy nhiên, function cha không có quyền truy cập đến các biến và function được định nghĩa bên trong function con.

Bên cạnh đó, nếu function con có thời gian thực thi lâu hơn của function cha các biến và function được định nghĩa bên trong function cha sẽ vẫn tồn tại dù việc thực thi function cha đã kết thúc.

Một closure được tạo ra khi một function con bằng cách nào đó trở nên khả dụng với bất kỳ scope nào bên ngoài function cha.

var pet = function(name) {   // Function cha định nghĩa một biến tên là "name"
  var getName = function() {
    return name;             // Function con có quyền truy cập đến biến "name" của function cha
  }
  return getName;            // Trả về function con, theo đó làm function con không còn bị giới hạn bên trong function cha nữa
}
var myPet = pet("Gấu");
   
myPet();                     // Trả về "Gấu"

Các tham số của function

Kể từ ES6, xuất hiện 2 dạng tham số mới: default parameters và rest parameters.

Default parameters

Trong JavaScript các tham số của function được đặt mặc định là undefined, tuy nhiên trong một số trường hợp chúng ta có thể đặt giá trị mặc định cho các tham số ngay khi khai báo function và khi gọi hàm chúng ta không cần truyền tham số ấy nếu tham số trùng với giá trị mặc định. VD

function Car(color, model='Honda City') {
    return {
        car_model : function() {
            return model;
        },
        car_color : function() {
            return color;
        }
    }
}
myCar = Car('white');            // Chỉ truyền tham số 'color'
console.log(myCar.car_model());  // Honda City
console.log(myCar.car_color());  // white

Rest parameters

Rest parameters cho phép chúng ta truyền số lương các tham số tùy ý cho function. Tức là với hàm sử dụng rest parameters chúng ta có thể truyền vô hạn các tham số để hàm xử lý, toàn bộ các tham số này sẽ được coi là một mảng và các tham số sẽ là các phần tử trong mảng. Xem qua ví dụ sau (hàm forEach() bạn có thể tìm hiểu ở đây):

// Tính tổng các giá trị trong mảng
function total(...list) {
    let sum = 0;
    list.forEach((arg) => sum += arg);
    return sum;
}

total_num = total(1, 2, 3, 8, 9); // Có thể truyền tham số tùy ý
console.log(total_num);           // 23

Arrow functions

Kể từ ES6, arrow functions là một cú pháp mới dùng để viết các hàm trong JavaScript. Nó giúp tiết kiệm thời gian phát triển và đơn giản hóa phạm vi function (function scope).

Arrow function (fat arrow) là cú pháp được mượn từ CoffeeScript, cú pháp này là cách ngắn gọn hơn dùng để viết function. Ở đây sử dụng kí tự =>, trông giống như một mũi tên. Bằng cách sử dụng arrow function, chúng ta tránh được việc phải gõ từ khoá function, return và dấu ngoặc nhọn.

Arrow function là một hàm vô danh và nó thay đổi cách this bind đến function. Arrow function làm code của ta trông ngắn gọn hơn, giúp đơn giản hóa function scoping cũng như từ khóa this.

Cách sử dụng Arrow Function

Có nhiều cách để khai báo Arrow Function trong ES6, dưới đây là một vài ví dụ:

// Hàm không có tham số:
const log = () => console.log('Hello, World!!!');

// Hàm có một tham số:
var log = error => console.log(error); // error là tham số truyền vào


// Hàm có nhiều tham số:
const multiply = (x, y) => { 
    return x * y 
};
// Có thể được viết gọn như sau:
const multiply = (x, y) => x * y ;


// Arrow Function trả về object:
const person = (first_name, last_name) => ({
    'first_name' : first_name,
    'last_name' : last_name
})
// Function Scope đặt trong dấu ngoặc tròn.
console.log(person('Thành', 'Phạm')); // { first_name: 'Thành', last_name: 'Phạm' }

Trước khi có arrow functions, mọi function mới sẽ tự định nghĩa giá trị this của nó. Khi sử dụng arrow function this không bị ràng buộc bởi thứ gì cả, nó chỉ kế thừa từ phạm vi cha của nó.

Cú pháp arrrow function là chức năng khá hữu ích trong ES6, tuy nhiên ngoài những ưu điểm thì nó cũng có những hạn chế như việc nó khiến code của bạn tuy ngắn nhưng lại khó hiểu. Dưới đây là một vài lưu ý khi nào nên sử dụng arrrow function khi nào nên sử dụng function:

  • Sử dụng function trong Global scope, Object.prototype properties.
  • Sử dụng class cho object constructors.
  • Sử dụng Arrow Function trong các trường hợp còn lại.

Callback function

Callback function hay được gọi với tên khác là Higher-order Function, là một function được truyền vào một function khác dưới dạng tham số và được gọi trong function đó. Callback function được sử dụng rất nhiều trong NodeJs và nhiều ngôn ngữ, framework khác. Ví dụ:

function hello() {
    console.log('Hello, World!!!');
}

function startMessenger(hello_word){
    hello_word();
}

// Truyền function "hello" dưới dạng biến vào function "startMessenger"
// hello() chính là Callback function
startMessenger(hello);   // Hello, World!!!

Cách thức hoạt động

Ta có thể truyền function như là một biếnreturn nó trong một function khác. Khi ta truyền callback function như là một tham số tới một function khác, ta chỉ truyền định nghĩa. Nó sẽ được thực thi khi ta truyền cả function dưới dang tham số.

Và chúng ta đã có định nghĩa của function callback dưới dang tham số, ta có thể thực thi bất kì lúc nào được gọi trong function chứa nó.

Callback Function là closure, khi ta truyền callback function dưới dạng tham số tới một function khác, callback được thực thi trong function scope chứa nó, callback function có thể sử dụng các biến của function chứa nó.

Tác dụng chính của callback funtion

  • Để thực hiện các tác vụ bất đồng bộ.
  • Cho những tác vụ event listeners/handlers.
  • Viết nhiều đoạn code dễ đọc hơn.
  • Tránh lặp lại code (DRY — Do not repeat yourself).
  • Tăng khả năng bao trì code và hệ thống.
  • Hiểu được luồng xử lý của nhiều thừ viện JavaScript.

Bài trước

Bài tiếp

Bình luận

avatar