Tham số hàm Function trong JavaScript

Javascript nâng cao | by Học Javascript

Trong JavaScript, hàm (function) là một thành phần quan trọng giúp tổ chức và tái sử dụng mã nguồn hiệu quả. Để hàm có thể hoạt động linh hoạt hơn, ta cần truyền dữ liệu vào thông qua tham số (parameters). Tham số giúp hàm nhận giá trị từ bên ngoài, xử lý và trả về kết quả mong muốn.

Việc hiểu rõ cách sử dụng tham số trong JavaScript không chỉ giúp tối ưu mã nguồn mà còn nâng cao khả năng xây dựng các chương trình phức tạp. JavaScript cung cấp nhiều cơ chế làm việc với tham số như tham số mặc định, toán tử rest (...args), truyền tham số theo giá trị hoặc tham chiếu, và thậm chí là destructuring parameters giúp viết mã gọn gàng và dễ hiểu hơn.

Trong bài viết này, mình sẽ tìm hiểu chi tiết về tham số trong hàm, cách truyền dữ liệu, cũng như những kỹ thuật nâng cao giúp lập trình viên tận dụng tối đa sức mạnh của JavaScript.

Tham số trong Hàm JavaScript

Trong JavaScript, hàm (function) là một khối mã được thiết kế để thực hiện một tác vụ cụ thể. Để một hàm hoạt động linh hoạt và có thể tái sử dụng trong nhiều tình huống khác nhau, chúng ta cần sử dụng tham số (parameters).

Tham số là gì?

Tham số là các biến được khai báo trong phần định nghĩa của một hàm và được sử dụng để truyền dữ liệu từ bên ngoài vào hàm. Khi gọi hàm, chúng ta cung cấp đối số (arguments) – tức là giá trị thực tế truyền vào tham số để hàm xử lý.

Ví dụ về tham số trong JavaScript:

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

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

Ở ví dụ trên, nametham số của hàm greet(), và khi gọi hàm, chúng ta truyền vào các đối số như "Linh""An" để nhận kết quả tương ứng.

Vai trò của tham số trong hàm

Tham số giúp hàm hoạt động linh hoạt hơn bằng cách cho phép truyền dữ liệu vào thay vì sử dụng các giá trị cố định. Một số vai trò chính của tham số trong hàm:

Truyền dữ liệu từ bên ngoài vào hàm để xử lý

Thay vì sử dụng giá trị cố định trong hàm, tham số giúp nhận giá trị động, giúp mã nguồn linh hoạt và dễ tái sử dụng hơn.

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

console.log(add(5, 3));  // Output: 8
console.log(add(10, 20)); // Output: 30

Ở đây, ab là hai tham số giúp hàm add() có thể thực hiện phép cộng với nhiều giá trị khác nhau.

Giúp hàm có thể tái sử dụng nhiều lần với nhiều dữ liệu khác nhau

Hàm có tham số có thể áp dụng cho nhiều tình huống mà không cần viết lại mã nhiều lần.

function calculateArea(width, height) {
    return width * height;
}

console.log(calculateArea(5, 10)); // Output: 50
console.log(calculateArea(7, 3));  // Output: 21

Nếu không có tham số, chúng ta phải viết riêng từng hàm cho từng giá trị widthheight, làm mã nguồn trở nên rườm rà.

Giúp tối ưu hóa mã nguồn và tăng tính bảo trì

Thay vì phải sửa đổi trực tiếp trong hàm mỗi khi muốn xử lý dữ liệu mới, chúng ta chỉ cần thay đổi giá trị của tham số khi gọi hàm. Điều này giúp mã nguồn dễ đọc, dễ quản lý và dễ bảo trì hơn.

Tăng hiệu suất và tiết kiệm bộ nhớ

Việc sử dụng tham số giúp tránh việc tạo nhiều biến không cần thiết, giảm lượng mã trùng lặp và giúp chương trình chạy nhanh hơn.

Khái niệm về tham số hàm trong JavaScript

  • Tham số (parameters): Là các biến được định nghĩa trong phần khai báo của một hàm. Chúng đại diện cho các giá trị mà hàm sẽ nhận khi được gọi.

  • Đối số (arguments): Là các giá trị thực tế được truyền vào khi gọi hàm.

Ví dụ minh họa:

function greet(name) { // name là tham số
    console.log(`Xin chào, ${name}!`);
}

