Bind, Apply and Call trong javascript
Trước tiên để hiểu bài này bạn cần tìm hiểu “this” trong javascript , javascript objects
Như các bạn đã biết trong javascript không có class mà chỉ có các functions.
Một function cũng là 1 object trong javascript. Và cũng giống như object , function sẽ có các methods, chưa các phương thức mạnh mẽ như “Apply”, “Call”, “Bind”. 3 hàm này chúng ra thường ít dùng tuy nhiên chúng tạo nên sự mạnh mẽ và bá đạo của javascript.
Chúng ta bắt đầu tìm hiểu các method này nào
Javascript Bind Method
Bind là một function nằm trong function prototype do đó chỉ có function mới có thể gọi dc nó. Chúng ta gọi Bind method dùng để xác định tham số “this” cho một function.
bind() cho phép chúng ta dễ dàng thiết lập một đối tượng cụ thể sẽ bị ràng buộc này khi một chức năng hoặc phương pháp được gọi.
Các tác dụng của bind
- Cho pheps chúng ta set giá trị của biến "this"
ví dụ: https://jsfiddle.net/knz9fv5c/
<input id ="click" type="button" value = "Click"/> <input id ="usingBind" type = "button" value = "UsingBind"/> <br/> <span id = "showInfo"></span> var user = { name: "A", age: "35", showInfo: function(){ $("#showInfo").text("This is " + this.name + "-" + this.age + "years old" ) }
} $("#click").click(user.showInfo);
$("#usingBind").click(user.showInfo.bind(user));
Mục đích trong ví dụ này ta sẽ xem xet bind có thể set giá trị “this” trong object như nào. Ta có 1 object user chứa function showInfo có nhiệm vụ hiển thị thông tin của object user đó thông qua biến “this”
Ở trường hợp đầu tiên khi không sử dụng bind ta có thể thấy khi click vào button giá trị “this” trong hàm showInfo không được gọi ( bằng “undefined”) .
Ở trường hợp thứ 2 khi ta dùng bind để set giá trị this của user. Lúc này ta có thể bind giá trị của user với this. Kết quả ta thấy show được thông tin của user rồi đó .
Một trường hợp khác với global và local variable. Ta có ví dụ tương tự như sau:
var name = "xxx";
var user = { name: "yyy", showInfo: function(){ console.log("Name:" + this.name); }
} var showData= user.showInfo;
var showDataBind = user.showInfo.bind(user)
showData() // call global data : return Name: xxx
showDataBind() // call local data: return Name: yyy
Ta thấy nếu ko sử dụng bind thì biến global data được gọi còn nếu sử dụng bind sẽ set giá trị cho this nên biến local sẽ được gọi.
- Cho phép tạo một Curry Function
Không chỉ bind được giá trị “this” , hàm bind còn có thể bind được các tham số truyền vào cho hàm . Tức là ta có thể tao ra một function mới từ function cũ bằng cách gắn mặc định một tham số cho function cũ đó . Đó là cách tạo một Curry Function
Giả sử mình có một hàm log đơn gilản có 3 tham số truyền vào như sau:
function log(level, time, msg){ console.log(level + “-” + time +”:” + msg);
}
Bây h mình muốn tạo một hàm log acces ngày hôm nay mình sẽ tạo ra sử dụng hàm cũ như sau:
function logAccessToday(msg){ log(“Access”, “Today”, msg);
}
Ta có thể tạo function mới bằng cách sử dụng bind bằng cách gán mặc định 2 tham số đầu tiên như sau:
var logAccessToday = log.bind(“Access”, “Today”, msg);
Khi gọi logAccessToday(“Server Access”) đều ra một thông báo như nhau.
Function Apply và Call trong javascript
Hai function này đều đều nằm trong function prototype cho nên chỉ có function mới có thể gọi được
Chức năng chung của chúng là xác định một tham số, xác định this và truyền các tham số còn lại vào.
Điểm khác nhau cơ bản giữa chúng là apply sẽ truyền 1 array các tham số còn call sẽ truyền lần lượt các tham số.
Cú pháp :
call()
Function.prototype.call(thisArg[, arg1[ , arg2, …]])
apply()
Function.prototype.apply(thisArg, argArray)
Ta sẽ cùng xem xet ví dụ sau:
var obj = { firstName: "Ahihi", lastName : "Ihaha", mMethod: function(firstName, lastName) { var firstName = firstName || this.firstName var lastName = lastName || this.lastName console.log("Hello " + firstName + " " + lastName) }
} var obj1 = { firstName: “xxx", lastName : "yyy"
}; obj.mMethod() // Hello Ahihi Ihaha obj.mMethod.call(obj1) // Hello xxx yyy obj.mMethod.apply(obj1) // Hello xxx yyy obj.mMethod.call(obj1, "xxx", "yyy") // Hello xxx yyy obj.mMethod.apply(obj1, ["xxx", "yyy"]) // Hello xxx yyy
Ngoài ra apply còn có thể truyền tham số tựa như mảng như sau:
obj.mMethod.apply(obj1, {'length': 2, '0': "xxx", '1': "yyy"}) // Hello xxx yyy
Như ta thấy ta có thể truyền 1 object tương tự mảng.
- Sử dụng call , apply để set this cho hàm callback Ví dụ:
function print() { console.log(this.mVal)
} var obj = { mVal: "lalala", mMethod: function(callback) { // truyền đối tượng hiện tại cho hàm phản hồi callback
callback.call(this) }
} obj.mMethod(print) //sẽ in ra lalala
Trong ví dụ trên hàm call đã set biến this cho hàm callback nên khi gọi hàm print biến this được gọi chính là obj. Nếu không gọi call , this.mVal sẽ chỉ là undefnied . Với apply cũng tương tự như vậy
- Sử dụng để mượn hàm (borrowing function)
Xem ví dụ sau:
function test(firstParam, secondParam, thirdParam){
var args = Array.apply(null, arguments); console.log(args); } test(1, 2, 3); // [1, 2, 3]
Arguments là một object giống array nhưng không phải là array . Arguments giống array vì nó có fieldlength, có thể truy cập các giá trị nó chứa thông qua index 0,1,2. Tuy nhiên, do arguments không phải là array nên nó không thể gọi các hàm của Array.prototype. Do đó, ta phải sử dụng call/apply để mượn một số hàm trong Array.prototype, các hàm này sẽ trả ra một array cho ta xử lý. Dòng code phía trên chuyển object arguments thành một array
Ví dụ tiếp
function transitionTo (name) { var args = Array.prototype.slice.call (arguments, 1); console.log (args); } transitionTo ("contact", "Today", "20"); // ["Today", "20"]
Tuơng tự ở đây arguments không phải là 1 array nên ta dùng call để mượn hàm trong Array prototype để ta sử lý. Khi đó hàm slice sẽ gọiđược
- Sử dụng để mở rộng chức năng của hàm mà ko cần phải sửa trực tiếp hàm cũ
Ví dụ
var user = { name: “XXX”, showName: function (){ console.log(“My name is:” + this.name); }
}
user.showName() // My name is XXX
Bây h ta mở rộng bằng bằng console.log ra 2 dòng trước và sau khi gọi showName bằng hàm sau:
var oldShowName = user.showName.bind(user);
user.showName = function(){ // ở đây ta thay đổi hàm showName bằng hàm mới console.log(“before show name”); oldShowName.call(this); // giữ nguyên hàm cũ console.log(“after show name”); } user.showName();
Kết quả ta thấy 2 console.log đc tạo → việc mở rộng hàm thành công rồi đó
Kết luận
Bằng việc sử dụng call, apply và bind ta có thể thay đổi được ngữ cảnh thực thi (phạm vi chứa hàm) để sử dụng một hàm với công dụng đa năng hơn như thực thi cho một đối tượng, phạm vi khác khác giúp ta có thể tận dụng tối đa mã nguồn được đã tạo ra, hay tạo shortcut cho hàm, linh hoạt hơn tham số đầu vào. Với call và apply chúng ta sử dụng để thực thi hàm đó luôn khi gọi, còn với bindta có thể thực thi hàm đó nhiều lần sau khi đã được buộc (bind) với một ngữ cảnh nhất định.