函数对象的call()方法和apply()方法

apply()和call()是ES3引入的新方法。为啥要引入这两个方法呢?为了更优雅的实现面向对象编程的继承。另外apply还有一个妙用。

区分apply,call就一句话:

foo.call(this,arg1,arg2,arg3)==foo.apply(this,arguments)==this.foo(arg1, arg2, arg3)

所以,它们的作用一样,只是方法传递的参数不同。

call(..)和apply(..)的语法中的相同点是,第一个参数都是一个对象,语法中的区别是,后面传入的参数的形式不同,call()方法接受的是若干个参数的列表,而apply()方法接受的是一个包含多个参数的数组。下面分别介绍:

call方法:

语法 fun.call(thisArg[, arg1[, arg2[, ...]]])

thisArg是在fun函数运行时指定的this值。需要注意的是下面几种情况:

(1)不传,或者传null,undefined,函数中的this指向window对象
(2)传递另一个函数的函数名,函数中的this指向这个函数的引用,并不一定是该函数执行时真正的this值
(3)值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象,如 String、Number、Boolean
(4)传递一个对象,函数中的this指向这个对象

arg1, arg2, ...是指定的参数列表,多余的参数将被自动忽略。

//定义函数a
function a(){
    //输出函数a中的this对象
    console.log(this); 
}

//定义函数b
function b(){} 

//定义对象obj
var obj = {name:'这是一个屌丝'};

a.call(); //window
a.call(null); //window
a.call(undefined);//window
a.call(1); //Number
a.call(''); //String
a.call(true); //Boolean
a.call(b);// function b(){}
a.call(obj); //Object

所以,foo.call(bar, arg1, arg2, arg3...)的执行过程如下,foo.apply(bar, [arg1, arg2, arg3...])也一样道理:

第一步,改变foo函数的this指向;

第二步,给foo函数传参;

第三步,执行foo函数。

例,最简用法:

function foo() {
    console.log( this.a );
}
var obj = {
    a: 2
};
foo.call(obj);  // 2,将call换成apply也成立

foo.call(obj);怎么理解?像上面说的,第一步,foo的this指向了obj,第二步传参略过,第三步,执行foo,foo中的console.log( this.a );的this由于指向obj,所以是打印2。可以再看一个例子:

function greet() {
  var reply = this.person + '是一个' + this.role + '。';
  console.log(reply);
}

var i = {
    person: '我',
    role: '演员'
};

greet.call(i); 
// 我是一个演员。

例,带参数用法:

function foo(a, b) {
    return a + '-' + b + '-' + this.c;
}

var bar = {};
bar.c = 100;

console.log( foo.call(bar, 1, 10) ); // 1-10-100
console.log( foo.apply(bar, [2, 20]) ); // 2-20-100

第一步,foo的this指向bar的引用,第二步,给foo传参1和10,第三步,执行foo,得到1-10-100。

apply方法:

语法与 call() 方法的语法几乎完全相同,唯一的区别在于,apply的第二个参数必须是一个包含多个参数的数组(或类数组对象)。

语法:fun.apply(thisArg[, argsArray])

thisArg同上call的thisArg参数。

argsArray是一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun 函数。如果该参数的值为null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5开始可以使用类数组对象。需要注意:Internet Explorer 8和9不接受类数组对象。如果传入类数组对象,它们会抛出异常。

例,使用apply方法调用父构造函数,实现继承:

// 定义一个人构造函数,是父构造函数
function Person(name,age)
{
    this.name = name;
    this.age = age;
}

// 定义一个学生构造函数,是子构造函数
function Student(name,age,grade)
{
    Person.apply(this, arguments);
    this.grade = grade;
}

// 创建一个学生实例
var student=new Student("Jack",7,"one");
// 测试
alert("name:" + student.name + "\n" + "age:" + student.age + "\n" + "grade:" + student.grade);
// 学生类里面并没有给name和age属性赋值,为什么又存在这两个属性的值呢,这个就是apply的神奇之处

new出student的过程,我们看看发生了什么:

new操作符的操作,先复习一下:

1、创建一个隐藏连接到该函数的prototype成员的新对象。
2、将函数的作用域赋给新对象,所以this也被绑定到那个新对象上。
3、像执行普通函数一样,执行一遍函数。
4、返回这个新对象。

于是,第一步,student对象诞生,第二步,Student的作用域赋给了student,Student的this也指向了student的引用,第三步,执行函数,执行函数又分为下面步骤:

第一句,Person.apply(this,arguments);,它的作用是:

第一步,让Person的this绑定到Student的引用上。

第二步,将name,age,grade传参给Person构造函数。

第三步,执行Person函数一遍,也就是给Student new出的实例对象添加了name、age两个属性。

然后还有一句,this.grade = grade;,它是给new出的实例对象添加了一个grade属性。

现在的student实例对象就有了三个属性,当然它就可以打印出三个属性。

为内置函数使用apply方法

聪明的apply用法允许你在某些本来需要写成遍历数组变量的任务中使用内建的函数。在接下里的例子中我们会使用Math.max/Math.min来找出一个数组中的最大/最小值。

我们先复习一下Math.max()的常规用法:

console.log(Math.max(7.25, 7.3, 22, 1, 35)); // 35

虽然很简单就得到了最大值,但是,Math.max()只接受参数列表,不接受数组参数。怎么办?你不接受,自有人接受,apply接受数组参数。用apply传递数组参数,如下:

var numbers = [5, 6, 2, 3, 7, 11];

var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);

console.log(max);
console.log(min);

// 如果不用apply,只能是手写,参数很少的话还好,多的话会死人的:
var max = Math.max(numbers[0], numbers[1], numbers[2], numbers[3], numbers[4], numbers[5]);
console.log(max);

所以,apply的一个特殊用途就是给只接受参数列表的函数传递数组参数

总结

目前面向对象编程的继承方式,主要是call/apply方式,和原型链方式。IE6开始支持他们所有方式,放心使用即可。

call和apply唯一区别在于call接受参数列表,apply接受数组参数或类数组参数。利用apply的这个特性,可以给任意不接受数组参数的原生函数改写成支持接受数组参数。其他时候根据实际情况选择用call还是apply。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容