Bind - Call - Apply method in Javascript
Bind thường được sử dụng để gọi một fuction và truyền this vào một cách tường minh. Hay nói cách khác bind cho phép chúng ta gán một object cụ thể cho this khi hàm hoặc phương thức được gọi. Bind method thực sự cần thiết khi chúng ta sử dụng this trong method và gọi phương thức đó từ một receiver ...
Bind thường được sử dụng để gọi một fuction và truyền this vào một cách tường minh. Hay nói cách khác bind cho phép chúng ta gán một object cụ thể cho this khi hàm hoặc phương thức được gọi.
Bind method thực sự cần thiết khi chúng ta sử dụng this trong method và gọi phương thức đó từ một receiver object. Trong một số trường hợp this không được bind tới object mà chúng ta muốn dẫn tới errors.
I.1. Gán this thông qua Bind
Xét ví dụ sau:
// <button>Get Random Person</button> // <input type="text"> var user = { data :[ {name:"T. Woods", age:37}, {name:"P. Mickelson", age:43} ], clickHandler:function (event) { var randomNum = ((Math.random () * 2 | 0) + 1) - 1; // lấy random 0 hoặc 1 // lấy random user từ array và add vào text field $ ("input").val (this.data[randomNum].name + " " + this.data[randomNum].age); } } $ ("button").click (user.clickHandler);
Khi click vào button errors sẽ xuất hiện trên màn hình console bởi vì this ở trong method clickHandler() này được bind tới HTML button.
"TypeError: Cannot read property '1' of undefined at HTMLButtonElement.clickHandler (fawixev.js:10:35) at HTMLButtonElement.dispatch (https://code.jquery.com/jquery-1.9.1.js:3074:9) at HTMLButtonElement.elemData.handle (https://code.jquery.com/jquery-1.9.1.js:2750:28)"
Để fix lỗi này chỉ cần bind this ở method clickHandler()
$ ("button").click (user.clickHandler.bind(user));
Xem kết quả tại đây https://jsbin.com/fawixev/edit?html,js,console,output
this cũng được bind tới một object khác nếu ta gán method ( method này có sử dụng this) cho một biến. Xét tiếp ví dụ bên dưới
// global variable var data = [ {name:"Samantha", age:12}, {name:"Alexis", age:14} ] var user = { // local data variable data :[ {name:"T. Woods", age:37}, {name:"P. Mickelson", age:43} ], showData:function (event) { var randomNum = ((Math.random () * 2 | 0) + 1) - 1; console.log (this.data[randomNum].name + " " + this.data[randomNum].age); } } // gán showData method của user object cho một biến khác. var showDataVar = user.showData; showDataVar (); // Samantha 12 (từ global data array, không phải từ local data array)
Khi thực thi showDataVar() function thì giá trị được in ra lấy từ global data array không lấy từ mảng data bên trong user object. Điều này xảy ra là vì showDataVar() được thực thi như một global fuction và sử dụng this bên trong function đó được tự bind không tường minh từ global scope (window object của browser).
Tương tự như ví dụ trên để fix được lỗi này ta chỉ cần bind tới đúng object cần gọi.
// bind showData method tới user object. var showDataVar = user.showData.bind (user); `this` lúc này được bind tường minh tới user object chứ không phải object từ global scope nữa. showDataVar (); // P. Mickelson 43
Chi tiết xem tại đây https://jsbin.com/pucubey/3/edit?html,js,console,output
I.2. Mượn phương thức khác thông qua bind
var cars = { data:[ {name:"Honda Accord", age:14}, {name:"Tesla Model S", age:2} ] } cars.showData = user.showData.bind (cars); cars.showData (); // Honda Accord 14
Như ví dụ trên cars object không có phương thức print, ta có thể mượn nó từ user object https://jsbin.com/pucubey/edit?html,js,console,output
Có một vấn đề chúng ta gặp phải khi sử dụng mượn phương thức của object khác thông qua bind của cars object, thực ra ta đã add thêm một phương thức mới cho cars object. Giả xử ta chỉ muốn mượn phương thức showData chứ không override nó thì có cách nào khác không? Câu trả lời là Apply và Call có thể giúp ta thực hiện việc mượn phương thức.
I.3. Bind với curry function
Khái niệm về curry function hay partial fuction application bạn có thể tham khảo dưới đây: Theo wiki định nghĩa https://en.wikipedia.org/wiki/Partial_application
In computer science, partial application (or partial function application) refers to the process of fixing a number of arguments to a function, producing another function of smaller arity.
Hoặc follow bài viết chi tiết về partial functin application tại đây https://viblo.asia/p/partial-application-function-trong-js-1Je5EMbw5nL
function greet (gender, age, name) { var salutation = gender === "male" ? "Mr. " : "Ms. "; if (age > 25) { return "Hello, " + salutation + name + "."; } else { return "Hey, " + name + "."; } } // chúng ta truyền null vì không muốn sử dụng this của greet function var greetAnAdultMale = greet.bind (null, "male", 45); greetAnAdultMale ("John Hartlove"); // "Hello, Mr. John Hartlove." var greetAYoungster = greet.bind (null, "", 16); greetAYoungster ("Alex"); // "Hey, Alex." greetAYoungster ("Emma Waterloo"); // "Hey, Emma Waterloo."
Khi sử dụng bind method theo kiểu curry, 2 tham số đầu đã được gán trước ngoại trừ tham số cuối name, ta có thể gọi hàm greet và truyền name dynamic hơn.
Tóm lại với bind ta có thể :
- Set giá trị this khi gọi phương thức của object
- Có thể mượn và copy phương thức
- Có thể gán method cho biến.
- Có thể sử dụng với Curry function
Apply và Call là 2 function thường được dùng cho việc mượn fuction và gán this value cho hàm được gọi. Thêm nữa apply function cho phép thực thi hàm với một mảng các parameters,
II.1. Gán this thông qua apply hoặc call method
Cũng giống như bind, ta có thể gán this khi gọi phương thức Apply or Call, nó chính là tham số đầu tiên của call và apply method.
var avgScore = "global avgScore"; function avg (arrayOfScores) { var sumOfScores = arrayOfScores.reduce (function (prev, cur, index, array) { return prev + cur; }); // this ở đây sẽ được bind bởi global object nếu ta không gán this với call hoặc apply this.avgScore = sumOfScores / arrayOfScores.length; } var gameController = { scores :[20, 34, 55, 46, 77], avgScore:null } avg (gameController.scores); console.log (window.avgScore); // 46.4 console.log (gameController.avgScore); // null avgScore = "global avgScore"; // gán this thông qua call method, this lúc này là gameController avg.call (gameController, gameController.scores); console.log (window.avgScore); //global avgScore console.log (gameController.avgScore); // 46.4
Tham số đầu tiên của call method được gán cho this và nó được gán cho gameContorller object. Những tham số còn lại được truyền vào cho avg() function. Apply và call method có thể coi là tương tự nhau khi ta gán this cho method. Ngoại trừ việc truyền các tham số đối với apply thì là mảng các tham số còn đối với call là một list các tham số riêng biệt được truyền vào.
II.2. Sử dụng Call hoặc Apply để gán this trong callback function
var clientData = { id: 094545, fullName: "Not Set", setUserName: function (firstName, lastName) { this.fullName = firstName + " " + lastName; } }
function getUserInput (firstName, lastName, callback, callbackObj) { callback.apply (callbackObj, [firstName, lastName]); }
Apply method gán this cho callbackObj, điều này cho phép chúng ta thực thi callback funtion với this một cách tường minh, Những tham số được truyền vào callback function sẽ được gán vào clientData object:
// clientData được sử dụng để gán cho `this` của callback function `setUserName` getUserInput ("Barack", "Obama", clientData.setUserName, clientData); console.log (clientData.fullName); // Barack Obama
II.3. Mượn phương thức với Apply và Call
Cũng giống như bind ta có thể mượn method của object khác thông qua apply và call nhưng có một vài điểm hơi khác một chút Xét ví dụ
var gameController = { scores :[20, 34, 55, 46, 77], avgScore:null, players :[ {name:"Tommy", playerID:987, age:23}, {name:"Pau", playerID:87, age:33} ] } var appController = { scores :[900, 845, 809, 950], avgScore:null, avg :function () { var sumOfScores = this.scores.reduce (function (prev, cur, index, array) { return prev + cur; }); this.avgScore = sumOfScores / this.scores.length; } } appController.avg.apply (gameController); console.log (gameController.avgScore); // 46.4 console.log (appController.avgScore); // null
gameController object mượn phương thức avg() của appController, this lúc này được gán cho gameController object nên khi gọi câu lệnh console.log (appController.avgScore) kết quả vẫn là null.
http://javascriptissexy.com/javascript-apply-call-and-bind-methods-are-essential-for-javascript-professionals/