谈谈我对call、apply的理解
2017-03-10
call在js面试中的地位不亚与prototype,但是可能连面试官都不一定真正搞懂call到底做了什么,不着急,先从标志答案说起
call是用来做什么的
call是什么?
1 | Function.prototype.call(thisArg, arg1, arg2, arg3) |
所以call是一个方法(函数),第一个参数是指定调用函数的上下文(this),后面的参数列表,就是调用函数需要传入的参数
返回值是执行函数的返回值
用法如下:
1 |
|
结果是:
1 | 输出 normal invoked |
所以说,call就是用来改变运行函数执行的上下文环境,也就是this,这个就算是标准答案,我面试时也是这么说的,那么面试官一定会追问下面这个问题
call与apply有什么区别
基本用过call和apply的同学基本知道call是接收的参数列表,apply接收的是数组
而看过文档就知道,apply接收是类数组对象(array-like object),什么是类数组对象,举个例子 arguments
那为什么可以接收数组,因为数组不仅仅是类数组对象,同时他还是数组
这里举一个apply的实用案例:获取数组中的最大(小)值
1 | Math.max.apply(null, [12,34,23423,235,32]); |
返回值是:23423
Function.call
你知道
["a", "b", "c"].map(Function.call, Number);
的结果是什么吗?
终于进入正题!
首先map接收两个参数,一个是回调函数function(value, index, array) {}
,
第二参数一般不常用,是指定回调函数的上下文环境,也就是this
先来看下map的简单实现
1 | Array.prototype.map = function(fn, ctx) { |
完整版的map polyfill参考这里
因为map内部实现也是用了call方法
所以["a", "b", "c"].map(Function.call, Number);
可以转换成如下
1 | [ |
简化之后会变成如下,这里会让很多人疑惑
1 | [ |
这时候就要看call的内部实现是怎样的了,根据规范的定义和,我猜测call的内部应该是类似如下的实现
1 | function F() { |
再来个简化版,只看执行逻辑,忽略掉参数的处理
1 | function F(thisArg, arg1, arg2, arg3) { |
也就是说call的内部是把this作为一个函数执行了,如果这时候你已经不知道this是什么了,请回去看第一个例子,一个函数中的this默认是指向它外层的对象,
正常情况使用call,如 Foo.test.call(obj)
,call内部的this就是test,执行this,就是执行test
再比如[].slice.call(arguments)
,call内部this就是slice,执行this,就是执行slice,而slice内部的this就是arguments
而Function.call.call(Number, "a", 0, ["a", "b", "c"])
, call内部的this指向call,那么call内部会进入if的条件中,执行函数变成了Number.call( "a", 0, ["a", "b", "c"])
至此应该就可以理解上面的那个简化是怎么来的了
然后再简化
1 | [ |
最后
[0, 1, 2]
fn.call.call.call.call.call.call()
有人看到call.call就这么复杂,那如果是很多个call,意味着什么?
意味着还是fn.call.call()
1 | Function.call === Function.call.call // true |
因为Function.prototype.call,这是原型上的方法,点多少个都是它自己,而call只认调用他的那个对象到底是什么,如果是普通对象,那说明只有一个call,如果是call,那就按照call.call的方式去处理
参考链接
- http://blog.sina.com.cn/s/blog_70a3539f0101fref.html
- https://zhidao.baidu.com/question/1795031515293789747.html
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/arguments
- http://www.ecma-international.org/ecma-262/6.0/#sec-function.prototype.call
- http://www.ecma-international.org/ecma-262/6.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist