Phương thức call() trong hàm trong JavaScript

Javascript nâng cao | by Học Javascript

Trong JavaScript, phương thức call() là một công cụ mạnh mẽ giúp kiểm soát cách một hàm được gọi và giá trị của this bên trong hàm đó. call() cho phép chúng ta gọi một hàm với một đối tượng cụ thể làm this, đồng thời truyền các tham số riêng lẻ. Điều này đặc biệt hữu ích khi làm việc với các phương thức dùng chung giữa nhiều đối tượng hoặc khi cần thay đổi ngữ cảnh thực thi của một hàm.

Việc hiểu rõ cách sử dụng call() không chỉ giúp lập trình viên kiểm soát tốt hơn hành vi của this, mà còn tối ưu hóa mã nguồn, giúp tái sử dụng các hàm linh hoạt hơn. Trong bài viết này, chúng ta sẽ tìm hiểu chi tiết về cú pháp, cách hoạt động, cũng như ứng dụng thực tế của phương thức call() trong JavaScript.

Phương thức call() trong JavaScript

Phương thức call() trong JavaScript là một phương thức có sẵn của mọi hàm, cho phép gọi hàm với một giá trị this cụ thể và các tham số được truyền riêng lẻ. Đây là một trong những cách quan trọng để kiểm soát ngữ cảnh thực thi của một hàm, đặc biệt là khi làm việc với các phương thức dùng chung giữa nhiều đối tượng.

Vai trò của call() trong việc kiểm soát this khi gọi hàm

Trong JavaScript, giá trị của this phụ thuộc vào cách hàm được gọi. Khi gọi một hàm theo cách thông thường (functionName()), this mặc định là undefined trong chế độ strict mode hoặc là window (trong trình duyệt) nếu không ở chế độ strict. Tuy nhiên, với call(), chúng ta có thể chỉ định một giá trị this cụ thể, giúp hàm hoạt động trong ngữ cảnh mong muốn.

Ví dụ, nếu có một hàm chung mà nhiều đối tượng cần sử dụng, call() giúp ta "mượn" phương thức từ một đối tượng khác mà không cần sao chép mã.

Ví dụ minh họa:

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

const person1 = { name: "An" };
const person2 = { name: "Bình" };

greet.call(person1); // Output: Xin chào, tôi là An
greet.call(person2); // Output: Xin chào, tôi là Bình

Ở đây, call() giúp ta gọi greet() với this lần lượt là person1person2, giúp phương thức có thể tái sử dụng linh hoạt.

Ngoài việc thay đổi this, call() còn cho phép truyền các tham số riêng lẻ khi gọi hàm, giúp kiểm soát cách hàm thực thi với từng trường hợp cụ thể. Trong các phần tiếp theo, chúng ta sẽ tìm hiểu chi tiết về cú pháp, cách hoạt động và các ứng dụng thực tế của call().

call() là gì?

Phương thức call() là một phương thức có sẵn của mọi hàm trong JavaScript, giúp ta 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ẻ. Điều này giúp kiểm soát cách một hàm thực thi, đặc biệt khi ta muốn tái sử dụng một hàm từ một đối tượng khác mà không cần sao chép lại mã nguồn.

Tại sao cần call()?

  • Kiểm soát giá trị this khi gọi hàm.
  • Cho phép mượn phương thức từ đối tượng khác.
  • Giúp truyền tham số linh hoạt hơn so với cách gọi hàm thông thường.

Cú pháp của call()

Cách sử dụng call() như sau:

functionName.call(thisArg, arg1, arg2, ...);

Trong đó:

  • thisArg: Giá trị được gán cho this bên trong hàm.
  • arg1, arg2, ...: Các tham số được truyền vào hàm (nếu có).

Nếu thisArg không được chỉ định hoặc đặt là null/undefined, this sẽ mặc định là đối tượng window trong trình duyệt hoặc global trong Node.js.

Ví dụ minh họa về call()

Gọi hàm với call() và thay đổi giá trị this

Ví dụ dưới đây minh họa cách sử dụng call() để thay đổi giá trị this khi gọi hàm:

function introduce() {
    console.log(`Tôi tên là ${this.name} và tôi ${this.age} tuổi.`);
}

const person1 = { name: "Nam", age: 25 };
const person2 = { name: "Linh", age: 30 };

introduce.call(person1); // Output: Tôi tên là Nam và tôi 25 tuổi.
introduce.call(person2); // Output: Tôi tên là Linh và tôi 30 tuổi.

Trong đoạn code trên, call() giúp ta gọi hàm introduce() và gán this lần lượt cho person1person2, giúp hàm có thể hoạt động linh hoạt với nhiều đối tượng khác nhau.

Sử dụng call() để mượn phương thức từ object khác

Một lợi ích quan trọng của call() là giúp "mượn" phương thức của một object mà không cần sao chép nó.

Ví dụ:

const student = {
    name: "Hải",
    greet: function() {
        console.log(`Xin chào, tôi là ${this.name}`);
    }
};

const teacher = { name: "Thầy Minh" };

student.greet.call(teacher); // Output: Xin chào, tôi là Thầy Minh

Ở đây, teacher không có phương thức greet(), nhưng nhờ call(), chúng ta có thể sử dụng phương thức greet() từ student mà không cần sao chép.

Sử dụng call() để truyền tham số

Ngoài việc thay đổi this, call() còn cho phép truyền tham số vào hàm.

Ví dụ:

function introduce(job, country) {
    console.log(`Tôi tên là ${this.name}, tôi là một ${job} ở ${country}.`);
}

const person = { name: "Lan" };

introduce.call(person, "kỹ sư", "Việt Nam");
// Output: Tôi tên là Lan, tôi là một kỹ sư ở Việt Nam.

