call,apply and bind in JavaScript
在ECMAScript中,每个函数都包含两个继承而来的方法:apply() 和 call(),这两个方法的用途都是在特定的作用域中调用函数,主要作用跟bind一样,用来改变函数体内this的指向,或者说是在函数调用时改变上下文。
文章尽量使用大量实例进行讲解,它们的使用场景。同时,也会由浅入深的引导出一些理论,毕竟这几个常用方法,在MDN上都能找到合理的解释
基本功能
改变this的指向
1 | var fruit = { |
当 getFruit 并非作为一个对象的属性,而是直接当做一个函数来调用,里面的this
就会被绑定到全局对象上,即window上, 所以直接调用 getFruit
,里面的this
指向了全局对象上,返回 undefined
。
在严格模式下,函数被调用后,里面的this默认是 undefined
后面,通过调用函数上的call
和apply
方法,该变this
指向,函数里面的this
指向fruit
。
区别:bind
同样实现了改变this
指向的功能,但是它不会立即执行,而是会重新创建一个绑定函数,新函数被调用时,使用bind()
方法里面的第一个参数作为this
接受参数
这三个方法,从接受的第二参数开始,都直接传递给函数,但是接受参数的方法却很大的不同。
call,从第二个参数开始,以参数列表的形式展示,
apply,则把传递的函数参数,放在一个数组里面作为第二个参数。
1 | fn.call(obj,arg1,arg2); |
bind,从第二个参数开始,同样以参数列表的形式,但是会提前放在新绑定函数的参数之前
1
2
3
4
5
6
7var foo = function(name,age){
console.log("name: "+name+"- age: "+age)
}
var p1 = foo.bind(this,"popo"); // "popo" 作为新函数的第一个参数。
p1(13); // logs name: popo- age: 13
p1("bobo",14) // logs name: popo- age: bobo
应用场景
- 绑定事件回调中
1
2
3
4
5$('.div-class').on('click',function(event) {
/*TODO*/
}.bind(this));
}
}
通常,我们在改变函数上下文之前,都会使用类似that = this
,或者self,_this
,来把this赋值给一个变量。利用.bind()
,可以传入外层的上下文。
- 实现继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16var Person = function(name,age) {
this.name = name;
this.age = age;
}
var P1 = function(name,age) {
// 借用构造函数的方式实现继承
// 利用call 继承了Person
Person.call(this,name,age)
}
P1.prototype.getName = function() {
console.log("name: "+this.name+", age: "+this.age);
}
var newPerson = new P1("popo",20); // logs name: popo, age: 20
newPerson.getName();
实质上,可以看成通过call()
或者apply()
方法,在即将新建的对象,即这里的newPerson
上,执行超类型的构造函数,分别在当前上下文this
上添加name
和age
属性。
- 数组验证的终极方法:
1
2
3function isArray(value) {
return Object.prototype.toString.call(value) == "[object Array]"
}
借用了Object原生的toString()方法,打印出对应变量的构造函数名,
类数组转换为数组:
1
2
3
4
5
6// 实现一个简单的数组 'unshift'方法
Array.prototype.unshift = function(){
this.splice.apply(this,
[0,0].concat(Array.prototype.slice.apply(arguments)));
return this.length;
}首先,利用
this.splice.apply()
,其中splice
,可以直接从数组中移除或者插入变量。apply()
则以数组的形式传递参数,需要利用concat
拼接数组。当函数被调用时,在函数内部会得到类数组
arguments
,它拥有一个length属性,但是没有任何数组的方法。所以,将slice
方法中的this
指向arguments
,获取到arguments
的长度,从而确定方法的start
和end
下标,得到一个数组变量。同样适用的还有,DOM里面的NodeList对象,它也是一种类数组对象。
深入理解
实现bind 方法
bind
方法在ECMAScript5里面被引入,前面提到过,调用该方法时,返回一个新的函数,可以简单使用下面方法实现其改变this
指向的功能。
1
2
3
4
5
6Function.prototype.bind = function(scope) {
var fn = this;
return function() {
return fn.apply(scope)
}
}
接着,就可以利用concat
把bind传递的预置参数拼接到新函数的参数列表中。
1
2
3
4
5
6
7Function.prototype.bind = function(scope) {
var args = Array.prototype.slice.call(arguments,1)
var fn = this
return function() {
return fn.apply(scope,args.concat(Array.prototype.slice.call(arguments)))
}
}
参考链接