嗨,老铁,欢迎来到我的博客!

如果觉得我的内容还不错的话,可以关注下我在 segmentfault.com 上的直播。我主要从事 PHP 和 Java 方面的开发,《深入 PHP 内核》作者之一。

[视频直播] PHP 进阶之路 - 亿级 pv 网站架构的技术细节与套路 直播中我将毫无保留的分享我这六年的全部工作经验和踩坑的故事,以及会穿插着一些面试中的 考点难点加分点

周梦康 发表于 2014-06-08 4902 次浏览 标签 : javascript

免费领取阿里云优惠券 我的直播 - 《PHP 进阶之路》

我在平常写js代码中很少用到这两个方法,但是看别人的代码的时候,经常会用到。前几天也特意搜了些这方面的资料觉得说得都不是重点,今天又看《JavaScript高级程序设计》,发觉真的是温故而知新啊。也不能这么说,因为自己看得太快,之前20%看懂,56%的没看懂,还有20%都忘记了。感觉这种方式读书也不为过,因为很多东西不是在最开始就能看懂的,经过一段时间的实践和学习沉淀再回来看也许就别有一番新的收获了。扯远了,还是写 apply()call() 的理解吧:

首先必须明确一点:函数实际上是对象。每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法。

每个函数都包含两个非继承而来的方法: apply()call() 。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。首先, apply() 方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是 Array 的实例,也可以是 arguments 对象。例如:

function sum(num1, num2){
    return num1 + num2;
}
function callSum1(num1, num2){
    return sum.apply(this, arguments);
}
function callSum2(num1, num2){
    return sum.apply(this, [num1, num2]);
}
alert(callSum1(10,10));   //20
alert(callSum2(10,10));   //20

在上面这个例子中,callSum1() 在执行 sum() 函数时传入了 this 作为 this 值(因为是在全局 作用域中调用的,所以传入的就是 window 对象)和 arguments 对象。而 callSum2 同样也调用了 sum() 函数,但它传入的则是 this 和一个参数数组。这两个函数都会正常执行并返回正确的结果。

call() 方法与 apply() 方法的作用相同,它们的区别仅在于接收参数的方式不同。对于 call() 方法而言,第一个参数是 this 值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来,如下面的例子所示。

function sum(num1, num2){
    return num1 + num2;
}
function callSum(num1, num2){
    return sum.call(this, num1, num2);
}
alert(callSum(10,10));   //20

在使用 call() 方法的情况下, callSum() 必须明确地传入每一个参数。结果与使用 apply() 没有什么不同。至于是使用 apply() 还是 call() ,完全取决于你采取哪种给函数传递参数的方式最方便。如果你打算直接传入 arguments 对象,或者包含函数中先接收到的也是一个数组,那么使用 apply() 肯定更方便;否则,选择 call() 可能更合适。(在不给函数传递参数的情况下,使用哪个方法都无所谓。)

事实上,传递参数并非 apply()call() 真正的用武之地;它们真正强大的地方是能够扩充函数赖以运行的作用域。

下面来看一个例子。

window.color = "red";
var o = { color: "blue" };
function sayColor(){
    alert(this.color);
}

sayColor();//red

sayColor.call(this);//red

sayColor.call(window);//red

sayColor.call(o);//blue

使用 call() (或 apply() )来扩充作用域的最大好处,就是对象不需要与方法有任何耦合关系。 

本人的简单理解:

所谓耦合,就是指两个或两个以上的实体相互依赖于对方的一个量度。这里的实体可以理解为我们的对象,方法,函数等。如果耦合度高,那么复用性必然会很低。低耦合一直是我们软件开发准则之一,而这里的 call()apply()都非常好的诠释了这一点,比如下面的一个简单例子:

function say(){
	this.mouth = 'mouth';
	console.log('haha');
}

function Person(name,age){
	this.name = name;
	this.age = age;
}

function Computer(brand,price){
	this.brand = brand;
	this.price = price;
}

var zmk = new Person('ZhouMengkang',25);
var myComputer = new Computer('dell',3000);

say.call(zmk);
console.log(zmk);			//Person {name: "ZhouMengkang", age: 25, mouth: "mouth"} 

say.call(myComputer);
console.log(myComputer);	//Computer {brand: "dell", price: 3000, mouth: "mouth"} 

zmk 对象和 myComputer 对象毫不相关的两个类的实例,都可以通过 say.call() 来使得他们有 mouth 这个属性。这里想表达的一点就是 Person 类和 Computer 类,毫不相关,不存在继承或者都同一继承谁。这一点需要自己在实际开发过程中慢慢体会,找到适当的完整例子再贴上来。

apply()数组化传参

这个只是其一个小技巧,并不是主要用途。例如取一个数组里面的最大值

var arr=[5,7,9,1,2,3,4,6];

function getMax(arr){
    var arrLen=arr.length;
    for(var i=0,ret=arr[0];i<arrLen;i++){
        ret=Math.max(ret,arr[i]);        
    }
    return ret;
}

这样写麻烦而且低效。如果用apply呢:

var arr=[5,7,9,1,2,3,4,6];
function getMax2(arr){
    return Math.max.apply(null,arr)
}

可以先参考这个帖子:http://www.cnblogs.com/xiaohongwu/archive/2011/06/15/2081237.html

再附带一位朋友自己的常用方法:

var ArrayProto = Array.prototype,
  slice = ArrayProto.slice,

  splat = function (fun) {
    return function (array) {
      return fun.apply(null, array);
    };
  },

  unsplat = function (fun) {
    return function () {
      return fun.call(null, slice.call(arguments));
    };
  };

新增一个apply的例子:http://mengkang.net/272.html

里面是把A函数对象作为参数传入B函数,然后在B函数里使用apply方法来实现A函数的动态调用。

嗨,老铁,欢迎来到我的博客!

如果觉得我的内容还不错的话,可以关注下我在 segmentfault.com 上的直播。我主要从事 PHP 和 Java 方面的开发,《深入 PHP 内核》作者之一。

[视频直播] PHP 进阶之路 - 亿级 pv 网站架构的技术细节与套路 直播中我将毫无保留的分享我这六年的全部工作经验和踩坑的故事,以及会穿插着一些面试中的 考点难点加分点

评论列表