Tìm hiểu Functions trong JavaScript

Javascript căn bản | by Học Javascript

Trong lập trình JavaScript, function (hàm) là một thành phần quan trọng giúp tổ chức và tái sử dụng mã lệnh một cách hiệu quả. Hàm cho phép chúng ta đóng gói một khối mã thực thi và gọi lại nó nhiều lần, giúp giảm thiểu sự lặp lại, cải thiện khả năng bảo trì và nâng cao hiệu suất của chương trình.

Việc hiểu rõ cách khai báo, sử dụng và tối ưu hóa hàm sẽ giúp lập trình viên viết code chuyên nghiệp hơn. Trong bài viết này, mình sẽ tìm hiểu về các cách khai báo hàm trong JavaScript, phạm vi hoạt động của biến trong hàm, cũng như các khái niệm nâng cao như callback function, higher-order function và ứng dụng thực tế của hàm trong lập trình.

Function trong JavaScript là gì?

Định nghĩa Function

Trong JavaScript, function (hàm) là một khối mã được thiết kế để thực hiện một nhiệm vụ cụ thể. Một function có thể nhận tham số đầu vào (parameters), thực thi các câu lệnh bên trong, và trả về một giá trị (return value).

Đặc điểm của function trong JavaScript:

  • Có thể tái sử dụng: Một function có thể được gọi nhiều lần mà không cần viết lại code.
  • Có thể nhận tham số: Function có thể nhận một hoặc nhiều tham số để xử lý dữ liệu.
  • Có thể trả về giá trị: Một function có thể trả về kết quả thông qua return.

Ví dụ về một function đơn giản:

function xinChao() {
    console.log("Xin chào, JavaScript!");
}
xinChao(); // Output: Xin chào, JavaScript!

Cấu trúc cơ bản của một Function

Một function trong JavaScript thường có cấu trúc như sau:

function tenFunction(thamSo1, thamSo2, ...) {
    // Khối lệnh được thực thi
    return giaTriTraVe; // (tùy chọn)
}

Các thành phần chính của function:

  • function: Từ khóa khai báo một function.
  • tenFunction: Tên của function, dùng để gọi lại khi cần thiết.
  • thamSo1, thamSo2, ...: Các tham số (parameters) được truyền vào function (có thể có hoặc không).
  • return: Từ khóa trả về giá trị (có thể có hoặc không). Nếu không có return, function sẽ mặc định trả về undefined.

Ví dụ minh họa function có tham số và trả về kết quả:

function tinhTong(a, b) {
    return a + b;
}
console.log(tinhTong(5, 10)); // Output: 15

Ở đây, function tinhTong(a, b) nhận hai tham số ab, thực hiện phép cộng và trả về kết quả.

Cách khai báo Function trong JavaScript

Trong JavaScript, có nhiều cách để khai báo một function, mỗi cách có đặc điểm riêng và được sử dụng trong những tình huống khác nhau. Dưới đây là các cách khai báo function phổ biến:

Function Declaration (Hàm khai báo)

Cú pháp:

function tenHam(thamSo1, thamSo2) {
    // Khối lệnh
    return giaTri;
}

Ví dụ minh họa:

function tinhTong(a, b) {
    return a + b;
}

console.log(tinhTong(5, 7)); // Output: 12

Đặc điểm của Function Declaration

  • Có hoisting: Có thể gọi function trước khi khai báo.
  • Dễ đọc, dễ hiểu, phù hợp cho các chương trình lớn.

Hoisting trong Function Declaration

Trong JavaScript, hoisting là cơ chế đưa khai báo function lên đầu phạm vi (scope), vì vậy bạn có thể gọi function trước khi khai báo.

Ví dụ:

console.log(chaoHoi("Nam")); // Output: Xin chào, Nam!

function chaoHoi(ten) {
    return `Xin chào, ${ten}!`;
}

Kết quả :

Lưu ý: Vì function được hoisting nên đoạn code trên vẫn hoạt động bình thường.

Function Expression (Hàm biểu thức)

Cú pháp

const tenHam = function(thamSo1, thamSo2) {
    // Khối lệnh
    return giaTri;
};
Ví dụ minh họa:
const tinhHieu = function(a, b) {
    return a - b;
};

console.log(tinhHieu(10, 4)); // Output: 6

Sự khác biệt giữa Function Declaration và Function Expression

