这篇文章简单的介绍了实现call() , apply() , bind()的思路
实现call(obj,arg,arg....)
将目标函数的this指向传入的第一个对象,参数为不定长,且立即执行
实现思路
- 改变this指向:可以将目标函数作为这个对象的属性
- 利用arguments类数组对象实现参数不定长
- 不能增加对象的属性,所以在结尾需要delete
那么怎么将不定长的参数传递给函数呢?有三种办法:eval,apply,ES6的解构语法
eval('obj.fn('+args+'));
obj.fn.apply(obj,args);
obj.fn(...args);
Function.prototype.mycall = function(obj){
var args = Array.prototype.slice.apply(arguments,[1]);
obj.fn = this;
obj.fn(...args);//es6的解构语法,也可以使用obj.fn.apply(obj,args);
delete obj.fn;
}
使用eval时,eval会将一个字符串解析为变量,所以如果传入的参数为字符串,会报 xxx is not defined,解决办法如下:
Function.prototype.mycall = function(obj){
obj = obj||window;
var args = [];
for(var i = 1 ; i < arguments.length; i++) {
args.push('arguments[' + i + ']');
}
obj.fn = this;
eval('obj.fn('+args+'));
delete obj.fn;
}
实现apply()
与call()只有一个区别,apply第二个参数为数组
Function.prototype.myapply = function(obj,arr){
obj.fn = this;
if(!arr){
obj.fn();
}else{
var args = [];
for(var i = 0; i < arr.length; i++) {
args.push('arr[' + i + ']');
}
eval('obj.fn('+args+')');
}
delete obj.fn;
}
实现bind()
返回一个与被调函数具有相同函数体的新函数,且这个新函数也能使用new操作符。
实现思路
- 返回一个新函数,可以使用闭包
- dind()传入的参数长度不定,使用函数内置的arguments对象数组,可利用Array.prototype.slice.call(arguments,1 )将其转化为数组
- 返回的新函数中,使用apply改变被调用函数的this指向,将arguments转化成的数组作为apply的第二个参数
- 因为返回的新函数也可以使用new操作符,所以在新函数内部需要判断是否使用了new操作符(为什么需要判断,后面会讲解到),如果使用则将apply的第一个参数设置为新创建的对象,如果没有则设置为在调用bind()时所传入的对象(不传的话默认为window)。
需要注意的是怎么去判断是否使用了new操作符呢?在解决这个问题之前,我们先看使用new操作符时具体干了些什么,下面是new操作符的简单实现过程:
//简洁版的new操作符实现过程
function newFunc(constructor){
//第一步:创建一个空对象obj
var obj = {};
//第二步:将构造函数 constructor的原型对象赋给obj的原型
obj.__proto__ = constructor.prototype;
//第三步:将构造函数 constructor中的this指向obj,并立即执行构造函数内部的操作
constructor.apply(obj);
//第四步:返回这个对象
return obj;
}
new操作符的一个过程相当于继承,新创建的构造函数的实例可以访问构造函数的原型链
在new操作符实现过程的第三步中,会将构造函数 constructor中的this指向obj,并立即执行构造函数内部的操作,那么,当在执行函数内部的操作时,如果不进行判断是否使用了new,就会导致 " 将构造函数 constructor中的this指向obj " 这一过程失效,具体原因请看下面的模仿实现bind()的代码:
Function.prototype.testBind = function(object){
var that = this,
args = Array.prototype.slice.call(arguments,1),
bound = function(){
return that.apply(this instanceof fNOP?this:object||window,
args.concat.apply(Array.from(arguments)));
};
//创建一个中转函数fNOP,让bound间接继承目标函数的原型
var fNOP = function(){};
fNOP.prototype= that.prototype;
bound.prototype= new fNOP();
return bound;
}
重点:创建一个中转函数fNOP,让bound间接继承目标函数的原型,一开始我想为什么不直接让 bound.prototype = that.prototype ,后来才发现直接赋值后,bound.prototype和that.prototype指向同一块内容,如果改变bound.prototype就会直接影响that.prototype,使用一个中转函数, bound.prototype= new fNOP()将bound.prototype的__poro__指向fNOP.prototype,然后fNOP.prototype = that.prototype,所以此时改变bound.prototype并不会影响that.prototype。
另外:上面实现bind()的代码中使用apply的地方可以换成原生实现的代码