Code Tu Tam

Function binding trong Javascript

Rate this post

Khi truyền các phương thức đối tượng dưới dạng callbacks tới setTimeout, có một vấn đề mà có thể bạn đã biết: this trả về không đúng (undefined, null, …).

Bài viết hôm nay sẽ hướng dẫn bạn khắc phục điều này.

Mất “this”

Chúng ta đã thấy những ví dụ về việc mất “this”. Sau khi một phương thức được chuyển đến một nơi nào đó tách biệt với đối tượng – this sẽ bị mất.

Ví dụ:

let user = {
  firstName: "Thái",
  demo() {
    alert(`Hello, ${this.firstName}!`);
  }
};

setTimeout(user.demo, 1000); 

// Trả về
Hello, undefined!

Như đã thấy, kết quả trả về ở đây không phải là – Hello, Thái! mà là Hello, undefined!. Nguyễn nhân là do setTimeout tách function user.demo ra khỏi đối tượng user. Vậy nên đoạn code cuối cũng có thể được hiểu như sau:

let demoNext = user.demo;
setTimeout(demoNext , 1000); 

// Trả về
lost user context

Phương thức setTimeout trong trình duyệt hơi đặc biệt: nó đặt this=window cho lệnh gọi hàm. Vì vậy, this.firstName nó cố gắng để trả về window.firstName, mà không tồn tại. Trong các trường hợp tương tự khác, thường this chỉ trở thành undefined.

Cách để giải quyết vấn đề này như sau:

Giái pháp 1: Gọi lại thông qua 1 function khác

Cách đơn giản nhất là gọi lại thông qua 1 function khác:

Ví dụ:

let user = {
  firstName: "Thái",
  demo() {
    alert(`Hello, ${this.firstName}!`);
  }
};

setTimeout(function() {
  user.demo(); // Hello, Thái!
}, 1000);

Bây giờ nó hoạt động, bởi vì nó nhận user từ môi trường bên ngoài, và sau đó gọi phương thức một cách bình thường.

Hoặc bạn có thể dùng arrow function như sau:

setTimeout(() => user.demo(), 1000); // Hello, Thái!

Mặc dù làm như này thì code vẫn chạy được. Nhưng có 1 vấn đề như sau:

Điều gì sẽ xảy ra nếu trước khi setTimeout kích hoạt (sau 1 giây) user Thay đổi giá trị? Sau đó, nó sẽ gọi nhầm đối tượng!

let user = {
  firstName: "Thái",
  demo() {
    alert(`Hello, ${this.firstName}!`);
  }
};

setTimeout(() => user.demo(), 1000);

// Giá trị của user sẽ bị thay đổi sau 1 giây
user = {
  demo() { alert("Thay đổi giá trị!"); }
};

// Thay đổi giá trị!

Giải pháp thứ 2 sẽ giải quyết vấn đề này.

Giá pháp 2: Dùng function bind

Cú pháp cơ bản là:

let boundFunc = func.bind(context);

Kết quả của func.bind(context)là một “đối tượng kỳ lạ” giống hàm đặc biệt, có thể gọi là hàm và chuyển lệnh gọi đến funccài đặt một cách minh bạch this=context.

Nói cách khác, gọi boundFuncgiống như funcvới cố định this.

Ví dụ: ở đây funcUserchuyển một cuộc gọi tới funcvới this=user:

let user = {
  firstName: "Thái"
};

function func() {
  alert(this.firstName);
}

let funcUser = func.bind(user);
funcUser(); // Thái

Đây func.bind(user)là một “biến thể ràng buộc” của func, với cố định this=user.

Tất cả các đối số được chuyển đến “nguyên trạng func” ban đầu, ví dụ:

let user = {
  firstName: "Thái"
};

function func(phrase) {
  alert(phrase + ', ' + this.firstName);
}

// bind this thành user
let funcUser = func.bind(user);

funcUser("Hello"); 
// Hello, Thái (tham số 'Hello' đã được truyền như bình thường và this=user)

Bây giờ hãy thử với một object method:

let user = {
  firstName: "Thái",
  demo() {
    alert(`Hello, ${this.firstName}!`);
  }
};

let demo = user.demo.bind(user); // (*)

// có thể chạy nó mà không có object
demo(); // Hello, Thái!

setTimeout(demo, 1000); // Hello, Thái!

// kể cả giá trị có thay đổi sau 1 giây
// demo sử dụng giá trị ràng buộc trước tham chiếu đến object user cũ
user = {
  demo() { alert("Thay đổi giá trị!"); }
};

Trong dòng, (*) chúng tôi lấy phương thức user.demo và bind nó với user. Đây demo là một hàm “bound”, có thể được gọi một mình hoặc được chuyển đến setTimeout – không quan trọng, context sẽ phù hợp.

Ở đây chúng ta có thể thấy rằng các đối số được truyền “nguyên trạng”, chỉ this được sửa bởi bind:

let user = {
  firstName: "Thái",
  say(phrase) {
    alert(`${phrase}, ${this.firstName}!`);
  }
};

let say = user.say.bind(user);

say("Hello"); // Hello, Thái! ("Hello" đối số được thông qua say)
say("Bye"); // Bye, Thái! ("Bye" được chuyển cho say)

Và có 1 lưu ý là:

bind không làm thay đổi được this của 1 arrow function. Nên các bạn nãy lưu ý điều này.

 

 

 

 

 

 

Exit mobile version