Đặc điểm Function Declaration Function Expression
Hoisting Có hoisting, có thể gọi trước khi khai báo Không có hoisting, phải khai báo trước khi gọi
Gán vào biến Không gán vào biến Gán vào biến, có thể làm đối số
Dùng trong Callback Ít phổ biến Thường dùng trong callback, event handler

Ví dụ về sự khác biệt:

chao(); // Hoạt động bình thường (hoisting)
function chao() {
    console.log("Xin chào!");
}

chao2(); //  Lỗi: Cannot access 'chao2' before initialization
const chao2 = function() {
    console.log("Chào bạn!");
};

Kết quả :

Lưu ý: Function Expression không bị hoisting, nên nếu gọi trước khi khai báo sẽ bị lỗi.

Arrow Function (Hàm mũi tên - ES6)

Cú pháp:

const tenHam = (thamSo1, thamSo2) => {
    return giaTri;
};

Hoặc khi có một biểu thức duy nhất, có thể viết ngắn gọn hơn:

const tenHam = (thamSo1, thamSo2) => giaTri;

Ví dụ minh họa:

const tinhTich = (a, b) => a * b;
console.log(tinhTich(3, 5)); // Output: 15

Ví dụ với một tham số:

const binhPhuong = x => x * x;
console.log(binhPhuong(4)); // Output: 16

Ví dụ không có tham số:

const chao = () => "Xin chào!";
console.log(chao()); // Output: Xin chào!

Hạn chế của Arrow Function

  • Không có arguments (không thể dùng arguments như function bình thường).
  • Không thể dùng làm constructor (new).
  • Không thể sử dụng this theo cách thông thường.

Ví dụ về vấn đề this trong Arrow Function:

const person = {
    name: "Nam",
    sayHello: () => {
        console.log(`Xin chào, tôi là ${this.name}`); // this không trỏ đến person
    }
};

person.sayHello(); // Output: Xin chào, tôi là undefined

Lưu ý: this trong Arrow Function không trỏ đến đối tượng gọi nó, nên trong trường hợp trên, this.name bị undefined.

Immediately Invoked Function Expression (IIFE - Hàm tự thực thi ngay lập tức)

Cú pháp:

(function() {
    console.log("Hàm này chạy ngay lập tức!");
})();
Hoặc với Arrow Function:
(() => {
    console.log("Arrow Function IIFE!");
})();
Ví dụ minh họa:
(function(ten) {
    console.log(`Xin chào, ${ten}!`);
})("Dũng"); // Output: Xin chào, Dũng!
(function() {
    let count = 0;
    console.log(`Biến count trong IIFE: ${count}`);
})();

console.log(typeof count); // Output: undefined (count không tồn tại bên ngoài IIFE)

Lưu ý: IIFE thường được dùng khi cần chạy một đoạn mã một lần duy nhất và không muốn làm ảnh hưởng đến biến toàn cục.

So sánh tổng hợp các cách khai báo function

Loại Function Hoisting Cú pháp ngắn this riêng Thích hợp cho
Function Declaration Không Chương trình lớn, cần hoisting
Function Expression Không Không Callback, event handlers
Arrow Function Không Không Callback, xử lý mảng
IIFE Không Không Chạy code ngay lập tức, tránh xung đột biến

Tham số và giá trị trả về của Function trong JavaScript

Khi làm việc với function trong JavaScript, chúng ta có thể truyền tham số (parameters) vào function để xử lý dữ liệu, đồng thời function có thể trả về giá trị (return value) để sử dụng sau này.

Tham số trong function

Định nghĩa và ví dụ

Tham số là các giá trị được truyền vào function để function có thể xử lý.

function chaoHoi(ten) {
    console.log(`Xin chào, ${ten}!`);
}

chaoHoi("Nam"); // Output: Xin chào, Nam!

Ở đây, ten là tham số của function chaoHoi, khi gọi function với giá trị "Nam", biến ten sẽ nhận giá trị đó.

Tham số mặc định (Default Parameters)

Trong JavaScript, chúng ta có thể đặt giá trị mặc định cho tham số nếu không có giá trị nào được truyền vào.

function chaoHoi(ten = "bạn") {
    console.log(`Xin chào, ${ten}!`);
}

chaoHoi("Mai"); // Output: Xin chào, Mai!
chaoHoi();      // Output: Xin chào, bạn!

Kết quả :

Lưu ý: Nếu không truyền giá trị nào vào ten, nó sẽ nhận giá trị mặc định "bạn".

Rest Parameters (...args) và ứng dụng

Rest Parameters (...args) cho phép function nhận một số lượng tham số không xác định dưới dạng mảng.

