Bind, call và apply

Gọi hàm với context khác

Biến this trỏ tới đối tượng gọi hàm. Khi đối tượng gọi hàm thay đổi (hay context thay đổi) thì biến this sẽ nhận giá trị khác.

Ví dụ gọi đối tượng với hàm bình thường như sau:

1
2
3
4
5
6
7
var userJohn = {
  firstName: "John",
  sayHi() {
    console.log(`Chào, ${this.firstName}!`);
  }
};
userJohn.sayHi(); // Chào, John!

Bây giờ hãy thay đổi con trỏ this của hàm sayHi() trỏ tới đối tượng khác.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var userJohn = {
  firstName: "John",
  sayHi() {
    console.log(`Chào, ${this.firstName}!`);
  }
};

// mượn hàm sayHi
var sayHi = userJohn.sayHi;

var userPeter = {
  firstName: "Peter",
  hi: sayHi
};
userPeter.hi(); // Chào, Peter!

Để ý lúc này đối tượng userPeter đã mượn hàm sayHi của userJohn và thực thi hàm này với context mới. Nên lúc này con trỏ this cũng thay đổi theo.

Thay đổi context theo ý muốn

Giờ nếu muốn userPeter.hi() in ra Chào, John! thì làm thế nào. Cách đầu tiên là thay đổi context theo ý muốn với hàm bind.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var userJohn = {
  firstName: "John",
  sayHi() {
    console.log(`Chào, ${this.firstName}!`);
  }
};

// Biến sayHi lúc này có context là userJohn
var sayHi = userJohn.sayHi.bind(userJohn);

var userPeter = {
  firstName: "Peter",
  hi: sayHi
};
userPeter.hi(); // Chào, John!

Cách khác nữa là gọi hàm sayHi với context là userJohn.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var userJohn = {
  firstName: "John",
  sayHi() {
    console.log(`Chào, ${this.firstName}!`);
  }
};

var userPeter = {
  firstName: "Peter",
  hi: function() {
    // context lúc này là userJohn
    userJohn.sayHi();
  }
};
userPeter.hi(); // Chào, Join!

Tham số của hàm bind

Hàm bind ngoài gán context vào hàm, thì còn có thể gán tham số vào hàm.
Ví dụ sau định nghĩa hàm tích, và hàm gấp đôi một số.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function mul(a, b) {
  return a * b;
}
// không thay đổi context và
// tham số a nhận giá trị 2
let double = mul.bind(null, 2);

console.log(double(3)); // = mul(2, 3) = 6
console.log(double(4)); // = mul(2, 4) = 8
console.log(double(5)); // = mul(2, 5) = 10

Hàm call

Hàm bind sẽ gán context vào hàm và không thực thi hàm. Còn hàm call thì gán context và thực thi hàm. Tham số đầu tiên của hàm call là context mới, các tham số tiếp theo là các tham số khi gọi hàm.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
var userJohn = {
  firstName: "John",
  sayHi(message) {
    console.log(`Chào, ${this.firstName}!`);
    console.log(message);
  }
};
var userPeter = {
  firstName: "Peter"
};

var sayHi = userJohn.sayHi;

// gọi hàm sayHi với context là userPeter
sayHi.call(userPeter, 'Hôm nay anh thế nào');
// -> Chào, Peter!
// -> Hôm nay anh thế nào

Hàm apply

Hàm apply tương tự hàm call, đó là bind context mới vào hàm, và thực thi hàm. Tuy nhiên các tham số ở dưới dạng một mảng (hoặc giống mảng). Hàm apply hay dùng trong các hàm viết sẵn của Javascript.

Ví dụ hàm push sẽ thêm các phần tử vào cuối array.

1
2
3
var array = ['a', 'b'];
array.push(1, 2, 3);
console.log(array); // [ "a", "b", 1, 2, 3 ]

Nếu muốn một mảng nối thêm các phần tử của một mảng khác thì làm thế nào. Hàm concat trả về một mảng mới nên không phù hợp. Trong trường hợp này, chúng ta có thể sử dụng với hàm push và hàm apply. Hàm apply lúc này sẽ biến hàm push từ nhận danh sách tham số thành nhận một mảng là tham số.

1
2
3
4
var array = ['a', 'b'];
var elements = [1, 2, 3];
Array.prototype.push.apply(array, elements);
console.log(array); // [ "a", "b", 1, 2, 3 ]

Ví dụ tiếp theo ở hàm tính giá trị lớn nhất. Hàm Math.max tính các giá trị lớn nhất của các tham số.

1
2
var max = Math.max(5, 6, 2, 3, 7);
console.log(max); // -> 7

Nếu muốn tính giá trị lớn nhất của mảng, thay vì viết vòng for chúng ta hãy sử dụng hàm Math.max và hàm apply.

1
2
3
4
5
const numbers = [5, 6, 2, 3, 7];

// context là null
var max = Math.max.apply(null, numbers);
console.log(max);  // -> 7

Nhưng cẩn thận với hàm apply khi sử dụng mảng với quá nhiều phần tử, vì chúng sẽ được chuyển sang danh sách tham số. Khi danh sách tham số của hàm quá lớn, chương trình sẽ báo lỗi. Giới hạn số lượng tham số hàm của Webkit Javascript Engine là 65536. Vì khả năng xảy ra lỗi nên chúng ta thường sử dụng hàm apply với ngoại lệ sử dụng try-catch.