Gọi hàm (Function Invocation) trong JavaScript

Javascript nâng cao | by Học Javascript

Hàm (Function) là một trong những thành phần quan trọng nhất trong JavaScript, giúp tổ chức mã nguồn rõ ràng, tái sử dụng logic và tối ưu hóa hiệu suất chương trình. Tuy nhiên, để sử dụng hàm hiệu quả, chúng ta cần hiểu rõ cách gọi hàm (Function Invocation) trong JavaScript.

Việc gọi hàm không chỉ đơn giản là sử dụng dấu ngoặc () mà còn có nhiều cách khác nhau, chẳng hạn như gọi trực tiếp, gọi trong object, sử dụng call(), apply(), bind(), gọi bằng new, hay thực thi hàm ngay lập tức (IIFE). Hiểu rõ các phương thức gọi hàm sẽ giúp lập trình viên kiểm soát tốt hơn phạm vi this, tối ưu hiệu suất và tránh các lỗi không mong muốn trong quá trình phát triển ứng dụng.

Cách gọi hàm trong JavaScript

Trong JavaScript, một hàm (Function) chỉ thực sự thực thi khi nó được gọi (invoke). Việc gọi hàm có thể thực hiện theo nhiều cách khác nhau, tùy thuộc vào cách khai báo và bối cảnh sử dụng.

Hiểu rõ các cách gọi hàm giúp lập trình viên kiểm soát tốt hơn phạm vi hoạt động của hàm, tránh các lỗi liên quan đến this, đồng thời tối ưu hiệu suất của chương trình. Một số lỗi phổ biến khi gọi hàm có thể kể đến như:

  • Gọi hàm không đúng ngữ cảnh dẫn đến this bị undefined hoặc trỏ sai đối tượng.
  • Truyền sai số lượng tham số khi gọi hàm, gây lỗi hoặc kết quả không mong muốn.
  • Gọi hàm không đồng bộ trong môi trường bất đồng bộ như setTimeout(), setInterval(), Promise, Async/Await.

Do đó, việc hiểu rõ cách gọi hàm không chỉ giúp tránh lỗi mà còn nâng cao hiệu quả viết mã, làm cho chương trình dễ đọc, bảo trì và tối ưu hơn.

Gọi hàm theo cách thông thường (Regular Function Invocation)

Gọi hàm bằng cú pháp functionName()

Cách đơn giản nhất để gọi một hàm trong JavaScript là sử dụng cú pháp:

function sayHello() {
    console.log("Xin chào!");
}

// Gọi hàm
sayHello();

Khi thực thi, hàm sayHello() sẽ chạy và hiển thị "Xin chào!" lên console.

Gọi hàm với tham số functionName(arg1, arg2)

Hàm có thể nhận tham số khi được gọi, giúp nó hoạt động linh hoạt hơn.

function greet(name, age) {
    console.log(`Xin chào, tôi là ${name}, tôi ${age} tuổi.`);
}

// Gọi hàm với đối số
greet("Kiên", 25);

Hàm greet() nhận hai tham số nameage, sau đó sử dụng chúng để hiển thị một thông điệp cá nhân hóa.

Gọi hàm có giá trị trả về và không có giá trị trả về

  • Hàm có giá trị trả về sử dụng return để trả kết quả về nơi gọi.
  • Nếu không có return, hàm sẽ mặc định trả về undefined.

Ví dụ về hàm có giá trị trả về:

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

// Gọi hàm và gán kết quả vào biến
let result = sum(5, 10);
console.log(result); // Output: 15

Ví dụ về hàm không có giá trị trả về:

function showMessage() {
    console.log("Đây là một thông báo.");
}

// Gọi hàm
showMessage();

Hàm showMessage() không có return, nên nó chỉ thực thi câu lệnh console.log() mà không trả về giá trị nào.

Gọi hàm dưới dạng phương thức (Method Invocation)

Khi một hàm là thuộc tính của một object, nó được gọi là phương thức (method).

Hàm là một phương thức của một object

Một phương thức được gọi thông qua object bằng cú pháp object.methodName().

const person = {
    name: "Kiên",
    age: 25,
    sayHello: function() {
        console.log(`Xin chào, tôi là ${this.name}`);
    }
};

// Gọi phương thức của object
person.sayHello(); // Output: Xin chào, tôi là Kiên

Ở đây, sayHello() là một phương thức của object person.

Sử dụng this trong phương thức của object

Trong một phương thức, this đại diện cho object chứa phương thức đó.

const car = {
    brand: "Toyota",
    model: "Corolla",
    getInfo: function() {
        return `Xe: ${this.brand} ${this.model}`;
    }
};

console.log(car.getInfo()); // Output: Xe: Toyota Corolla

Hàm getInfo() sử dụng this để truy cập thuộc tính brandmodel của object car.

Ví dụ minh họa về cách gọi hàm trong object

const student = {
    name: "Dũng",
    age: 20,
    getDetails: function() {
        return `${this.name} đang học năm ${this.age}`;
    }
};

