js 中 call、apply和bind的区别
call,apply,bind的作用是用来改变this的指向。
示例:
var name = "lucy";
let obj = {
name: "martin",
say: function () {
console.log(this.name);
}
};
obj.say(); //martin,this指向obj对象
setTimeout(obj.say, 0); //lucy,this指向window对象
可以观察到,正常情况下 say
方法中的 this
是指向调用它的 obj
对象的,而定时器 setTimeout
中的 say
方法中的 this
是指向 window
对象的(在浏览器中),这是因为 say
方法在定时器中是作为回调函数来执行的,因此回到主栈执行时是在全局执行上下文的环境中执行的,但我们需要的是 say
方法中 this
指向 obj
对象,因此我们需要修改 this
的指向。
1. call
call
方法的第一个参数也是this
的指向,后面传入的是一个参数列表(注意和apply
传参的区别)。当第一个参数为null
或undefined
的时候,表示指向window
(在浏览器中),和apply
一样,call
也只是临时改变一次this指向,并立即执行。
示例:
var arr = [1,10,5,8,3];
console.log(Math.max.call(null, arr[0], arr[1], arr[2], arr[3], arr[4])); //10
call
以参数列表的形式传入,而apply
以参数数组的形式传入。
2. apply
apply
接受两个参数,第一个参数是 this
的指向,第二个参数数组,且当第一个参数为 null、undefined
的时候,默认指向 window
(在浏览器中),使用 apply
方法改变 this
指向后原函数会立即执行,且此方法只是临时改变 this
指向一次。
示例:
var name = "martin";
var obj = {
name: "lucy",
say: function(year, place){
console.log(this.name + " is " + year + " born from "+place);
}
};
var say = obj.say;
setTimeout(function(){
say.apply(obj, ["1996", "China"])
} ,0); //lucy is 1996 born from China, this 改变指向了obj
say("1996", "China") //martin is 1996 born from China,this指向window,说明apply只是临时改变一次this指向
小技巧:改变参数传递方式
示例:
求数组中的最大值:
var arr = [1,10,5,8,3];
console.log(Math.max.apply(null, arr)); //10
其中Math.ma
x函数的参数是以参数列表,如:Math.max(1,10,5,8,3)
的形式传入的,因此我们没法直接把数组当做参数,但是apply
方法可以将数组参数转换成列表参数传入,从而直接求数组的最大值。
3. bind
bind
方法和call
很相似,第一个参数也是this
的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入,call
则必须一次性传入所有参数),但是它改变this
指向后不会立即执行,而是返回一个永久改变this
指向的函数。
示例:
var arr = [1,10,5,8,12];
var max = Math.max.bind(null, arr[0], arr[1], arr[2], arr[3])
console.log(max(arr[4])); //12,分两次传参
可以看出,bind方法可以分多次传参,最后函数运行时会把所有参数连接起来一起放入函数运行。
4. 实现bind方法
4.1 简易版
Function.prototype.bind=function () {
var _this = this;
var context = arguments[0];
var arg = [].slice.call(arguments, 1);
return function(){
arg = [].concat.apply(arg, arguments);
_this.apply(context, arg);
}
};
4.2 完美版
// 实现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)));
};
// 维护原型关系
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;
};
var arr = [1,11,5,8,12];
var max = Math.max.bind(null,arr[0],arr[1],arr[2],arr[3]);
console.log(max(arr[4])); //12
5. 总结
- 三者都可以改变函数的
this
对象指向。 - 三者第一个参数都是
this
要指向的对象,如果如果没有这个参数或参数为undefined
或null
,则默认指向全局window
。 - 三者都可以传参,但是
apply
是数组,而call
是参数列表,且apply
和call
是一次性传入参数,而bind
可以分为多次传入。 bind
是返回绑定this
之后的函数,便于稍后调用;apply
、call
则是立即执行 。