JavaScript 中至关重要的 Bind

人生代码

共 4921字,需浏览 10分钟

 ·

2021-02-04 21:58

面试官:请你讲讲 js 中 Bind

本文翻译自:

http://javascriptissexy.com/javascript-apply-call-and-bind-methods-are-essential-for-javascript-professionals/#

本来有三部分内容,关于 Bind, Call, Apply。但是我们先拆解成三部分分开写,今天就先讲讲 Bind 方法。

JavaScript 中至关重要的 Bind

我们用 Bind() 来实现在指明函 数内部 this 指向的情况下去调用该函数, 换句话说, bind() 允许我们非常简单的在函数或者方法被调用时绑定 this 到指定对象上.

当我们在一个方法中用到了 this, 而这个方法调用于一个接收器对象, 我们会需要使用到 bind() 方法; 在这种情况下, 由于 this 不一定完全如我们所期待的绑定在目标对象上, 程序有时便会出错;

Bind 允许我们明确指定方法中的 this 指向

当以下按钮被点击的时候, 文本输入框会被随机填入一个名字.

// 
// 

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); 换成以下形式:

$("button").click(user.clickHandler.bind(user));

再考虑另一个方法来修复 this 的值: 你可以给 click() 方法传递一个匿名回调函数, jQuery 会将匿名函数的 this 绑定到按钮对象上.

bind() 函数在 ECMA-262 第五版才被加入;它可能无法在所有浏览器上运行。你可以部份地在脚本开头加入以下代码,就能使它运作,让不支持的浏览器也能使用 bind() 功能。- MDN

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 实现函数柯里化.


浏览 10
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报