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

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

Trong JavaScript, phương thức bind() là một công cụ mạnh mẽ giúp kiểm soát giá trị của this trong các hàm. Khi làm việc với các đối tượng, sự kiện hoặc lập trình hướng đối tượng, việc xác định chính xác this là điều quan trọng để đảm bảo mã hoạt động đúng như mong đợi.

Phương thức bind() cho phép tạo một bản sao của hàm gốc với this được cố định về một giá trị cụ thể, giúp giải quyết các vấn đề về phạm vi this, đặc biệt trong các callback hoặc sự kiện. Không giống như call()apply(), vốn gọi hàm ngay lập tức, bind() chỉ tạo ra một hàm mới mà không thực thi ngay, giúp linh hoạt hơn trong nhiều trường hợp sử dụng.

Việc hiểu rõ cách hoạt động của bind() sẽ giúp lập trình viên viết mã JavaScript tối ưu hơn, tránh lỗi và tận dụng hiệu quả tính năng của ngôn ngữ này.

bind() là gì?

bind() là một phương thức có sẵn của mọi hàm trong JavaScript, thuộc Function.prototype. Nó cho phép tạo ra một bản sao của hàm gốc với this được cố định về một giá trị cụ thể. Điều này rất hữu ích khi làm việc với các callback, sự kiện hoặc trong lập trình hướng đối tượng khi cần kiểm soát this một cách rõ ràng.

Một điểm quan trọng của bind() là nó không thực thi hàm ngay lập tức mà chỉ trả về một hàm mới với this đã được cố định. Khi cần gọi hàm, ta vẫn phải gọi hàm mới được trả về từ bind(). Điều này khác với call()apply(), vốn thực thi hàm ngay lập tức.

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

Giả sử chúng ta có một object và một phương thức bên trong object đó:

const person = {
    name: "John",
    greet: function() {
        console.log("Hello, my name is " + this.name);
    }
};

const greetFn = person.greet;
greetFn(); // Kết quả: "Hello, my name is undefined" (do this không trỏ đến person)

Trong trường hợp trên, khi gán person.greet cho greetFn, this không còn trỏ đến person nữa. Để khắc phục, ta sử dụng bind():

const boundGreet = person.greet.bind(person);
boundGreet(); // Kết quả: "Hello, my name is John"

Hàm boundGreet được tạo ra từ bind()this cố định về person, nên khi gọi hàm, nó vẫn có thể truy cập đúng thuộc tính name của object.

Cú pháp của bind()

Cú pháp của bind() như sau:

const newFunction = functionName.bind(thisArg, arg1, arg2, ...);
  • functionName: Hàm gốc cần được ràng buộc this.

  • thisArg: Giá trị mà this sẽ được cố định trong hàm mới.

  • arg1, arg2, ...: (Tùy chọn) Các tham số được truyền vào hàm mới.

Ví dụ minh họa:

function introduce(age, city) {
    console.log(`Hello, my name is ${this.name}, I'm ${age} years old and I live in ${city}.`);
}

const person = { name: "Alice" };

const boundIntroduce = introduce.bind(person, 25);
boundIntroduce("New York");
// Kết quả: "Hello, my name is Alice, I'm 25 years old and I live in New York."

Ở đây, bind() cố định thisperson và truyền trước tham số age = 25, khi gọi hàm sau này, chỉ cần cung cấp city.

Phương thức bind() giúp kiểm soát this một cách rõ ràng, tránh các lỗi do thay đổi phạm vi this và giúp code dễ bảo trì hơn.

Ví dụ minh họa về bind() trong JavaScript

Giữ giá trị this trong phương thức của object

Trong JavaScript, khi truyền một phương thức của object vào một biến hoặc callback, this có thể mất liên kết với object ban đầu. bind() giúp duy trì giá trị this để phương thức hoạt động đúng.

Ví dụ sai khi mất this
const user = {
    name: "Alice",
    greet: function () {
        console.log(`Hello, my name is ${this.name}`);
    }
};

const greetFn = user.greet;
greetFn(); // Kết quả: "Hello, my name is undefined" (this bị mất liên kết)

Ở đây, khi gán user.greet vào greetFn, this không còn trỏ đến user nữa, dẫn đến lỗi.

Sửa lỗi bằng bind()
const boundGreet = user.greet.bind(user);
boundGreet(); // Kết quả: "Hello, my name is Alice"

Lúc này, bind(user) giúp giữ nguyên giá trị của this, đảm bảo phương thức hoạt động chính xác.

Sử dụng bind() để truyền đối số cố định trước

bind() không chỉ cố định this mà còn cho phép truyền một số tham số mặc định trước.

Ví dụ truyền tham số trước
function introduce(age, city) {
    console.log(`Hello, my name is ${this.name}, I'm ${age} years old and I live in ${city}.`);
}

const person = { name: "Bob" };

const boundIntroduce = introduce.bind(person, 30); // Cố định age = 30
boundIntroduce("London");
// Kết quả: "Hello, my name is Bob, I'm 30 years old and I live in London."

boundIntroduce("Paris");
// Kết quả: "Hello, my name is Bob, I'm 30 years old and I live in Paris."

Ở đây, age = 30 được cố định, nhưng city vẫn có thể được truyền vào sau.

Kết hợp bind() với setTimeout() để giữ ngữ cảnh this

Một vấn đề thường gặp khi sử dụng setTimeout()this bên trong callback không còn trỏ đến object ban đầu. bind() giúp khắc phục điều này.

Ví dụ mất this trong setTimeout()

const counter = {
    count: 0,
    increment: function () {
        setTimeout(function () {
            this.count++;
            console.log(this.count); 
        }, 1000);
    }
};

counter.increment(); // Kết quả: NaN hoặc lỗi (this không trỏ đến counter)

Lý do lỗi là vì hàm bên trong setTimeout() không giữ nguyên this của counter, mà this sẽ trỏ đến window hoặc undefined (trong chế độ strict mode).

Sửa lỗi bằng bind()

const counterFixed = {
    count: 0,
    increment: function () {
        setTimeout(
            function () {
                this.count++;
                console.log(this.count);
            }.bind(this), 
        1000);
    }
};

counterFixed.increment(); // Kết quả: 1 (this giữ nguyên giá trị của counterFixed)

Giờ đây, bind(this) giúp cố định this của increment() vào object counterFixed, nên chương trình hoạt động chính xác.

So sánh bind() vs call() vs apply() trong JavaScript

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

Ví dụ minh họa

call() – Gọi hàm ngay lập tức với từng tham số

function greet(age, city) {
    console.log(`Hello, my name is ${this.name}, I'm ${age} years old and I live in ${city}.`);
}

const person = { name: "Alice" };

greet.call(person, 25, "New York"); 
// Kết quả: "Hello, my name is Alice, I'm 25 years old and I live in New York."

call() gọi hàm ngay lập tức, truyền từng tham số.

apply() – Gọi hàm ngay lập tức với tham số dạng mảng

greet.apply(person, [25, "New York"]); 
// Kết quả: "Hello, my name is Alice, I'm 25 years old and I live in New York."

apply() giống call(), nhưng truyền tham số bằng mảng.

bind() – Trả về một hàm mới với this cố định, không gọi ngay

const boundGreet = greet.bind(person, 25); // Cố định `this` và `age = 25`
boundGreet("New York"); 
// Kết quả: "Hello, my name is Alice, I'm 25 years old and I live in New York."

bind() không gọi hàm ngay, mà trả về một hàm mới với this cố định.

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

Giữ giá trị this trong sự kiện DOM

Trong trình xử lý sự kiện DOM, this thường trỏ đến phần tử HTML, không phải object của bạn. Dùng bind() để giữ giá trị this.

const button = document.getElementById("myButton");

const user = {
    name: "Bob",
    sayHello: function () {
        console.log(`Hello, I'm ${this.name}`);
    }
};

button.addEventListener("click", user.sayHello.bind(user)); 
// Không bị mất `this` khi gọi từ sự kiện DOM.

Nếu không dùng bind(user), this trong sayHello() sẽ trỏ đến button, không phải user.

Tạo hàm với tham số cố định trước (Partial Application)

Khi cần tạo một hàm với một số tham số được cố định sẵn.

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

const double = multiply.bind(null, 2); // Cố định `a = 2`
console.log(double(5)); // Kết quả: 10
console.log(double(10)); // Kết quả: 20

double() là một phiên bản của multiply() với a = 2 cố định.

Sử dụng với setTimeout() và setInterval()

Trong setTimeout(), this thường bị thay đổi, cần bind() để giữ nguyên.

Ví dụ lỗi do mất this

const counter = {
    count: 0,
    increment: function () {
        setTimeout(function () {
            this.count++; 
            console.log(this.count);
        }, 1000);
    }
};

counter.increment(); // Lỗi: this.count là undefined

this không còn trỏ đến counter, gây lỗi.

Sửa lỗi bằng bind()

const counterFixed = {
    count: 0,
    increment: function () {
        setTimeout(
            function () {
                this.count++;
                console.log(this.count);
            }.bind(this), 
        1000);
    }
};

counterFixed.increment(); // Kết quả: 1

bind(this) giúp giữ nguyên ngữ cảnh của this.

Tạo phương thức có this cố định trong lập trình hướng đối tượng

Khi truyền phương thức của object vào biến hoặc callback, this có thể bị mất. bind() giúp giữ this.

class Person {
    constructor(name) {
        this.name = name;
    }

    greet() {
        console.log(`Hello, I'm ${this.name}`);
    }
}

const alice = new Person("Alice");
const greetFn = alice.greet.bind(alice); // Giữ `this` cố định
greetFn(); // Kết quả: "Hello, I'm Alice"

Nếu không dùng bind(), this trong greet() sẽ là undefined khi gọi qua biến greetFn.

Kết bài

Phương thức bind() trong JavaScript là một công cụ mạnh mẽ giúp kiểm soát giá trị của this, đặc biệt hữu ích trong các tình huống như xử lý sự kiện, lập trình hướng đối tượng, và callback bất đồng bộ. Không giống như call()apply(), 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ố định, giúp lập trình viên linh hoạt hơn trong việc quản lý ngữ cảnh của hàm.

Việc hiểu rõ cách hoạt động và ứng dụng của bind() sẽ giúp bạn tránh được các lỗi phổ biến liên quan đến this, tối ưu hóa mã nguồn và viết code dễ bảo trì hơn. Hãy tận dụng bind() một cách hiệu quả để cải thiện khả năng lập trình JavaScript của bạn!

Bài viết liên quan