本文1630字,阅读大约须要8分钟。
总括: 本文从零开始经过提出问题而后解决问题的方式模拟实现了比较完善的call和apply方法前端
每个未曾起舞的日子,都是对生命的辜负。数组
首先介绍下call和apply两个方法,这两个方法都是挂载在函数的原型上的,因此全部的函数均可以调用这两个方法。app
注意:call()方法的做用和 apply() 方法相似,区别就是call()
方法接受的是 参数列表,而apply()
方法接受的是 一个参数数组。
例子:异步
function foo(b = 0) { console.log(this.a + b); } const obj1 = { a: 1 }; const obj2 = { a: 2 }; foo.call(obj1, 1); // 2 foo.call(obj2, 2); // 4 foo.apply(obj1, [1]); // 2 foo.apply(obj2, [2]); // 4
对于this
不熟悉的同窗能够先异步:理解Javascript的this。总结起来一句话:Javascript函数的this
指向调用方,谁调用this
就指向谁,若是没人谁调用这个函数,严格模式下指向undefined
,非严格模式指向window
。函数
因此本质上call和apply就是用来更改被调用函数的this值的。如上,call和apply只有参数的不一样,模拟实现了call,那么apply就只是参数处理上的区别。也就是说,call和apply干了两件事:学习
this
值;如今模拟实现call和apply的问题转移到另外一个问题上,即如何去更改一个函数的this
值,很简单:测试
function foo(b = 0) { console.log(this.a + b); } const obj1 = { a: 1, foo: foo }; const obj2 = { a: 2, foo: foo }; obj1.foo(1); obj2.foo(2);
也就是说咱们把这个方法赋值给对象,而后对象调用这个函数就能够了。改变一个函数的this步骤很简单,首先将这个函数赋值给this要指向的对象,而后对象调用这个函数,执行完从对象上删除掉这个函数就行了。步骤以下:this
obj.foo = foo; obj.foo(); delete obj.foo;
有了思路咱们实现初版call方法:spa
Function.prototype.call2 = function(context) { context = context || {}; context[this.name] = this; context[this.name](); delete context[this.name]; }
this.name
是函数声明的名称,但实际上是不必必定对应函数名称的,咱们随便用一个key均可以:prototype
Function.prototype.call2 = function(context) { context = context || {}; context.func = this; context.func(); delete context.func; }
使用新的call调用上面的函数:
foo.call2(obj1); // 1 foo.call2(obj2); // 2
OK,this的问题解决了,接下来就是传参的问题:
函数中的参数保存在一个类数组对象arguments
中。所以咱们能够从arguments里面去拿从传到call2里面的参数:
Function.prototype.call2 = function(context) { context = context || {}; var params = []; for (var i = 1; i < arguments.length; i++) { params[i - 1] = arguments[i]; } context.func = this; context.func(); delete context.func; }
此时问题来了,如何把参数params
传递到func
中呢?比较容易想到的办法是利用ES6的扩展运算符:
Function.prototype.call2 = function(context) { context = context || {}; var params = []; for (var i = 1; i < arguments.length; i++) { params[i - 1] = arguments[i]; } context.func = this; context.func(...params); delete context.func; }
看下咱们的例子:
foo.call2(obj1, 1); // 2 foo.call2(obj2, 2); // 4
还有一个实现,是利用不经常使用的eval函数,即咱们把参数拼接成一个字符串,传给eval函数去执行,
eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。
看下咱们的第二版实现:
Function.prototype.call2 = function(context) { context = context || {}; var params = []; for (var i = 1; i < arguments.length; i++) { params[i - 1] = arguments[i]; } // 注意,此处的this是指的被调用的函数 context.func = this; eval('context.func(' + params.join(",") + ')'); delete context.func; }
call
和apply
还有另外两个重要的特性,能够正常返回函数执行结果,接受null
或undefined
为参数的时候将this
指向window
,而后咱们来实现下这两个特性,而后加上必要的判断提示,这是咱们的第三版实现:
Function.prototype.call2 = function(context) { context = context || window; var params = []; // 此处将i初始化为1,是为了跳过context参数 for (var i = 1; i < arguments.length; i++) { params[i - 1] = arguments[i]; } // 注意,此处的this是指的被调用的函数 context.func = this; var res = eval('context.func(' + params.join(",") + ')'); delete context.func; return res; }
而后咱们调用测试下:
foo.call2(obj1, 1); // 2 foo.call(2, 1); // NaN foo.call2(2, 1); // context.func is not a function
如上咱们发现将对象改为数字2后原始call返回了NaN,咱们的call2却报错了,说明一个问题,咱们直接context = context || window
是有问题的。内部还有一个类型判断,解决这个问题后,咱们的第四版实现以下:
Function.prototype.call2 = function(context) { if (context === null || context === undefined) { context = window; } else { context = Object(context) || context; } var params = []; // 此处将i初始化为1,是为了跳过context参数 for (var i = 1; i < arguments.length; i++) { params[i - 1] = arguments[i]; } // 注意,此处的this是指的被调用的函数 context.func = this; var res = eval('context.func(' + params.join(",") + ')'); delete context.func; return res; }
这就是咱们的最终代码,这个代码能够从ES3一直兼容到ES6,此时:
foo.call(2, 1); // NaN foo.call2(2, 1); // NaN
apply和call只是参数上的区别,将call2改写就行了:
Function.prototype.apply2 = function(context, arr) { if (context === null || context === undefined) { context = window; } else { context = Object(context) || context; } // 注意,此处的this是指的被调用的函数 context.func = this; arr = arr || []; var res = eval('context.func(' + arr.join(",") + ')'); delete context.func; return res; }
以上就是咱们最终的实现,目前还有一个问题就是context.func
的问题,这样一来咱们传进来的context
就不能使用func
字符串做为方法名了。
咱们实现过程都解决了如下问题:
this
;null
或undefined
的时候被调用函数的this
指向window
;以上。
能力有限,水平通常,欢迎勘误,不胜感激。
订阅更多文章可关注公众号「前端进阶学习」,回复「666」,获取一揽子前端技术书籍