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