Gọi hàm (Function Invocation) trong JavaScript
Javascript nâng cao | by
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ố name
và age
, 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 brand
và model
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.name
và this.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ỏ đếnstudent
nữa mà trở thànhundefined
(hoặcwindow
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ẻ | Có |
apply() |
Gọi hàm với this cụ thể |
Truyền một mảng tham số | Có |
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