greet("Linh"); // "Linh" là đối số, Output: Xin chào, Linh!
greet("An");   // "An" là đối số, Output: Xin chào, An!

Trong ví dụ trên:

  • name là tham số của hàm greet().

  • "Linh""An" là các đối số được truyền vào khi gọi hàm.

Sự khác biệt giữa Tham số và Đối số

Đặc điểm Tham số (Parameters) Đối số (Arguments)
Định nghĩa Biến được khai báo trong hàm Giá trị được truyền vào khi gọi hàm
Số lượng Cố định trong khai báo hàm Có thể thay đổi khi gọi hàm
Giá trị Không có giá trị mặc định (trừ khi sử dụng tham số mặc định) Là giá trị thực tế truyền vào

Ví dụ:

function add(a, b) {  // a, b là tham số
    return a + b;
}

console.log(add(5, 3));  // 5 và 3 là đối số, Output: 8
console.log(add(10, 20)); // 10 và 20 là đối số, Output: 30
  • a, btham số, vì chúng chỉ được định nghĩa trong phần khai báo hàm.

  • 5, 310, 20đối số, vì chúng là giá trị thực tế được truyền vào khi gọi hàm.

Cách truyền tham số vào hàm

Có nhiều cách truyền tham số vào một hàm trong JavaScript, bao gồm:
Truyền tham số dạng cơ bản (primitive values)

function multiply(x, y) {
    return x * y;
}
console.log(multiply(4, 5)); // Output: 20

Truyền tham số dạng object

function printPerson(person) {
    console.log(`Tên: ${person.name}, Tuổi: ${person.age}`);
}

const student = { name: "Minh", age: 22 };
printPerson(student); // Output: Tên: Minh, Tuổi: 22

Truyền tham số dạng mảng

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

console.log(sum([1, 2, 3, 4])); // Output: 10

Truyền tham số dạng hàm (callback function)

function execute(callback) {
    callback();
}

execute(() => console.log("Hàm được gọi!")); // Output: Hàm được gọi!

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

Trong JavaScript, khi một tham số không được truyền giá trị khi gọi hàm, nó sẽ có giá trị undefined. Để tránh lỗi khi không truyền đối số, chúng ta có thể đặt giá trị mặc định cho tham số.

Cú pháp tham số mặc định:

function functionName(parameter = defaultValue) {
    // Code xử lý
}

Nếu không có đối số nào được truyền vào, tham số sẽ nhận giá trị mặc định thay vì undefined.

Ví dụ minh họa về cách sử dụng tham số mặc định

Ví dụ 1: Tham số mặc định đơn giản

function greet(name = "Người dùng") {
    console.log(`Xin chào, ${name}!`);
}

greet();         // Output: Xin chào, Người dùng!
greet("Linh");   // Output: Xin chào, Linh!

Trong trường hợp greet() được gọi mà không có đối số, tham số name sẽ có giá trị mặc định là "Người dùng".

Ví dụ 2: Dùng tham số mặc định để tránh lỗi khi không truyền đối số

function multiply(a = 1, b = 1) {
    return a * b;
}

console.log(multiply(5, 3)); // Output: 15
console.log(multiply(4));    // Output: 4 (b mặc định là 1)
console.log(multiply());     // Output: 1 (cả a và b mặc định là 1)

Nếu không có giá trị mặc định, khi gọi multiply(4), b sẽ là undefined, dẫn đến kết quả NaN.

Ví dụ 3: Sử dụng giá trị mặc định động

Chúng ta cũng có thể sử dụng một biểu thức hoặc một hàm để gán giá trị mặc định cho tham số.

function generateRandom() {
    return Math.floor(Math.random() * 10);
}

function printNumber(num = generateRandom()) {
    console.log(`Số được chọn: ${num}`);
}

printNumber(); // Output: Số được chọn: (một số ngẫu nhiên từ 0 đến 9)
printNumber(7); // Output: Số được chọn: 7

Trong ví dụ trên, nếu không truyền tham số vào printNumber(), nó sẽ tự động nhận một số ngẫu nhiên làm giá trị mặc định.

Toán tử Rest (...args) để nhận nhiều tham số trong JavaScript

Toán tử Rest (...) trong JavaScript cho phép một hàm nhận một số lượng tham số không xác định và gom chúng lại thành một mảng. Điều này rất hữu ích khi chúng ta không biết trước số lượng đối số sẽ được truyền vào hàm.

