在js中,call(),apply()和bind()是function自带的三个函数,这三个函数的作用都是用来改变this指向的。接下来,我们就详细理解一下这几个函数的用法。
共同点和区别
call(),appy(),bind()这几个方法的共同点就是改变this指向。
call()和apply()的用法:
看例子
function A(x, y){
console.log(x,y);
console.log(this);
console.log(arguments);
}
var C = {};
A.call(C,5,6); //5 6 {} arguments = [5,6]
A.apply(C,[5,6]); //5 6 {} arguments = [5,6]
从例子也可以看出,原来A函数的this指向是全局,但是通过call()和apply()调用之后,this指向是空对象C。
call()和apply()的用法只有一点不同,就是传参列表不同,其它完全相同。
再看例子
function person(age, male){
this.age = age;
this.male = male;
}
var person1 = {};
person.call(person1,20, "female"); //person1 = {age: 20, male: "female"}
person.apply(person1,[20,"female"]); //person1 = {age: 20, male: "female"}
从返回结果看,person1空对象通过调用call()和apply()函数为自己添加了age和male属性。
var person = {
age: 20,
male: "female",
sayAge: function(){
console.log("my age is"+this.age+" my male is "+this.male);
}
};
var person1 = {
age: 30,
male: "male"
};
person.sayAge.call(person1); //my age is30 my male is male
person.sayAge.apply(person1); //my age is30 my male is male
再来看bind()的用法,上面讲的无论是call()还是apply()都是立马就调用了对应的函数,但是bind()不会,bind()会生成一个新的函数并返回这个新函数,在以后想什么调用这个新函数都可以,bind()的传参形式和call()一致,第一个参数也是会绑定this的值,后面接受传递给函数的不定参数。
接下来看代码
var person = {
age: 20
};
var age = 30;
function sayAge (male){
var age = 50;
console.log(this.age + " "+male);
}
sayAge("male"); //30 male 函数中的this指向window
var foo = sayAge.bind(person,"female"); //20 female
sayAge.call(person,"female"); //20 female
sayAge.apply(person,["female"]);
foo(); //20 female
方法的实现
bind()方法的实现:
bind()方法有两条准则:
1.会返回一个函数
2.可以传参,既可以给bind()函数传参,又可以给返回函数传参。
首先满足第一个条件
Function.prototype.myBind = function(contex){
var self = this; //谁调用该函数this就指向谁
return function(){
self.apply(contex);
}
}
然后满足参数条件
Function.prototype.myBind = function(contex){
var self = this; //谁调用这个函数this就指向谁
var args = Array.prototype.slice.call(arguments,1); //去掉第一个参数,第一个参数表示是contex
//类数组转换成了数组
return function(){ //参数是两个函数传参的拼接
var arguments = Array.prototype.slice.call(arguments);
var newArgs = args.concat(arguments);
self.apply(contex,newArgs);
};
};
另外,返回函数还可以当构造函数使用,因此,还需要加入当构造函数使用时的程序:
Function.prototype.myBind = function(contex){
var self = this; //这里的this指向绑定函数
var args = Array.prototype.slice.call(arguments,1); //去掉第一个参数,第一个参数表示是contex
//类数组转换成了数组
var fbound = function(){ //参数是两个函数传参的拼接
var arguments = Array.prototype.slice.call(arguments);
var newArgs = args.concat(arguments);
//当作为构造函数时,this指向实例,self指向绑定的函数。
//当作为普通函数时,this指向window,self指向绑定函数。
self.apply(this instanceof self ? this : contex, newArgs);
};
fbound.prototype = this.prototype; //修改返回函数的prototype为绑定函数的prototype,实例就可以继承函数原型中的值,至此就形成了继承的关系。所以,当作为构造函数使用时,this指向实例,self指向绑定函数,结果返回true;相反,如果没有new的话,表示返回函数只是普通函数调用,这时,this指向window,self指向绑定函数,所以,返回false.
return fbound;
};
用call()方法实现bind();
Function.prototype.myBind = function(contex){
var self = this;
var args = Array.prototype.slice.call(arguments,1);
var fbound = function(){
var arguments = Array.prototype.slice.call(arguments);
var newArgs = args.concat(arguments);
self.call(this instanceof self ? this : contex, ...newArgs);
};
fbound.prototype = this.prototype;
return fbound;
};
call()方法的实现:
基本实现过程分为三步:
- 为对象添加方法。
- 方法执行。
- 删除方法。
程序:
Function.prototype.myCall = function(context){
context.fn = this; //这个this指向调用的函数,为对象添加一个函数
//为这个函数传参
var args = Array.prototype.slice.call(arguments,1); //将类数组转化为了数组,且不包含第一个参数
console.log(args);
eval( 'context.fn('+ args + ')'); //字符串拼接成一个语句
delete context.fn;
};
还可以用es6的方法:
Function.prototype.myCall = function(context){
context.fn = this; //这个this指向调用的函数,为对象添加一个函数
//为这个函数传参
var args = Array.prototype.slice.call(arguments,1); //将类数组转化为了数组,且不包含第一个参数
console.log(args instanceof Array);
context.fn( ...args ); //...扩展运算符将数组转化为用逗号分隔的序列
delete context.fn;
};
Person.myCall(bar,"wang",18);
apply的实现
Function.prototype.myApply = function(context){
context.fn = this; //这个this指向调用的函数,为对象添加一个函数
//为这个函数传参
var args = [];
for (var i = 0; i < arguments[1].length; i++){
args[i] = arguments[1][i];
}
context.fn( ...args );
delete context.fn;
};