面試官:請你講講 js 中 Bind
本文翻譯自:javascript
http://javascriptissexy.com/javascript-apply-call-and-bind-methods-are-essential-for-javascript-professionals/#
原本有三部份內容,關於 Bind, Call, Apply。可是咱們先拆解成三部分分開寫,今天就先講講 Bind 方法。java
JavaScript 中相當重要的 Bind
咱們用 Bind() 來實如今指明函 數內部 this 指向的狀況下去調用該函數, 換句話說, bind() 容許咱們很是簡單的在函數或者方法被調用時綁定 this 到指定對象上.web
當咱們在一個方法中用到了 this, 而這個方法調用於一個接收器對象, 咱們會須要使用到 bind() 方法; 在這種狀況下, 因爲 this 不必定徹底如咱們所期待的綁定在目標對象上, 程序有時便會出錯;面試
Bind 容許咱們明確指定方法中的 this 指向
當如下按鈕被點擊的時候, 文本輸入框會被隨機填入一個名字.數組
// <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; // random number between 0 and 1
// 從 data 數組中隨機選取一個名字填入 input 框內
$("input").val(this.data[randomNum].name + " " + this.data[randomNum].age);
}
}
// 給點擊事件添加一個事件處理器
$("button").click(user.clickHandler);
當你點擊按鈕時, 會發現一個報錯信息: 由於 clickHandler() 方法中的 this 綁定的是按鈕 HTML 內容的上下文, 由於這纔是 clickHandler 方法的執行時的調用對象.瀏覽器
在 JavaScript 中這種問題比較常見, JavaScript 框架中例如 Backbone.js, jQuery 都自動爲咱們作好了綁定的工做, 因此在使用時 this 老是能夠綁定到咱們所指望的那個對象上.微信
爲了解決以前例子中存在的問題, 咱們利用 bind() 方法將 $("button").click(user.clickHandler); 換成如下形式:app
$("button").click(user.clickHandler.bind(user));
再考慮另外一個方法來修復 this 的值: 你能夠給 click() 方法傳遞一個匿名回調函數, jQuery 會將匿名函數的 this 綁定到按鈕對象上.框架
bind() 函數在 ECMA-262 第五版才被加入;它可能沒法在全部瀏覽器上運行。你能夠部份地在腳本開頭加入如下代碼,就能使它運做,讓不支持的瀏覽器也能使用 bind() 功能。- MDNdom
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this, // 此處的 this 指向目標函數
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP
? this // 此處 this 爲 調用 new obj() 時所生成的 obj 自己
: oThis || this, // 若 oThis 無效則將 fBound 綁定到 this
// 將經過 bind 傳遞的參數和調用時傳遞的參數進行合併, 並做爲最終的參數傳遞
aArgs.concat(Array.prototype.slice.call(arguments)));
};
// 將目標函數的原型對象拷貝到新函數中,由於目標函數有可能被看成構造函數使用
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
繼續以前的例子, 若是咱們將包含 this 的方法賦值給一個變量, 那麼 this 的指向也會綁定到另外一個對象上, 以下所示:
// 全局變量 data
var data = [
{name:"Samantha", age:12},
{name:"Alexis", age:14}
]
var user = {
// 局部變量 data
data :[
{name:"T. Woods", age:37},
{name:"P. Mickelson", age:43}
],
showData:function(event) {
var randomNum = ((Math.random () * 2 | 0) + 1) - 1; // random number between 0 and 1
console.log(this.data[randomNum].name + " " + this.data[randomNum].age);
}
}
// 將 user 對象的 showData 方法賦值給一個變量
var showDataVar = user.showData;
showDataVar(); // Samantha 12 (來自全局變量數組而非局部變量數組)
當咱們執行 showDataVar() 函數時, 輸出到 console 的數值來自全局 data 數組, 而不是 user 對象. 這是由於 showDataVar() 函數是被當作一個全局函數執行的, 因此在函數內部 this 被綁定位全局對象, 即瀏覽器中的 window 對象.
來, 咱們用 bind 方法來修復這個 bug.
// Bind the showData method to the user object
var showDataVar = user.showData.bind(user);
Bind 方法容許咱們實現函數借用
在 JavaScript 中, 咱們能夠傳遞函數, 返回函數, 借用他們等等, 而 bind() 方法使函數借用變得極其簡單. 如下爲一個函數借用的例子:
// cars 對象
var cars = {
data:[
{name:"Honda Accord", age:14},
{name:"Tesla Model S", age:2}
]
}
// 咱們從以前定義的 user 對象借用 showData 方法
// 這裏咱們將 user.showData 方法綁定到剛剛新建的 cars 對象上
cars.showData = user.showData.bind(cars);
cars.showData(); // Honda Accord 14
這裏存在一個問題, 當咱們在 cars 對象上添加一個新方法(showData)時咱們可能不想只是簡單的借用一個函數那樣, 由於 cars 自己可能已經有一個方法或者屬性叫作 showData 了, 咱們不想意外的將這個方法覆蓋了. 正如在以後的 Apply 和 Call 方法 章節咱們會介紹, 借用函數的最佳實踐應該是使用 Apply 或者 Call 方法.
Bind 方法容許咱們柯里化一個函數
柯里化的概念很簡單, 只傳遞給函數一部分參數來調用它, 讓它返回一個函數去處理剩下的參數. 你能夠一次性地調用 curry 函數, 也能夠每次只傳一個參數分屢次調用, 如下爲一個簡單的示例.
var add = function(x) {
return function(y) {
return x + y;
};
};
var increment = add(1);
var addTen = add(10);
increment(2);
// 3
addTen(2);
// 12
如今, 咱們使用 bind() 方法來實現函數的柯里化. 咱們首先定義一個接收三個參數的 greet() 函數:
function greet(gender, age, name) {
// if a male, use Mr., else use Ms.
var salutation = gender === "male" ? "Mr. " : "Ms. ";
if (age > 25) {
return "Hello, " + salutation + name + ".";
}
else {
return "Hey, " + name + ".";
}
}
接着咱們使用 bind() 方法柯里化 greet() 方法. bind() 接收的第一個參數指定了 this 的值:
// 在 greet 函數中咱們能夠傳遞 null, 由於函數中並未使用到 this 關鍵字
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."
當咱們用 bind() 實現柯里化時, greet() 函數參數中除了最後一個參數都被預約義好了, 因此當咱們調用柯里化後的新函數時只須要指定最後一位參數.
因此小結一下, bind() 方法容許咱們明確指定對象方法中的 this 指向, 咱們能夠借用, 複製一個方法或者將方法賦值爲一個可做爲函數執行的變量. 咱們以能夠借用 bind 實現函數柯里化.
本文分享自微信公衆號 - 人生代碼(lijinwen1996329ken)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。