Trong ví dụ này, call() cho phép ta truyền thêm hai tham số "kỹ sư""Việt Nam" vào hàm introduce(), giúp nó linh hoạt hơn.

  • call() là một phương thức hữu ích giúp gọi hàm với một giá trị this cụ thể.
  • Cho phép truyền tham số riêng lẻ, giúp kiểm soát cách hàm thực thi.
  • Thường được dùng để mượn phương thức từ một object khác mà không cần sao chép code.

call() vs apply() vs bind() trong JavaScript

Ba phương thức call(), apply(), và bind() trong JavaScript đều được sử dụng để thay đổi giá trị của this khi gọi hàm. Tuy nhiên, chúng có những điểm khác biệt quan trọng:

Phương thức Cách hoạt động Thời điểm thực thi Cách truyền tham số
call() Gọi hàm với một giá trị this cụ thể Ngay lập tức Truyền từng tham số riêng lẻ
apply() Giống call(), nhưng truyền tham số dưới dạng một mảng Ngay lập tức Truyền tham số dưới dạng mảng
bind() Trả về một hàm mới với this đã được cố định Không thực thi ngay Truyền từng tham số riêng lẻ

So sánh call() với apply()

Cả hai phương thức này đều gọi hàm ngay lập tức và cho phép thay đổi this, nhưng call() truyền tham số riêng lẻ, trong khi apply() truyền tham số dưới dạng một mảng.

Ví dụ:

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

const person = { name: "An" };

greet.call(person, "Xin chào", "!"); 
// Output: Xin chào, tôi là An!

greet.apply(person, ["Xin chào", "!"]); 
// Output: Xin chào, tôi là An!

Khi tham số đã có sẵn trong một mảng và ta không muốn tách chúng ra thủ công.

Ví dụ với Math.max():

const numbers = [5, 2, 9, 1];
console.log(Math.max.apply(null, numbers)); // Output: 9

Nếu dùng call(), ta phải truyền từng giá trị riêng lẻ:

console.log(Math.max.call(null, 5, 2, 9, 1)); // Output: 9

So sánh call() với bind()

Sự khác biệt lớn nhất giữa call()bind() là:

  • call() thực thi hàm ngay lập tức.
  • bind() trả về một hàm mới, có thể gọi sau này.

Ví dụ:

function introduce() {
    console.log(`Tôi là ${this.name}`);
}

const person = { name: "Hùng" };

const boundFunction = introduce.bind(person); 
boundFunction(); // Output: Tôi là Hùng
  • Khi cần tạo một phiên bản mới của hàm với this đã được cố định.
  • Khi muốn truyền trước một số tham số mà không gọi hàm ngay lập tức (còn gọi là partial application).

Ví dụ về partial application:

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

const double = multiply.bind(null, 2); // Gán giá trị 2 cho tham số đầu tiên
console.log(double(5)); // Output: 10

Ứng dụng thực tế của call() trong JavaScript

Mượn phương thức từ object khác

Một trong những ứng dụng phổ biến nhất của call() là mượn phương thức từ các object khác.

Ví dụ: Array.prototype.slice.call(arguments)

function convertToArray() {
    return Array.prototype.slice.call(arguments);
}

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

Ở đây, arguments không phải là một mảng thật sự, nhưng call() giúp ta sử dụng slice() của mảng để chuyển đổi nó thành một mảng thực sự.

Gọi hàm với đối tượng cụ thể trong lập trình hướng đối tượng

Trong lập trình hướng đối tượng, đôi khi ta muốn sử dụng một phương thức của một class khác mà không cần sao chép lại code.

Ví dụ:

const vehicle = {
    brand: "Toyota",
    showBrand: function() {
        console.log(`Hãng xe: ${this.brand}`);
    }
};

const bike = { brand: "Honda" };

vehicle.showBrand.call(bike); 
// Output: Hãng xe: Honda

Xử lý đa kế thừa trong JavaScript

JavaScript không hỗ trợ đa kế thừa trực tiếp như các ngôn ngữ khác, nhưng ta có thể sử dụng call() để mô phỏng điều này.

Ví dụ:

const canEat = {
    eat: function() {
        console.log(`${this.name} đang ăn.`);
    }
};

const canWalk = {
    walk: function() {
        console.log(`${this.name} đang đi.`);
    }
};

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

// Kế thừa phương thức từ canEat và canWalk
Person.prototype.eat = canEat.eat;
Person.prototype.walk = canWalk.walk;

const person = new Person("Tùng");

person.eat(); // Output: Tùng đang ăn.
person.walk(); // Output: Tùng đang đi.

Nhờ call(), ta có thể mượn các phương thức từ các object khác mà không cần sử dụng kế thừa class truyền thống.

Kết bài

Phương thức call() trong JavaScript là một công cụ mạnh mẽ giúp kiểm soát giá trị của this khi gọi hàm. Bằng cách cho phép ta truyền một this cụ thể cùng với các tham số riêng lẻ, call() giúp tái sử dụng các phương thức từ object khác, xử lý đa kế thừa và tối ưu hóa cách gọi hàm trong lập trình hướng đối tượng.

Việc hiểu rõ sự khác biệt giữa call(), apply(), và bind() sẽ giúp lập trình viên viết code linh hoạt hơn, tránh các lỗi liên quan đến this và tận dụng tối đa khả năng của JavaScript. Khi áp dụng call() một cách hiệu quả, bạn có thể tối ưu hóa hiệu suất chương trình và viết code dễ bảo trì hơn.

Hãy thực hành với call() qua các ví dụ thực tế để hiểu sâu hơn về cách nó hoạt động và ứng dụng vào các dự án của bạn!

Bài viết liên quan