// Gọi phương thức
console.log(student.getDetails()); // Output: Dũng đang học năm 20

Trong ví dụ này, getDetails() sử dụng this.namethis.age để lấy thông tin của sinh viên.

Lưu ý:

  • Nếu gọi phương thức mà không thông qua object (const getDetails = student.getDetails; getDetails();), this sẽ không trỏ đến student nữa mà trở thành undefined (hoặc window trong môi trường không sử dụng "use strict").
  • Điều này có thể khắc phục bằng cách dùng .bind() hoặc arrow function (vì arrow function không có this riêng).

Gọi hàm với call(), apply(), và bind() trong Javascript

call(): Gọi hàm với một giá trị this và truyền tham số riêng lẻ

Phương thức call() cho phép gọi một hàm với một giá trị this cụ thể và truyền các tham số riêng lẻ.

Cú pháp:

function sayHello(greeting, punctuation) {
    console.log(`${greeting}, tôi là ${this.name}${punctuation}`);
}

const person = { name: "Kiên" };

// Gọi hàm bằng call()
sayHello.call(person, "Xin chào", "!"); // Output: Xin chào, tôi là Kiên!

call() giúp thiết lập this và truyền các tham số lần lượt.

apply(): Gọi hàm với một giá trị this và truyền tham số dạng mảng

Phương thức apply() hoạt động giống call(), nhưng thay vì truyền tham số riêng lẻ, ta truyền một mảng các tham số.

Cú pháp:

sayHello.apply(person, ["Chào bạn", "."]); // Output: Chào bạn, tôi là Kiên.

apply() hữu ích khi ta đã có sẵn dữ liệu dạng mảng cần truyền vào hàm.

bind(): Trả về một hàm mới với this được cố định

Phương thức bind() không gọi hàm ngay lập tức mà trả về một hàm mới với this được cố định.

Cú pháp:

const sayHelloBound = sayHello.bind(person, "Hello");
sayHelloBound("!!!"); // Output: Hello, tôi là Kiên!!!

bind() thích hợp khi ta muốn lưu trữ một hàm với this đã được cố định để gọi sau.

So sánh call(), apply(), và bind()

Phương thức Mô tả Cách truyền tham số Gọi ngay lập tức?
call() Gọi hàm với this cụ thể Truyền từng tham số riêng lẻ
apply() Gọi hàm với this cụ thể Truyền một mảng tham số
bind() Trả về một hàm mới với this cố định Truyền từng tham số riêng lẻ Không

Ví dụ thực tế:
Giả sử ta có một object mathOperations với một phương thức tính tổng.

const mathOperations = {
    number: 10,
    add: function (x, y) {
        return this.number + x + y;
    }
};

// Dùng call()
console.log(mathOperations.add.call({ number: 20 }, 5, 5)); // Output: 30

// Dùng apply()
console.log(mathOperations.add.apply({ number: 30 }, [5, 5])); // Output: 40

// Dùng bind()
const boundAdd = mathOperations.add.bind({ number: 50 }, 5);
console.log(boundAdd(5)); // Output: 60

Gọi hàm bằng toán tử new (Constructor Invocation) trong Javascript

Dùng new để tạo một object từ hàm constructor

Trong JavaScript, nếu gọi một hàm với toán tử new, nó sẽ hoạt động như một constructor function, tạo ra một object mới và gán this vào object đó.

Cú pháp:

function Person(name, age) {
    this.name = name;
    this.age = age;
}

// Tạo object mới bằng new
const person1 = new Person("Kiên", 25);
console.log(person1.name); // Output: Kiên
console.log(person1.age);  // Output: 25

Khi dùng new, this trong hàm constructor sẽ trỏ đến object mới được tạo.

this trong constructor function

  • Nếu một hàm constructor không trả về gì, mặc định nó trả về this, tức là chính object mới được tạo.
  • Nếu hàm constructor trả về một object khác, giá trị this sẽ bị ghi đè.

Ví dụ minh họa:

function User(name) {
    this.name = name;
    return { name: "Khác" }; // Trả về một object khác
}

const user1 = new User("Kiên");
console.log(user1.name); // Output: Khác

Nếu constructor trả về một object khác, object mới sẽ không được sử dụng.

So sánh giữa gọi hàm thông thường và gọi bằng new

Cách gọi Hoạt động this trỏ đến Kết quả trả về
Gọi thông thường Thực thi code trong hàm window (hoặc undefined trong strict mode) Kết quả từ return
Gọi bằng new Tạo object mới Object mới Object mới (hoặc giá trị được return nếu là object)

Ví dụ minh họa:

function testFunction() {
    console.log(this);
}

// Gọi thông thường
testFunction(); // Output: window (hoặc undefined trong strict mode)

// Gọi bằng new
const obj = new testFunction(); // Output: testFunction {}

Khi gọi bằng new, this không còn trỏ đến window, mà trỏ đến object mới được tạo.

Gọi hàm tự động (Immediately Invoked Function Expression - IIFE) trong Javascript

Khái niệm về IIFE

