重要声明:本文章仅仅代表了作者个人对此观点的理解和表述。读者请查阅时持自己的意见进行讨论。
javascript 允许我们以各种姿势去执行一个函数,这些所有的姿势中就包含了如标题所写的这三个姿势。下面直接介绍一下它们对应的用法。本文我们先定义一个函数,以这个函数为测试对象来介绍这三个姿势的不同之处。
一、定义函数
我们先简单的定义出来一个用于测试的函数,本文的重点不是如何定义函数,因此这里直接写一个简单的函数就好了,那么我们不妨实现一个:截取字符串中指定位置的子字符串。
/**
* 截取 str 从 startIndex 到 endIndex 之间的字符串并返回。
* @param str {string} 待截取字符串。
* @param startIndex {number} 开始位置。
* @param endIndex {number} 结束位置。
*/
function mySubstring(str, startIndex, endIndex) {
var result = "";
for (var index = 0; index < str.length; index++) {
if (startIndex <= index && index <= endIndex) {
result += str.charAt(index);
}
}
this.result = result; // 在 this 上将结果也添加上。
return result;
}
现在函数定义好了,这个函数也是很简单的,传入要截取的字符串以及位置信息,就可以返回截取结果了,中途有一行代码是将result
也放在了this
上面,此代码主要是为了后面讲解的时候会用到。接下来就分别对各个姿势进行介绍。
二、函数正常执行
直接在函数后面使用()
括号,并传入对应参数,就可以正常执行了。但凡学过javascript都必然知道这种姿势。
var str = mySubstring("I'm Jack.", 2, 6);
// str 得到 "m Jac"
上述代码可以看到,函数后面通过括号直接执行,这也是最为常规的代码了。
三、使用call执行函数
使用call
执行函数可以修改函数内this
的指向,上面的函数中正好有this.result = result;
这句代码,既然call
可以修改this
的指向,那么如果我们将其指向到我们希望的一个对象上,当执行完成这个函数后,这个对象上就应该会有一个 result
属性,并且有值的。看下面的代码:
// 创建一个自己的对象。
var myObj = {tag: "这是我自己的对象"};
// 使用call去执行上面的函数,并且传入我们自己的对象,从而实现上述函数里的this指向我们自己的对象。
var str = mySubstring.call(myObj, "I'm Jack.", 2, 6);
// str 得到 "m Jac"。
console.log(myObj);
// 同时 myObj 则为:{tag: "这是我自己的对象", result: "m Jac"};
看到没有,使用call
去执行我们的函数时,第一个参数不是我们自己的函数定义的第一个了, 而是一个用于设定你希望函数内部this
指向的一个东西,而后面的参数就和正常情况一样,原来的函数参数怎么定义的后面就怎么传递就好了。这看起来好像没有卵用,诚然,大多数情况下我们并不会去这样子使用。但如果你要写稍微工具化或者框架级别的系统时,你可能就会常常遇到各种奇葩的功能需求,而恰好这样的写法可以满足你的需求!
四、使用apply执行函数
apply
和call
的作用几乎一模一样的,作用也是修改原函数内部的this
指向,唯一的不同之处,就是参数的传递方式有所差异。请阅读下面的代码:
// 创建一个自己的对象。
var myObj = {tag: "这是我自己的对象"};
// 使用apply去执行上面的函数,并且传入我们自己的对象,从而实现上述函数里的this指向我们自己的对象。
var str = mySubstring.apply(myObj, ["I'm Jack.", 2, 6]);
// str 得到 "m Jac"。
console.log(myObj);
// 同时 myObj 则为:{tag: "这是我自己的对象", result: "m Jac"};
很简单对吧,它与 call
的差别就只是第一个参数后面不同,他们的第一个参数,都是用于指定this指向的,而后面的参数则有所不同了。apply
是需要你把所有的参数放在一个数组里面,然后直接放在后面;而call
则是你直接把你的参数挨着向后面继续写就可以了。
五、使用bind
bind
与上面两个方法的作用类似,也可以修改原函数内的this
指向,而bind
的一个很大的区别在于,它不会执行函数本身,而是会产生一个新的函数,这个新的函数功能和原来的函数功能一模一样,但就是内部的this
指向不一样。比方说下面的代码:
// 创建一个自己的对象。
var myObj = {tag: "这是我自己的对象"};
// 使用bind去处理上面的函数,并且传入我们自己的对象,从而实现上述函数里的this指向我们自己的对象。
var newFun = mySubstring.bind(myObj);
// newFun 是一个新的函数,不再是原来函数的执行结果了,这时候你可以 newFun() 执行这个新的函数。
// 执行这个新的函数,得到我们的结果
var str = newFun("I'm Jack.", 2, 6);
// str 得到 "m Jac"。
console.log(myObj);
// 同时 myObj 则为:{tag: "这是我自己的对象", result: "m Jac"};
要注意,上面的代码第5行可以看到,我们在使用bind
函数的时候,只是传入了要修改的this
指向,没有传入原本函数定义的那些参数,而是在下面执行新的函数时,传入了具体的参数。
实际上,你可以直接在bind
的时候指定参数,指定了多少个,产生的新的函数,就只能传递剩下的参数,比如说:
// 创建一个自己的对象。
var myObj = {tag: "这是我自己的对象"};
// 使用bind去处理上面的函数,并且传入我们自己的对象,并且新函数数第一个参数设为 "I'm Jack."。
var newFun = mySubstring.bind(myObj, "I'm Jack.");
// 执行这个新的函数的时候,我们就只需要传递剩下的两个参数。
var str = newFun(2, 6);
// str 得到 "m Jac"。
console.log(myObj); // 同时 myObj 则为:{tag: "这是我自己的对象", result: "m Jac"};
// 如果我们直接指定两个参数,则新函数执行时只需要传最后一个了。
var newFun2 = mySubstring.bind(myObj, "I'm Jack.", 2);
var str2 = newFun2(6);
// str2 得到 "m Jac"。
// 甚至我们可以把所有参数在bind的时候都给设置了。
var newFun3 = mySubstring.bind(myObj, "I'm Jack.", 2, 6);
var str3 = newFun2();
// str3 得到 "m Jac"。
通过bind
处理了之后,我们可以让许多函数产生一个新的适用于自己模块的替代函数出来,从而实现一些底层的工作。还有一点值得一提,当我们通过bind
得到了新的函数后,如果你再用新的函数去通过call
或者apply
去执行的话,函数会执行,但是不会修改this
指向了,函数执行时的this
执行还是bind
指定的那个。