JavaScript 中相當重要的 Bind

面試官:請你講講 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(arguments1), 
        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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索