前言
call,apply和bind方法,之前使用的时候,总会有蒙圈的时候。这次顺着用实例来说明this的含义之后,就一次也把这三个方法整理明白吧。
一、call和apply的比较
ECMAScript标准中Function原型具备有call方法 Function.prototype.call
和apply方法Function.prototype.apply
,因此JavaScript中每个函数都有call方法和apply方法。
call方法和apply方法,实现的效果作用是一样的,区别就在于参数的形式和数量上不同。具体而言是:
- call方法接收的参数数量可以是多个。第一个参数是函数执行上下文的对象,后面的参数可以是多个
<script>
var a={
name:'hyh'
}
var name='hyhaa'
function exp(a,b){
console.log(a+this.name+b)
}
exp.call(null,'happy ',' day') //happy hyhaa day
exp.call(a,'happy ',' day') //happy hyh day
</script>
- apply方法接收的参数数量是两个。第一个参数也是函数执行上下文的对象,第二个参数是多个传入的函数参数组成的类数组。
<script>
var a={
name:'hyh'
}
var name='hyhaa'
function exp(a,b,c){
console.log(this.name+a+b+c)
}
exp.apply(null,[' happy ',' every',' day']) //hyhaa happy every day
</script>
call和apply方法的使用,其实就是换一种参数传递的形式来达到使用函数方法的目的。比如有些时候,我们手头有的参数形式是数组(类数组),直接传递给某个函数方法,但其不接受,那我们就借位,把函数方法换个apply写法,这样就能接受我们的参数形式。
所以,鉴于apply和call方法的作用是一样的,那么如何挑选哪种写法合适,就看具体情况了。简单来说,就是参数是数组形式,就使用apply方法,参数是单独多个的话,就使用call方法。
注意:如果第一个参数不需要写,不需要改动函数上下文的对象的话,记得写上null值来占位。如上面例子中的exp.apply(null,[' happy ',' every',' day'])
写法。
二、call和apply的作用
2.1 改变this的指向
接上面所说的,call方法和apply方法的第一个参数都是作为函数执行上下文的对象,也就是说call方法和apply方法改变了函数被调用时this的指向。
<script>
var value=100
var obj5={
value:200
}
function fn4(a,b){
console.log(this.value+a+b)
}
fn4(3,4) //107
fn4.call(obj5,3,4) //207
fn4.apply(obj5,[3,4]) //207
</script>
这个例子中,等同于obj5这个对象传递给了函数fn4,函数fn4的this就指向obj5。等同于实现:
function fn4(a,b){
console.log(obj5.value+a+b)
}
再举个复杂点的例子:
<script>
var fruits={
name:'apple',
saleprice:function(price){
console.log(this.name+' price is '+price +' yuan')
}
}
var vegtable={
name:'tomato'
}
fruits.saleprice('20') //apple price is 20 yuan
fruits.saleprice.call(vegtable,'12') //tomato price is 12 yuan
</script>
在这个例子中,fruits.saleprice.call(vegtable,'12')
是将vegtable传给到fruits对象中,因此this.name就等同于vegtable的name属性'tomato'。
2.2 立即执行函数
这两个方法的使用,会让函数立即执行。
function exp(){
console.log(1)
}
exp.call()
exp.apply()
不过因为例如exp()的写法也能让函数立即执行,所以常见的是这种更简便的写法exp()。
三、call和apply常见的应用写法
3.1 数组的扩充
<script>
var a=[123,'2fds','3sdf',34]
var b=['aa','bb',343]
Array.prototype.push.apply(a,b)
console.log(a) //[123, "2fds", "3sdf", 34, "aa", "bb", 343]
</script>
3.2 字符串的拼接
function expjoin(arguments){
var a=Array.prototype.join.apply(arguments);
console.log(a);
}
expjoin([123,'2fds','3sdf',34]) //"123,2fds,3sdf,34"
这个写法也等同于
[].join.apply([123,'2fds','3sdf',34]) //"123,2fds,3sdf,34"
3.3 最大/小值的获取
var arr=[34,54,656,877] //undefined
Math.max.apply(Math,arr) //877
Math.max.apply(null,arr) //877
Math.max.call(null,34,54,656,877) //877
Math.min.call(null,34,54,656,877) //34
Math.min.apply(null,arr) //34
Math.min.apply(Math,arr) //34
3.4 验证是否为数组
<script>
function isArray(obj){
return Object.prototype.toString.call(obj) ==='[object Array]'
}
console.log(isArray(343));
console.log(isArray([4343,565]));
</script>
3.5 把一个类数组转换为真正的数组
var arr= Array.prototype.slice.call([2,4,5]);
arr //[2, 4, 5]
arr.push('33') //[2, 4, 5, "33"],能够具备并使用数组的方法例如push()
domNodes.push(3434) //[2, 4, 5, "33", 3434]
四、call和apply写法的转换
如果看了感觉会晕,那么可以换种方式来看call和apply的写法
Array.prototype.join.apply(arguments)等同于arguments.join()
Array.prototype.slice.call(arguments)等同于arguments.slice()
前面例子中的fruits.saleprice.call(vegtable,'12')
等同于vegtable.saleprice('12')
。
这样就不难理解,诸如下面的写法:
function log(){
console.log.apply(console, arguments); //console.log(arguments)
}
log(1,2,3,4,5) //arguments会默认使用[ ]运算符获取参数,结果为:1 2 3 4 5
对上面这个例子使用到了arguments,再来引申讲下arguments
在函数调用时,会自动在该函数内部生成一个名为 arguments的隐藏对象
该对象类似于数组,可以使用[ ]运算符获取函数调用时传递的实参
只有函数被调用时,arguments对象才会创建,未调用时其值为null
function fn5(name, age){
console.log(arguments); //["Byron", 20]
name = 'XXX';
console.log(arguments); //["XXX", 20]
arguments[1] = 30;
console.log(arguments); //["XXX", 30]
}
fn5('Byron', 20);
五、bind方法
终于写到bind了~~bind的用法稍微迂回点,需要稍长点耐心理解。
call方法和apply方法是ECMAScript3标准定义的,而bind方法是更晚的ECMAScript5标准定义的方法。
5.1 bind、call和apply方法的相同之处
前面我们已经说了call和apply的作用相同,区别就在参数的形式不同。bind方法的作用也是改变this的指向。bind方法的参数要求和call方法一样,第一个参数是函数执行上下文的对象,后面的参数可以是多个。
但bind又有自己的特别之处,下面来说说bind的用法上的区别:
5.2 bind方法的用法上的区别
区别一: bind()方法是会创建一个新函数,当调用这个新函数时,会以创建新函数时传入 bind()方法的第一个参数作为 this。
看栗子来体会:
var name='xxx';
var obj = {
name: 'yyy'
}
function func() {
console.log(this.name);
}
var func1 = func.bind(obj);
func1(); //yyy
func(); //xxx
func1等于通过bind方法创建的和func函数相同的新函数。func1的this对象为传入的obj,所以this.name就只等于obj对象的name值‘yyy’。
因为是新生成的函数,所以原来函数func()并不受影响,所以直接执行func(),this指向全局window,所以this.name为'xxx'。
区别二: 参数的使用上,call方法是将第二个及之后的参数,作为实参传递到函数中。
而 bind() 方法的第二个以及以后的参数,再加上新函数运行时的参数,按照顺序作为实参传递到新函数中。
这样看文字解释,真的太绕了,直接看栗子,就很好理解:
function func(a, b, c) {
console.log(a, b, c);
}
var func1 = func.bind(null,'aaa');
func('A', 'B', 'C'); // A B C
func1('A', 'B', 'C'); // aaa A B
func1('B', 'C'); // aaa B C
func.call(null, 'aaa'); // aaa undefined undefined
区别三: 最后一个不同,就是bind函数不是立即调用的,实现生成新的处理函数,然后再执行。而call和apply方法都是立即执行。所以写法上,bind函数需要再加个()
还是看栗子吧,改写下前面的例子:(一样的实现效果,不一样的执行写法)
var name='xxx';
var obj = {
name: 'yyy'
}
function func() {
console.log(this.name);
}
console.log(func.bind(obj()); //yyy
console.log(func.call(obj)); //yyy
console.log(func.apply(obj)); //yyy
5.3 bind方法常见的应用写法
例子1:改变setTimeout()方法的this指向
上一篇专门写this的使用实例的时候,讲到一个setTimeout()的例子。上一篇的例子中是使用var _this=this的方式来使setTimeout()的this不指向window。现在学了bind,同样也可以用bind来达到同样效果:
<body>
<button class="bindtest">dianwo</button>
<script>
/*bind的用法--和使用var _this=this达到同样的目的*/
$bindtest=document.querySelector('.bindtest')
$bindtest.addEventListener('click',function(){
console.log(this) //<button class="domtest">dianwo</button>
setTimeout(function(){
console.log(this) //<button class="domtest">dianwo</button>
}.bind(this),300)
})
</script>
</body>
前面说了,bind的作用是得到一个新的函数,而新函数的this就是bind传递的第一个参数。这里bind方法是和setTimeout方法同级的,属于$bindtest对象的点击事件下的,所以this是$bindtest对象。
例子2:字符串拼接的bind方法实现
前面call方法的使用例子中实现的字符串拼接,现在换成用bind方法来实现:
常见的是,比如我需要一个join操作,但是我没有这个方法,那么我就需要借助数组Array原型的join方法来借位使用。
function joinStr(){
var joins=Array.prototype.join.bind(arguments);
console.log(joins('-'))
}
joinStr('a','b','c')
例子中的写法等同于['a','b','c'].join('-')
六、结语
写了这么一大通,结合实例来理解,真的事半功倍。我列举的call,apply和bind方法的常见使用的例子,只是我目前看到的常见写法。更多的变化写法,就待实践和学习,来扩充积累啦。
参考文章:
https://segmentfault.com/a/1190000009650716
http://book.jirengu.com/fe/%E5%89%8D%E7%AB%AF%E8%BF%9B%E9%98%B6/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/this.html
https://github.com/lin-xin/blog/issues/7
https://segmentfault.com/a/1190000006993545