call和apply是js里非常常见的两个概念。但自己从来没有好好的深入理解这两者,尤其是在何时使用,如何使用才适合这两个问题上,没有进一步的思考。这里写一篇笔记来反思。
call和apply的定义
在JavaScript权威指南上是这么定义的:
call()和apply()可以看做是某个对象的方法,通过调用方法的形式来简洁调用函数。
call, apply都属于Function.prototype的方法。因此所有函数都有该方法。
call()和apply()的第一个实参是要调用函数的母对象,是调用上下文,在函数体内通过this来获得对它的引用。要想以对象o的方法来调用函数f(),可以这样使用它们。
1
2
3
4
5
6f.call(o);
f.apply(o);
等同于
o.m = f;
o.m();
delete o.m;代码可以理解,但描述理解起来实在困难。
具体说明
定义都是用来考试时死记硬背的。所以来看看实例。
首先要明确一点:call()和apply()的作用是一样的,只是调用方式不同。
例如:
foo.call(this, arg1, arg2, arg3) == foo.apply(this, arguments) == this.foo(arg1, arg2, arg3/* arguments */)
两者唯一的不同点在于第二个传入的参数。
第一个实参指代当前调用的对象,如果在ES5严格模式下,第一个实参不管传入什么null或undefined或其他,都会变成this。
第二个实参,call方法可以传入任意的实参,不论数量长度。如
foo.call(this, arg1, arg2, arg3…)
apply方法则传入的是数组,可以是任意长度的数组,而且传入的数组包括类数组对象。如
foo.apply(this, arguments)
应用场景
我觉得这部分才是要深入理解的,何时去调用它们,使用它们有什么方便之处。
call和apply的用途是借用别人的方法来调用,或者说,是动态改变this这个对象。
前者或许更好理解。来看看这样一个例子。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const a = {
name: 'mdzz',
say: (name) => {
console.log(name)
window.alert(`我是 ${name}`);
}
}
const b = {};
// b也是zz,但没有相应的say方法,所以此时b想借用a的方法。
// 调用a的方法给b使用
a.say.call(b);
b.name = 'yrmdzz';
// 传入参数时
a.say.call(b, b.name);
a.say.apply(b, [b.name]);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41/* 或者这样定义更为明显 */
function Foo(){};
// 添加原型属性
Foo.prototype = {
name: 'mdzz',
say: function() {
console.log(this); // 当前调用的对象Foo
alert(`I am ${this.name}`);
}
}
// new 生成一个实例对象
const people = new Foo();
console.info(people);
people.say();
// 定义一个没有say方法的对象animal
// 让它调用people的say方法
const animal = {
name: 'kitty'
}
people.say.call(animal);
/* 一个更显而易见的例子 */
// 定义一个函数,可以判断传入参数的基本类型
function type(obj) {
let toString = Object.prototype.toString;
let map = [
'[object Boolean]': 'boolean',
'[object Object]': 'Object',
'[object Array]': 'array',
'[object Number]': 'number',
'[object String]': 'string',
'[object RegExp]': 'regExp',
'[object Function]': 'function',
'[object Date]': 'data',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
];
return map[toString.call(obj)];
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27/* 在参数不定时,则可使用apply */
// 这里的例子改写自权威指南
// 内置Math对象有一个max方法,接受任意个参数,返回其中的最大值
// 但有时我们调用时,无法预知参数的个数,而且这种取最大值的情况通常在数组中发生
// 假如有这样一个数组
let arr = [4,98,34,90,41,78];
// 直接Math.max会返回NaN,因为它不接受数组作为参数
Math.max(arr); // 返回NaN
// apply方法接受数组作为参数
let biggest = Math.max.apply(Math, arr);
console.info(biggest); // 98
// 第一个参数Math可以替换为任何其他值,如下的也都可以
let biggest2 = Math.max.apply(this, arr);
let biggest3 = Math.max.apply(null, arr);
console.info(biggest2); // 98
console.info(biggest3); // 98
/* 还有一个比较有意思的apply使用场景 */
// 使用push方法来合并数组
let arrayA = ['say', 'hello'];
let arrayB = ['ok', 'master'];
Array.prototype.push.apply(arrayA, arrayB);
// arrayA: ['say', 'hello', 'ok', 'master']所以我的理解是以下在两种情况下使用call(), apply():
1.动态改变调用的对象,或者说使没有该方法的对象调用有该方法的对象的方法。
2.改变方法的传入参数为数组。
- 就目前看来,肯定还有更多的使用场景。但我还未遇到,等遇到或理解的更深一步的时候,再来补充。
参考:《JavaScript权威指南》
评论