当前位置: 首页 > 工具软件 > BIND 10 > 使用案例 >

详解bind函数

齐晟
2023-12-01

之前将call和apply的具体实现模拟写了一遍,今天就将bind函数的具体实现来写一遍看看
bind:一句话描述:将创建一个新函数,当这个函数被调用时,传入的第一个参数作为新的this对象,后续的参数作为绑定函数的参数,新函数与被调函数(绑定函数的目标函数)具有相同的函数体。
与call和apply相同点:都可以传递参数
不同点:apply和call是会具体执行该回调函数,而bind是返回一个函数
再补充一点:bind之后无法再次通过call和apply修改this指向
首先,我们实现一个简单的bind绑定

function bindFoo(){
	console.log('My name is ' + this.name);
}
var bindObj = {name : 'gui'}
var foo = bindFoo.bind(bindObj);  //改变this指向,并返回一个新的函数
foo();  //函数执行 输出My name is gui

所以从上面的代码我们可以看出,最重要的一点就是改变this指向,那么如何改变自然就可以想到之前分析过的call和apply来实现
所以通过call和apply的方式,我们可以简单的重写下bind函数

Function.prototype.bindFunc  = function (context){
	var _self = this;  //_self指向bindFoo函数
	//_self = function bindFoo(){ console.log('My name is ' + this.name); }
	return function(){
		// 通过apply来改变this对象
		_self.apply(context); 
	}
}

第二步,我们要和call,apply一致考虑参数的情况

function bindFoo(age, height, weight){
	console.log('My name is ' + this.name + '。age is ' + age + '。height is ' + height + '。weight is ' + weight);
}
var bindObj = {name : 'gui'};
Function.prototype.bindFunc= function(context){
	var _self = this;
	//重点1,先获取从第一个arguments,进行分割,var foo = bindFoo.bind(bindObj, '18')中因为传入的第一个是对象。所以获取从第二个到最后一个的参数
	var args = Array.prototype.slice.call(arguments, 1);
	return function(){
		//重点2,此处获取的arguments是第二次foo('175', '130')中传入的参数,所以不需要从第二个开始获取
		//和call,apply类似,我们也通过arguments来处理
		var args2 = Array.prototype.slice.call(arguments);
		var bindArgs = args.concat(args2);
		_self.apply(context,bindArgs);
	}
}
var foo = bindFoo.bindFunc(bindObj, '18');  //改变this指向,并返回一个新的函数
foo('175', '130'); //函数执行 输出My name is gui。age is 18。height is 175。weight is 130

除了var foo = bindFoo.bindFunc(bindObj, ‘18’); foo(‘175’, ‘130’)这种调用方式,我们还有一种new对象的方式。
new对象的方式就是通过把返回的函数作为新的构造函数,然后新构建一个对象,并将原先的参数传入,如上文中的var foo = bindFoo.bindFunc(bindObj, ‘18’);
我们可以通过var fooObj = new foo(‘175’, ‘130’);的方式将新的参数传入,此时fooObj就作为一个新的对象,在new的时候自然也会相应的执行bindFoo中的方法。
举个例子:

function bindFoo(age, height, weight){
	console.log('My name is ' + this.name + '。age is ' + age + '。height is ' + height + '。weight is ' + weight);
}
var bindObj = {name : 'gui'}
var name = 'sichao';
var foo = bindFoo.bind(bindObj, '18');  //改变this指向,并返回一个新的函数
var fooObj = new foo('175', '130'); // 输出My name is undefined。age is 18。height is 175。weight is 130

重点来了My name is undefined,name输出为undefined了,其实就是因为new新对象的时候,bindFoo中的this已经指向fooObj了,而fooObj中并没有name值,所以就是undefined
所以针对这种情况,再写一版:

Function.prototype.bindFunc = function(context){
	var _self = this;
	var args = Array.prototype.slice.call(arguments, 1);
	var bindFunc = function(){
		var args2 = Array.prototype.slice.call(arguments, 0);
		var bindArgs = args.concat(args2);
		/* 第二步:
		当作为构造函数new foo('175', '130')时,this 指向实例,因为第一步中bindFunc.prototype = this.prototype,
		所以结果为true,当结果为 true 的时候,this 指向实例。
        当作为普通函数foo('175', '130')时,this 指向 window,bindFunc指向绑定函数,
        此时结果为 false,当结果为 false 的时候,this 指向绑定的 context。*/
		var bindThis = this instanceof bindFunc?this :context;
		_self.apply(bindThis, bindArgs);
	}
	//第一步:先将返回函数bindFunc的原型对象修改为绑定函数bindFoo的原型对象 
	//实例就可以继承绑定函数的原型中的值
	bindFunc.prototype = _self.prototype;
	return bindFunc;
}

这样写又会有一个问题,那就是我们修改bindFunc.prototype,会直接影响绑定函数的prototype
所以需要通过一个空函数来进行中转,就是利用了原型链继承的方式:

Function.prototype.bindFunc = function(context){
	var _self = this;
	var args = Array.prototype.slice.call(arguments, 1);
	var turnFunc = function(){};
	var bindFunc = function(){
		var args2 = Array.prototype.slice.call(arguments, 0);
		var bindArgs = args.concat(args2);
		var bindThis = this instanceof bindFunc?this:context;
		_self.apply(bindThis, bindArgs);
	}
	turnFunc.prototype = _self.prototype;
	bindFunc.prototype = new turnFunc();
	return bindFunc;
}

好了,最后的最后,我们再兼容一下绑定函数不正确的情况:

Function.prototype.bindFunc = function(context){
	if(typeof this != 'function'){
		console.log(typeof this);
		throw new Error("not function!");
	}
	var _self = this;
	var args = Array.prototype.slice.call(arguments, 1);
	var turnFunc = function(){};
	var bindFunc = function(){
		var args2 = Array.prototype.slice.call(arguments, 0);
		var bindArgs = args.concat(args2);
		var bindThis = this instanceof bindFunc?this:context;
		_self.apply(bindThis, bindArgs);
	}
	turnFunc.prototype = _self.prototype;
	bindFunc.prototype = new turnFunc();
	return bindFunc;
}
 类似资料: