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。