call:第一个参数是为函数内部指定this指向,后续的参数则是函数执行时所需要的参数,一个一个传递。
apply:第一个参数与call相同,为函数内部this指向,而函数的参数,则以数组的形式传递,作为apply第二参数。
call 的性能更好,不过 lodash 里的源码当参数小于等于 3 时用 call,之后用 apply
call模拟实现
Function.prototype.myCall = function(obj, ...arg) {
if(typeof this !== 'function'){
throw new TypeError(this + ' is not a function');
}
const context = obj;
const fn = Symbol();
context[fn] = this;
const ret = context[fn](...arg)
delete context[fn]
return ret;
}
apply模拟实现
Function.prototype.myApply = function(obj, arg) {
if(typeof this !== 'function'){
throw new TypeError(this + ' is not a function');
}
const context = obj;
const fn = Symbol();
context[fn] = this;
const ret = context[fn](...arg)
delete context[fn]
return ret;
}
为什么call 比apply 快?
这里就要提到他们被调用之后发生了什么。
Function.prototype.apply (thisArg, argArray)
1、如果IsCallable(Function)为false,即Function不可以被调用,则抛出一个TypeError异常。
2、如果argArray为null或未定义,则返回调用function的[[Call]]内部方法的结果,提供thisArg和一个空数组作为参数。
3、如果 Type(argArray)不是Object,则抛出TypeError异常。
4、获取argArray的长度。调用argArray的[[Get]]内部方法,找到属性length。 赋值给len。
5、定义 n 为ToUint32(len)。ToUint32(len)方法:将其参数len转换为范围为0到232-1的232个整数值中的一个。
6、初始化 argList 为一个空列表。
7、初始化 index 为 0。
8、循环迭代取出argArray。重复循环 while(index < n)
a、将下标转换成String类型。初始化 indexName 为 ToString(index).
b、定义 nextArg 为 使用 indexName 作为参数调用argArray的[[Get]]内部方法的结果。
c、将 nextArg 添加到 argList 中,作为最后一个元素。
d、设置 index = index+1
9、返回调用func的[[Call]]内部方法的结果,提供thisArg作为该值,argList作为参数列表。
Function.prototype.call (thisArg [ , arg1 [ , arg2, … ] ] )
1、如果 IsCallable(Function)为false,即Function不可以被调用,则抛出一个TypeError异常。
2、定义argList 为一个空列表。
3、如果使用超过一个参数调用此方法,则以从arg1开始的从左到右的顺序将每个参数附加为argList的最后一个元素
4、返回调用func的[[Call]]内部方法的结果,提供thisArg作为该值,argList作为参数列表。
我们可以看到,明显apply比call的步骤多很多。
由于apply中定义的参数格式(数组),使得被调用之后需要做更多的事,需要将给定的参数格式改变(步骤8)。 同时也有一些对参数的检查(步骤2),在call中却是不必要的。
另外一个很重要的点:在apply中不管有多少个参数,都会执行循环,也就是步骤6-8,在call中也就是对应步骤3 ,是有需要才会被执行。
综上,call 方法比 apply 快的原因是 call 方法的参数格式正是内部方法所需要的格式
apply最后也是转换为call实现的
更新:apply 转化的是内置的 call,并非 Function.prototype.call,感谢楼里同学提醒。apply 最后还是转化成 call 来执行的,call 要更快毫无疑问,详细介绍见 https://tc39.es/ecma262/#sec-function.prototype.apply
bind实现
Function.prototype.bind2 = function(context, ...args){
var fn = this;
return function () { // 这里不能使用箭头函数,不然参数arguments的指向就很尴尬了,指向父函数的参数
fn.call(context, ...args, ...arguments);
}
}