一、bind的概念
由MDN定义可知:bind()
方法创建一个新的函数,在bind()
被调用时,这个新函数的this
被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。
例子:
var module = {
x: 42,
getX: function() {
return this.x;
}
}
console.log(module.getX()); // 输出42
var unboundGetX = module.getX;
console.log(unboundGetX()); // The function gets invoked at the global scope
// expected output: undefined
var boundGetX = unboundGetX.bind(module);
console.log(boundGetX()); // expected output: 42
我们与apply()和call()在例子中对比一下: 可见bind与另两者的区别在于它返回的是原函数而并非带参执行原函数。
function foo(x,y) {
console.log(x, y, this)
}
foo.call(100, 1, 2) // 1,2,Number {100}
foo.apply(true, [3,4]) // 3,4,Boolean {true}
foo.apply(null) // undefined,undefined,window
foo.apply(undefined) // undefined,undefined,window
foo.bind(true) // 返回foo函数
foo.bind(null) // 同上
foo.bind(null)() // undefined,undefined,window
foo.bind(true)() // undefined undefined Boolean {true}
foo.bind()() // undefined,undefined,window
二、bind的作用:改变this指向、对参数柯里化
2.1 改变this指向(同call、apply)
2.2 对参数进行柯里化
function add(a,b,c) {
return a + b + c
}
var fun1 = add.bind(undefined, 10) // 实际上返回add(10, b, c) {..}函数
fun1(1,2) // 执行add(10, 1, 2) {..}函数得到13
var fun2 = fun1.bind(undefined, 20) // 实际上返回add(10, 20, c) {..}函数
fun2(10) // 执行add(10,20,10)得到40
实际工程中类似的使用实例:
比如定义一个可传参决定颜色、大小、其他选项的配置函数,但颜色和大小是公共配置基本通用的就不用每次都往传参里写,为里方便重用通用配置项,借助bind的curry功能我们就可以默认使用“标配”,执行时只用传最后一个参数otherOptions就行。
function getConfig(colors, size, otherOptions) {
console.log(colors, size, otherOptions);
}
var defaultConfig = getConfig.bind(null, "#fff", "1024*768")
defaultConfig("123") // #fff, 102*768, 123
defaultConfig("2333") // #fff, 102*768, 2333
三、手写实现bind功能
我们可以通过一个示例来试试看原生的bind
对于使用new
的情况是如何的:
function animal(name) {
this.name = name
}
var obj = {}
var cat = animal.bind(obj)
cat('lily')
console.log(obj.name) //lily
var tom = new cat('tom')
console.log(obj.name) //lily
console.log(tom.name) //tom
试验结果发现,obj.name
依然是lily
而没有变成tom
,所以就像MDN描述的那样,如果绑定函数cat
是通过new
操作符来创建实例对象的话,this
会指向创建的新对象tom
,而不再固定绑定指定的对象obj
。
由于 bind()是ES5新增的方法,故仅支持IE9以上及Chrome等现代浏览器,在老版本浏览器想要实现需自己Polyfill 。
下面是MDN的Polyfill:
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,
fNOP = function() {},
fBound = function() {
// this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
return fToBind.apply(this instanceof fBound
? this
: oThis,
// 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
aArgs.concat(Array.prototype.slice.call(arguments)));
};
// aArgs就是调用bind时传入的初始化参数(剔除了第一个参数oThis)。将aArgs与绑定函数执行时的实参arguments通过concat连起来作为参数传入,就实现了bind函数初始化参数的效果。
// 维护原型关系
if (this.prototype) {
// 当执行Function.prototype.bind()时, this为Function.prototype
// this.prototype(即Function.prototype.prototype)为undefined
fNOP.prototype = this.prototype;
}
// 下行的代码使fBound.prototype是fNOP的实例,因此
// 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
fBound.prototype = new fNOP();
return fBound;
};
}
四、apply、call 实现
apply 的实现:
Function.prototype.myApply= function(context){
context = context || window; // 参数默认值并不会排除null,所以重新赋值
context.fn = this; //1.将函数挂载到传入的对象
var arg = [...arguments].splice(1)[0]; //2.取参数
if(!Array.isArray(arg)) {
throw new Error('apply的第二个参数必须是数组') //3.限制参数类型为数组
}
context.fn(arg) //4.执行对象的方法
delete context.fn; //5.移除对象的方法
}
var obj = {
name:'obj'
}
function sayName(arr){
console.log(this.name,arr)
}
sayName.myApply(obj,[1,2,3]) //obj [1, 2, 3]
call实现:与apply的唯一区别就是参数格式不同
Function.prototype.myCall= function(context){
context = context || window; // 参数默认值并不会排除null,所以重新赋值
context.fn = this;//1.将函数挂载到传入的对象
var arg = [...arguments].splice(1);//2.取参数
context.fn(...arg) //3.执行对象的方法
delete context.fn; //4.移除对象的方法
}
var obj = {
name:'obj1'
}
function sayName(){
console.log(this.name,...arguments)
}
sayName.myCall(obj,1,2,3,5) //obj1 1,2,3,5
本文引用参考:
Function.prototype.bind()developer.mozilla.org