Cách sử dụng ...args để nhận số lượng tham số không xác định

Cú pháp:

function tenHam(...args) {
    // args là một mảng chứa tất cả các đối số được truyền vào
}

Lưu ý:

  • Toán tử ... phải được đặt ở tham số cuối cùng trong danh sách tham số.

  • args sẽ chứa tất cả các đối số còn lại chưa được gán cho tham số khác.

Ví dụ minh họa về cách dùng Rest Parameters

Ví dụ 1: Gom nhiều tham số thành một mảng

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

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

Trong ví dụ này, tất cả các tham số truyền vào sẽ được gom thành một mảng numbers, sau đó sử dụng reduce() để tính tổng.

Ví dụ 2: Sử dụng Rest kết hợp với tham số khác

function introduce(name, age, ...hobbies) {
    console.log(`Xin chào, tôi là ${name}, ${age} tuổi.`);
    console.log(`Sở thích của tôi: ${hobbies.join(", ")}`);
}

introduce("Linh", 25, "Đọc sách", "Chơi game", "Du lịch");
// Output:
// Xin chào, tôi là Linh, 25 tuổi.
// Sở thích của tôi: Đọc sách, Chơi game, Du lịch

Ở đây, nameage được nhận trước, còn ...hobbies sẽ chứa tất cả các tham số còn lại dưới dạng một mảng.

Ví dụ 3: Kết hợp ...args với Destructuring

function displayInfo(first, second, ...others) {
    console.log("Người đầu tiên:", first);
    console.log("Người thứ hai:", second);
    console.log("Những người còn lại:", others);
}

displayInfo("An", "Bình", "Châu", "Dũng", "Hà");
// Output:
// Người đầu tiên: An
// Người thứ hai: Bình
// Những người còn lại: [ 'Châu', 'Dũng', 'Hà' ]

Ở đây, tham số firstsecond nhận hai giá trị đầu tiên, còn ...others gom tất cả phần còn lại thành một mảng.

Truyền tham số theo giá trị và tham chiếu trong JavaScript

Truyền tham số theo giá trị (Pass by Value)

Trong JavaScript, các kiểu dữ liệu nguyên thủy (Primitive Types) như:
Number
String
Boolean
Null
Undefined
Symbol

... được truyền theo giá trị.

Điều này có nghĩa là khi một biến chứa giá trị nguyên thủy được truyền vào một hàm, một bản sao của giá trị đó sẽ được tạo ra. Thay đổi giá trị bên trong hàm sẽ không ảnh hưởng đến biến bên ngoài.

Ví dụ minh họa: Truyền tham số theo giá trị

function changeValue(x) {
    x = 10;
    console.log("Giá trị bên trong hàm:", x);
}

let num = 5;
changeValue(num);
console.log("Giá trị bên ngoài hàm:", num);

// Output:
// Giá trị bên trong hàm: 10
// Giá trị bên ngoài hàm: 5 (Không bị thay đổi)

Biến num bên ngoài không bị thay đổi, vì x chỉ là một bản sao của giá trị num.

Truyền tham số theo tham chiếu (Pass by Reference)

Các kiểu dữ liệu tham chiếu (Reference Types) như:
Object
Array
Function

... được truyền theo tham chiếu.

Điều này có nghĩa là khi một đối tượng (object hoặc array) được truyền vào một hàm, tham chiếu (địa chỉ bộ nhớ) của đối tượng đó sẽ được truyền đi thay vì tạo bản sao mới.

Do đó, nếu chúng ta thay đổi nội dung của đối tượng bên trong hàm, nó cũng ảnh hưởng đến đối tượng bên ngoài.

Ví dụ minh họa: Truyền tham số theo tham chiếu

function updatePerson(person) {
    person.age = 30;
    console.log("Tuổi bên trong hàm:", person.age);
}

let student = { name: "Linh", age: 25 };
updatePerson(student);
console.log("Tuổi bên ngoài hàm:", student.age);

// Output:
// Tuổi bên trong hàm: 30
// Tuổi bên ngoài hàm: 30 (Bị thay đổi)

Biến student bị thay đổi ngay cả khi hàm kết thúc, vì nó được truyền theo tham chiếu.

Sự khác biệt giữa truyền theo giá trị và truyền theo tham chiếu

