本文翻译自:
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(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 实现函数柯里化.