网上讲这些的文章数不胜数,之前也看过有理解一些。前段时间在工作中遇到了一个场景又用到了,正好来重新总结一下,整理思路,加强记忆。
参考资料:深入浅出 妙用Javascript中apply、call、bind
call,apply,bind的作用
js在开发的过程中经常会用到this,来表示当前运行环境本身,通过this来取得当前环境下的各种数据和方法。但是,有时候我们想要让a对象借b对象的方法用一下,要怎么办呢?例如:
var person = {
name: '文德嗣',
sayName: function(){
console.log(this.name)
}
}
var person1 = {
name: '马千瞩'
}
//如果我们想让文德嗣叫一下马路对面的马千瞩,咋办呢?
//只要call他一下就可以啦
person.sayName.call(person1)
//同样的,使用apply和bind也可以达到相同的效果:
person.sayName.apply(person1)
//下边会说,为什么bind后边多了个()
person.sayName.bind(person1)()
call,apply,bind的关系
上边的例子我们知道了他们的作用,接下来说说他们的区别:
call和apply的作用是完全一样的,唯一的区别在于他们接受参数的方式。上边的例子只有一个环境参数,所以看不出区别,看下边这个:
var func = function(arg1, arg2) {
//do something
}
func.call(this, arg1, arg2);
func.apply(this, [arg1, arg2])
其中this是你想要指定的上下文环境,就是一开始那个例子中的person。后边有多个参数的时候,call需要按顺序传递参数,而apply只要传递一个参数数组就可以了。
js中参数数量是不定的,所以当参数数量已知固定时,用call,否则用apply,传入数组的长度就随意了。
bind
bind() 方法与 apply 和 call 很相似,其区别在于,call和apply会直接执行方法,文德嗣会当场叫马千瞩。而bind则是生成了一个新方法,不会自动执行。所以在一开始的例子中才会再后边多加一个括号,让他执行。举个例子:
var foo = {
bar : 1,
eventBind: function(){
$('.someClass').on('click',function(event) {
/* Act on the event */
console.log(this.bar); //1
}.bind(this));
}
}
上边绑定了一个新事件,再将时间绑到foo对象上就可以在执行事件函数的时候读取this.bar了。
当多次bind的时候,会怎么样呢?
var bar = function(){
console.log(this.x);
}
var foo = {
x:3
}
var sed = {
x:4
}
var func = bar.bind(foo).bind(sed);
func(); //3
var fiv = {
x:5
}
var func = bar.bind(foo).bind(sed).bind(fiv);
func(); //3
答案是,两次都仍将输出 3 ,而非期待中的 4 和 5 。原因是,在Javascript中,多次 bind() 是无效的。更深层次的原因, bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的。
总结:
- apply 、 call 、bind 三者都是用来改变函数的this对象的指向的;
- apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;
- apply 、 call 、bind 三者都可以利用后续参数传参;
- bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。
工作中的使用场景
项目中有一个table组件用来显示一堆数据,一个delete组件用来删除table中选中的行中的数据。
但是现在,table中有的数据要求无法删除,这就需要在delete执行删除之前,进行判断。而不同的table中,不能删除的数据的范围不同,所以这个判断要放在table组件里边才好根据不同的table来设定判断条件。
这时候,就要在delete组件中,删除前调用其parent(也就是容纳他的table组件)中的deleteConfirmFunc:
//其中this.dodelete是delete组件中的删除方法,作为回调函数传入deleteConfirmFunc
this.parent.deleteConfirmFunc.call(this.parent,this.dodelete)
而在table组件中,编辑deleteConfirmFunc
deleteConfirmFunc: function(data, cb) {
var deleteable = data.deletable
if (deleteable) {
cb.call(this.actionDelete);
} else {
bootbox.alert("无法删除已付款订单!")
}
}
调用cb的时候,再call一下,使用delete的上下文环境。
这样就实现了。