Đặc điểm Truyền theo giá trị (Pass by Value) Truyền theo tham chiếu (Pass by Reference)
Kiểu dữ liệu Số, chuỗi, boolean, null, undefined, symbol Object, array, function
Khi truyền vào hàm Tạo bản sao của giá trị Truyền địa chỉ bộ nhớ của đối tượng
Ảnh hưởng khi thay đổi trong hàm Không ảnh hưởng đến biến gốc Làm thay đổi dữ liệu gốc

Cách tránh thay đổi dữ liệu gốc khi truyền tham chiếu

Nếu không muốn thay đổi dữ liệu gốc khi truyền Object hoặc Array, có thể tạo một bản sao của nó trước khi truyền vào hàm.

Cách 1: Dùng Object.assign() (Sao chép shallow copy)

function updatePerson(person) {
    let newPerson = Object.assign({}, person); // Sao chép object
    newPerson.age = 30;
    console.log("Tuổi bên trong hàm:", newPerson.age);
}

let student = { name: "Linh", age: 25 };
updatePerson(student);
console.log("Tuổi bên ngoài hàm:", student.age);

// Output:
// Tuổi bên trong hàm: 30
// Tuổi bên ngoài hàm: 25 (Không bị thay đổi)

Cách 2: Dùng spread operator (...) để tạo bản sao

function updatePerson(person) {
    let newPerson = { ...person }; // Tạo bản sao mới
    newPerson.age = 30;
    console.log("Tuổi bên trong hàm:", newPerson.age);
}

let student = { name: "Linh", age: 25 };
updatePerson(student);
console.log("Tuổi bên ngoài hàm:", student.age);

// Output:
// Tuổi bên trong hàm: 30
// Tuổi bên ngoài hàm: 25 (Không bị thay đổi)

arguments Object trong JavaScript

Trong JavaScript, argumentsmột đối tượng (object-like) có sẵn trong tất cả các hàm thông thường (không áp dụng cho arrow function). Nó chứa tất cả các đối số được truyền vào hàm dưới dạng mảng-like.

Đặc điểm của arguments object:

  • Có thể truy cập tất cả các đối số truyền vào hàm.

  • Không phải là một mảng thực sự, nên không thể dùng các phương thức mảng (map(), forEach(), ...).

  • Chỉ tồn tại trong hàm thông thường (function declarationfunction expression), không có trong arrow function.

Sự khác biệt giữa arguments và ...args

Đặc điểm arguments ...args (Rest Parameters)
Kiểu dữ liệu Đối tượng giống mảng (array-like object) Mảng thực sự (array)
Sử dụng trong Chỉ có trong function declarationfunction expression Có trong tất cả các hàm, kể cả arrow function
Có thể dùng phương thức mảng (map(), filter(), ...)? Không
Bao gồm tất cả tham số? Có, nhưng chỉ với các tham số chưa được gán khác
Hoạt động với arrow function? Không

Ví dụ minh họa về cách sử dụng arguments

Ví dụ 1: Truy cập arguments trong một hàm thông thường

function sum() {
    let total = 0;
    for (let i = 0; i < arguments.length; i++) {
        total += arguments[i];
    }
    return total;
}

console.log(sum(1, 2, 3, 4)); // Output: 10
console.log(sum(10, 20));     // Output: 30
console.log(sum());           // Output: 0

arguments.length giúp lấy số lượng tham số được truyền vào.
arguments[i] truy cập từng đối số theo index.

Ví dụ 2: arguments không tồn tại trong arrow function

const testArgs = () => {
    console.log(arguments);
};

testArgs(1, 2, 3); // Error: arguments is not defined

Lỗi xảy ra vì arrow function không có arguments object.

Ví dụ 3: So sánh arguments...args

function normalFunction() {
    console.log(arguments); // arguments object
}

const arrowFunction = (...args) => {
    console.log(args); // Mảng thực sự
};

normalFunction(1, 2, 3);  // Output: [Arguments] { '0': 1, '1': 2, '2': 3 }
arrowFunction(1, 2, 3);   // Output: [1, 2, 3]

arguments là một đối tượng giống mảng.
...args là một mảng thực sự, có thể sử dụng phương thức mảng như map(), forEach(),...

Destructuring Parameters hàm trong JavaScript