function tong(...so) {
    return so.reduce((total, num) => total + num, 0);
}

console.log(tong(1, 2, 3, 4, 5)); // Output: 15
console.log(tong(10, 20));        // Output: 30

Ứng dụng:

  • Rest Parameters hữu ích khi không biết trước số lượng tham số.
  • Dùng nhiều trong các hàm xử lý mảng (Math.max(), Math.min(), v.v.).

Ví dụ về Rest Parameters với tham số khác:

function hienThiThongTin(hoTen, ...soThich) {
    console.log(`Họ tên: ${hoTen}`);
    console.log(`Sở thích: ${soThich.join(", ")}`);
}

hienThiThongTin("Hải", "Đọc sách", "Chơi game", "Du lịch");
// Output:
// Họ tên: Hải
// Sở thích: Đọc sách, Chơi game, Du lịch

Kết quả :

Lưu ý: ...args phải luôn là tham số cuối cùng trong danh sách tham số.

Cách sử dụng return để trả về giá trị từ function

Hàm có thể trả về một giá trị để tiếp tục sử dụng ở nơi khác.

function tinhTong(a, b) {
    return a + b;
}

let ketQua = tinhTong(5, 7);
console.log(ketQua); // Output: 12

Lưu ý: return ngay lập tức kết thúc function, các dòng sau return sẽ không được thực thi.

Ví dụ minh họa:

function test() {
    return "Kết thúc";
    console.log("Dòng này sẽ không chạy");
}

console.log(test()); // Output: Kết thúc

Function không có giá trị trả về (Void Function)

Một function không có return hoặc chỉ có console.log() được gọi là void function.

Ví dụ function không có return:

function xinChao(ten) {
    console.log(`Xin chào, ${ten}!`);
}

xinChao("Linh"); // Output: Xin chào, Linh!

Lưu ý: Nếu function không có return, khi gọi nó sẽ trả về undefined.

Ví dụ minh họa:

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

let kq = demo();
console.log(kq); // Output: undefined

Phạm Vi Biến và Function Scope trong JavaScript

Khi làm việc với biến trong JavaScript, một yếu tố quan trọng cần hiểu là phạm vi (scope) của biến. Phạm vi xác định nơi biến có thể được truy cập và sử dụng trong code.

Biến toàn cục (Global) và biến cục bộ (Local) trong JavaScript

Biến toàn cục (Global Variables)

  • Biến được khai báo bên ngoài bất kỳ function nào.
  • Có thể truy cập ở mọi nơi trong chương trình.
let globalVar = "Tôi là biến toàn cục";

function sayHello() {
    console.log(globalVar); 
}

sayHello(); // Output: Tôi là biến toàn cục
console.log(globalVar); // Output: Tôi là biến toàn cục

Kết quả:

Lưu ý: Dùng quá nhiều biến toàn cục có thể gây xung đột tên biến, làm code khó bảo trì.

Biến cục bộ (Local Variables)

  • Biến được khai báo bên trong một function.
  • Chỉ có thể truy cập bên trong function đó.
function testFunction() {
    let localVar = "Tôi là biến cục bộ";
    console.log(localVar); 
}

testFunction(); // Output: Tôi là biến cục bộ
console.log(localVar); // Error: localVar is not defined

Lưu ý: Biến localVar chỉ tồn tại trong function testFunction, gọi nó bên ngoài sẽ gây lỗi.

Function Scope và Block Scope trong JavaScript

Function Scope (Phạm vi function)

  • Trong JavaScript, biến khai báo bằng var chỉ có phạm vi trong function chứa nó.

Ví dụ về Function Scope:

function testScope() {
    var x = 10;
    if (true) {
        var y = 20;
    }
    console.log(x); // Output: 10
    console.log(y); // Output: 20 (vẫn truy cập được)
}

testScope();
console.log(x); // Error: x is not defined

Lưu ý: var không có block scope, nghĩa là biến y vẫn có thể truy cập bên ngoài if.

Block Scope (Phạm vi khối lệnh) với let và const

  • Biến khai báo bằng letconst chỉ có hiệu lực trong block chứa nó ({}).

Ví dụ về Block Scope:

function testBlockScope() {
    let a = 10;
    if (true) {
        let b = 20;
        console.log(a); // Output: 10
        console.log(b); // Output: 20
    }
    console.log(a); // Output: 10
    console.log(b); // Error: b is not defined
}

testBlockScope();

Lưu ý: Biến b được khai báo bằng let chỉ tồn tại trong block if.

Closure trong JavaScript