IIFE (Immediately Invoked Function Expression) là một hàm trong JavaScript được gọi ngay lập tức sau khi khai báo.

Cú pháp IIFE:

(function() {
    console.log("Hàm này được gọi ngay lập tức!");
})();

Khi trình duyệt đọc đoạn code trên, hàm sẽ tự động thực thi mà không cần gọi.

Lợi ích của IIFE trong việc tránh ô nhiễm phạm vi toàn cục

  • Tránh xung đột biến toàn cục: IIFE tạo một phạm vi riêng, giúp tránh ghi đè biến lên window.
  • Bảo mật dữ liệu: Các biến bên trong IIFE không thể bị truy cập từ bên ngoài.

Ví dụ minh họa:

(function() {
    var secret = "Đây là biến riêng tư";
    console.log(secret); // Output: Đây là biến riêng tư
})();

console.log(typeof secret); // Output: undefined (không thể truy cập biến secret từ bên ngoài)

IIFE giúp bảo vệ dữ liệu và tránh rác thải biến trong phạm vi toàn cục.

Gọi hàm trong hàm khác (Nested Function Invocation) trong Javascript

Hàm bên trong hàm: Closure và phạm vi của biến

Trong JavaScript, ta có thể định nghĩa một hàm bên trong một hàm khác. Khi một hàm con có thể truy cập biến của hàm cha ngay cả sau khi hàm cha đã thực thi, đó gọi là Closure.

Ví dụ về Closure:

function outerFunction(outerVariable) {
    return function innerFunction(innerVariable) {
        console.log(`Outer: ${outerVariable}, Inner: ${innerVariable}`);
    };
}

const newFunction = outerFunction("A");
newFunction("B"); // Output: Outer: A, Inner: B

Closure giúp duy trì trạng thái biến ngay cả khi hàm cha đã kết thúc.

Ví dụ minh họa về gọi hàm lồng nhau

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

greet("Kiên"); // Output: Xin chào, Kiên!

Hàm sayHello() có thể truy cập biến name của greet() nhờ vào phạm vi hàm lồng nhau.

Gọi hàm bất đồng bộ (Asynchronous Invocation) trong Javascript

JavaScript hỗ trợ lập trình bất đồng bộ để xử lý các tác vụ mất thời gian như setTimeout, setInterval, Promise và async/await.

Gọi hàm với setTimeout() và setInterval()

  • setTimeout(): Gọi hàm sau một khoảng thời gian.
  • setInterval(): Gọi hàm lặp đi lặp lại sau một khoảng thời gian cố định.

Ví dụ setTimeout():

setTimeout(() => {
    console.log("Chạy sau 2 giây!");
}, 2000);

Hàm chỉ thực thi một lần sau 2 giây.

Ví dụ setInterval():

let count = 0;
const intervalId = setInterval(() => {
    console.log(`Lặp lại lần ${++count}`);
    if (count === 3) clearInterval(intervalId);
}, 1000);

Hàm chỉ thực thi một lần sau 2 giây.

Ví dụ setInterval():

let count = 0;
const intervalId = setInterval(() => {
    console.log(`Lặp lại lần ${++count}`);
    if (count === 3) clearInterval(intervalId);
}, 1000);

Hàm sẽ lặp lại mỗi giây, sau 3 lần thì dừng.

Gọi hàm với Promise và async/await

Promise giúp xử lý các tác vụ bất đồng bộ dễ dàng hơn.

Ví dụ với Promise:

function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve("Dữ liệu đã tải"), 3000);
    });
}

fetchData().then(data => console.log(data)); // Output: Dữ liệu đã tải (sau 3 giây)

Promise giúp chờ dữ liệu mà không làm đứng chương trình.

Ví dụ với async/await:

async function getData() {
    console.log("Bắt đầu tải...");
    const data = await fetchData();
    console.log(data);
}

getData();
// Output:
// Bắt đầu tải...
// (sau 3 giây) Dữ liệu đã tải

async/await giúp code bất đồng bộ dễ đọc hơn, giống như code đồng bộ.

Kết bài

Việc hiểu rõ các cách gọi hàm trong JavaScript giúp lập trình viên tối ưu hiệu suất, tránh lỗi và viết code rõ ràng, dễ bảo trì.Gọi hàm thông thường và gọi hàm trong object là những cách cơ bản nhất để sử dụng hàm.Phương thức call(), apply(), bind() giúp kiểm soát giá trị this linh hoạt hơn.Toán tử new dùng để khởi tạo object từ constructor function.IIFE (Immediately Invoked Function Expression) giúp bảo vệ biến cục bộ và tránh xung đột.Hàm lồng nhau và Closure giúp giữ trạng thái của biến, tạo ra các mô-đun linh hoạt.Gọi hàm bất đồng bộ (setTimeout(), setInterval(), Promise, async/await) giúp quản lý các tác vụ chạy nền mà không làm đứng trình duyệt.Nắm vững cách gọi hàm trong JavaScript sẽ giúp bạn viết code chuyên nghiệp, tối ưu và hiệu quả hơn trong các dự án thực tế.

Bài viết liên quan