Object Destructuring là một tính năng giúp trích xuất giá trị từ object và truyền chúng vào các biến một cách gọn gàng.

  • Giúp mã nguồn ngắn gọn và dễ đọc hơn.

  • Không cần truy cập từng thuộc tính thông qua dot notation (obj.key).

Ví dụ về Object Destructuring trong hàm

Ví dụ 1: Truyền object vào hàm và sử dụng destructuring

function printUserInfo({ name, age, city }) {
    console.log(`Tên: ${name}`);
    console.log(`Tuổi: ${age}`);
    console.log(`Thành phố: ${city}`);
}

const user = {
    name: "Linh",
    age: 25,
    city: "Hà Nội"
};

printUserInfo(user);

// Output:
// Tên: Linh
// Tuổi: 25
// Thành phố: Hà Nội

Không cần viết user.name, user.age, user.city trong hàm, giúp mã ngắn gọn hơn.


Ví dụ 2: Truyền tham số với giá trị mặc định

function greet({ name = "User", age = 18 }) {
    console.log(`Xin chào ${name}, bạn ${age} tuổi.`);
}

greet({ name: "Dũng" }); 
// Output: Xin chào Dũng, bạn 18 tuổi.

greet({});
// Output: Xin chào User, bạn 18 tuổi.

Nếu object không có thuộc tính nào đó, giá trị mặc định sẽ được sử dụng.

Ví dụ 3: Đặt tên biến khác khi destructuring

function displayBook({ title: bookTitle, author: bookAuthor }) {
    console.log(`Sách: ${bookTitle}`);
    console.log(`Tác giả: ${bookAuthor}`);
}

const book = {
    title: "JavaScript Basics",
    author: "John Doe"
};

displayBook(book);

// Output:
// Sách: JavaScript Basics
// Tác giả: John Doe

Sử dụng cú pháp { title: bookTitle } để đổi tên biến.

Giới thiệu về Array Destructuring khi truyền tham số vào hàm

Array Destructuring giúp trích xuất các phần tử của một mảng và gán chúng vào biến một cách trực tiếp.

  • Giúp mã nguồn dễ đọc hơn.

  • Truy cập nhanh các phần tử mà không cần dùng array[index].

Ví dụ về Array Destructuring trong hàm

Ví dụ 1: Destructuring một mảng khi truyền vào hàm

function printCoordinates([x, y]) {
    console.log(`Tọa độ X: ${x}`);
    console.log(`Tọa độ Y: $2025`);
}

const point = [10, 20];
printCoordinates(point);

// Output:
// Tọa độ X: 10
// Tọa độ Y: 20

Không cần viết point[0], point[1] trong hàm.

Ví dụ 2: Sử dụng giá trị mặc định khi destructuring

function displayUser([name = "User", age = 18]) {
    console.log(`Tên: ${name}, Tuổi: ${age}`);
}

displayUser(["Linh"]); // Output: Tên: Linh, Tuổi: 18
displayUser([]);       // Output: Tên: User, Tuổi: 18

Nếu mảng không có giá trị, sẽ dùng giá trị mặc định.

Ví dụ 3: Bỏ qua phần tử không cần thiết khi destructuring

function getThirdItem([, , third]) {
    console.log(`Phần tử thứ ba: ${third}`);
}

const numbers = [10, 20, 30, 40];
getThirdItem(numbers);

// Output:
// Phần tử thứ ba: 30

Dùng dấu , để bỏ qua các phần tử không cần thiết.

Kết bài

Tham số trong hàm là một phần quan trọng trong JavaScript, giúp truyền dữ liệu vào hàm để xử lý một cách linh hoạt và hiệu quả. Việc sử dụng tham số mặc định, toán tử rest (...args), arguments object, cũng như kỹ thuật destructuring parameters giúp lập trình viên viết mã ngắn gọn, dễ đọc và bảo trì hơn.

Hiểu rõ về cách truyền tham số theo giá trị và tham chiếu, cùng với khả năng tận dụng Object và Array Destructuring, sẽ giúp tối ưu hóa hiệu suất và cải thiện khả năng quản lý dữ liệu trong các ứng dụng thực tế.

Bằng cách áp dụng linh hoạt các kỹ thuật này, bạn có thể viết các hàm mạnh mẽ, dễ hiểu và tái sử dụng tốt hơn, từ đó nâng cao chất lượng mã nguồn và hiệu suất lập trình trong JavaScript.

Bài viết liên quan