Closure là gì?

Closure là function có thể ghi nhớ và truy cập các biến từ phạm vi bên ngoài của nó, ngay cả khi function đó được gọi sau này.

Ứng dụng của Closure

Tạo biến private trong JavaScript
Viết hàm callback, hàm factory
Giữ trạng thái trong function

function outerFunction() {
    let count = 0; // Biến cục bộ

    return function innerFunction() {
        count++; 
        console.log(`Count: ${count}`);
    };
}

const counter = outerFunction(); // Tạo một closure
counter(); // Output: Count: 1
counter(); // Output: Count: 2
counter(); // Output: Count: 3

Giải thích:

  • outerFunction() trả về innerFunction(), nhưng innerFunction() vẫn nhớ biến countouterFunction() đã kết thúc.

Ứng dụng thực tế của Closure

Đếm số lần click nút (Counter)

function createCounter() {
    let count = 0;
    return function() {
        count++;
        console.log(`Bạn đã nhấn ${count} lần`);
    };
}

const clickCounter = createCounter();
document.getElementById("myButton").addEventListener("click", clickCounter);

Mỗi lần click vào nút, biến count vẫn được nhớ nhờ closure.

Tạo Factory Function (Hàm tạo đối tượng động)

function multiplier(factor) {
    return function(number) {
        return number * factor;
    };
}

const double = multiplier(2);
const triple = multiplier(3);

console.log(double(5)); // Output: 10
console.log(triple(5)); // Output: 15

Closure giúp tạo ra các function chuyên biệt (double, triple).

Function Callback và Higher-Order Functions trong JavaScript

Function Callback

Định nghĩa callback function

Callback function là một function được truyền vào một function khác như một đối số, và được thực thi sau khi function đó hoàn thành tác vụ.

JavaScript là ngôn ngữ bất đồng bộ (asynchronous), nên callback được sử dụng rộng rãi trong Xử lý sự kiện (Event Handling), Xử lý bất đồng bộ (Async Processing), Lập trình hàm (Functional Programming), v.v.

Ví dụ sử dụng callback trong JavaScript

Callback trong hàm thông thường

function greet(name, callback) {
    console.log(`Xin chào, ${name}!`);
    callback(); 
}

function afterGreeting() {
    console.log("Chúc bạn một ngày tốt lành!");
}

greet("Kiên", afterGreeting);
function greet(name, callback) {
    console.log(`Xin chào, ${name}!`);
    callback(); 
}

function afterGreeting() {
    console.log("Chúc bạn một ngày tốt lành!");
}

greet("Kiên", afterGreeting);

Kết quả:

Xin chào, Kiên!
Chúc bạn một ngày tốt lành!

afterGreeting() được truyền vào greet() và được gọi sau khi lời chào được in ra.


Callback trong setTimeout() (Xử lý bất đồng bộ)

console.log("Bắt đầu...");
setTimeout(function() {
    console.log("Đã chờ 2 giây...");
}, 2000);
console.log("Hoàn thành!");

Kết quả (thực thi bất đồng bộ):

Bắt đầu...
Hoàn thành!
(Chờ 2 giây...)
Đã chờ 2 giây...

setTimeout() nhận một callback function và thực thi nó sau 2 giây.


Callback trong xử lý sự kiện (Event Handling)

document.getElementById("myButton").addEventListener("click", function() {
    console.log("Nút đã được nhấn!");
});

Callback giúp xử lý sự kiện khi người dùng nhấn nút.

Kết bài

Functions đóng vai trò quan trọng trong JavaScript, giúp tổ chức code một cách khoa học, tối ưu hóa hiệu suất và tăng tính tái sử dụng. Từ các cách khai báo function như Function Declaration, Function Expression, Arrow Function đến những khái niệm nâng cao như Callback, Higher-Order Functions, Scope, Closure, mỗi tính năng đều giúp lập trình viên viết code hiệu quả hơn.

Ngoài ra, các cải tiến trong ES6+ như Default Parameters, Rest Parameters, Destructuring giúp việc xử lý dữ liệu linh hoạt hơn, giảm thiểu lỗi và làm cho code dễ đọc, dễ bảo trì.

Việc nắm vững function trong JavaScript không chỉ giúp bạn xây dựng các ứng dụng web mạnh mẽ mà còn là nền tảng quan trọng để tiếp cận các khái niệm nâng cao như Asynchronous JavaScript (Promises, Async/Await) và lập trình hướng đối tượng trong JavaScript.

Bài viết liên quan

  • 2