JS: call和apply 区别

本文详细探讨了JavaScript中call和apply方法的应用,包括如何通过它们改变函数调用上下文、模拟面向对象继承以及在Prototype框架中创建类和绑定事件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://blog.163.com/bobo0218bo@126/blog/static/3329139320071115111153224/



因为继续研究Ajax Framework的原因,更多的接触了call和apply

再次论述call和apply,

1)无疑关于call,最简单的解释就是:把隐藏的第一个参数显示化。因为通常一个函数(Function)的调用,会有一个额外的隐藏参数,就是函数(Function)所属的对象(如果没有所特指,则为global(如window)对象),在函数内你可用this关键字访问之。

从call的构造式 -- call(thisArg[,arg1,arg2…] ]);可看出

call(apply)方法可将一个函数的对象上下文(Function Context)从初始的上下文改变为由 thisObj 指定的新对象,这就是利用call(apply)的最大优势。说到此,我们不妨提提所谓的Function Context到底是为何物。先看看下面FunctionContextDemo:

var changed={ item:"banana", act: "eating" };

var original={

        item: "chair",

 act: "sitting in",

 ask: function(){

  return "who's been "+this.act+" my "+this.item;

 }

};

alert("Original : " + original.ask());

alert("Not so simple,that have been changed to: " + original.ask.call(changed));

解析上述代码:

最初存在2个对象changed和original,changed即就是一个数组,分别有item和act属性,而original除了具有和changed一样的item和act属性,还具有一个ask函数(询问到底是谁坐在椅子上),故当调用original.ask()时,可以看到意料中的结果:who's been sitting in my chair.而仔细往下看,当我们调用original.ask.call(changed)时,你觉得会出现什么的结果呢?在此,是利用了call把original的方法(函数)ask给与changed对象来执行,原来changed是没有ask方法(函数),如此绑定之后,函数的对象上下文(Function Context)即就是changed对象,故在方法(函数)ask里所调用的this就应该是changed对象,则可推知original.ask.call(changed)的结应该是:who's been eating my banana.

通过FunctionContextdemo例子可看出如果我们需要在编程过程中需要替换函数的对象上下文(Function Context),call就是不错的选择。

你可以试着把FunctionContextdemo例子修改如下,再看看是什么结果:

var changed={ item:"banana", act: "eating" };

var original={item: "chair",act: "sitting in"};

function ask(){

   return "who's been "+this.act+" my "+this.item;

}

alert("Original : " + ask.call(original));

alert("changed: " + ask.call(changed));

2)javascript如何利用call来模拟面向对象中的继承的,而且可以实现多重继承

// 多重继承

function base1() {

    this.member = " base1_Member";

    this.showSelf = function() {

        window.alert(this.member);

    }

}

function base2() {

    this.person = " base2_Member";

 this.act = " is dancing happily";

    this.showAction = function() {

        window.alert(this.person + this.act);

    }

}

function extend() {

    base1.call(this);

    base2.call(this);

}

window.onload=function(){

   var demo = new extend();

   demo.showSelf();

   demo.showAction();

}

但仔细深入看看,这样的继承是有问题的,直接在类函数体里面定义成员方法,将导致每个实例都有副本,重复占用了内存。

最为优雅简洁的方式应该算是基于原型(prototype)继承。

3)接下来我们再次来看看javascript框架prototype里是如何利用apply来创建一个定义类的模式:

var Class = {

  create: function() {

    return function() {

      this.initialize.apply(this, arguments);

    }

  }

};

解析:

从代码看,该对象仅包含一个方法:Create,其返回一个函数,即类。但这也同时是类的构造函数,其中调用initialize,而这个方法是在类创建时定义的初始化函数,如此就可以实现prototype中的类创建模式.目的是规定如此一个类创建模式,让类的初始化函数名一定是initialize(),而this.initialize.apply(this, arguments);(令人有些费解)则是保证initialize一定会在类的实例创建后调用,既方便管理又体现Object-Oriented的思想。

注意: 里边的this其实是同一对象,即相当于类本身调用自己的构造函数来创建Class对象! 因为apply方法的第二个参数本身要求是一个数组,所以传递给该函数的参数也传递给类的initialize方法,如果直接写为 this.initialize(arguments); 则所有的参数是作为一个数组传递给了initialize构造函数。

var vehicle=Class.create();

vehicle.prototype={

    initialize:function(type){

        this.type=type;

    }

    showSelf:function(){

        alert("this vehicle is "+ this.type);

    }

}

var moto=new vehicle("Moto");

moto.showSelf();

4) 活用apply(javascript框架prototype的事件绑定):

Function.prototype.bind = function() {

  var __method = this; 

  var args = $A(arguments); 

  var object = args.shift();

  return function() { 

    // 调用函数的apply方法执行函数, 其中的object为目标对象, args为bind方法中的参数列表(除了第一个参数以外的参数构成的数组)

    return __method.apply(object, args.concat($A(arguments)));// 事实上, 这里的$A(arguments)一定是一个空数组

  }

}

代码解析:

该bind用途在于将某个函数绑定到特定的函数去执行,

a) var __method = this;这首先把Function Context重新赋值到一个本地变量,使得在Closure(这是一个javascript的特性,可解释为"闭包")中都能访问到,如此在下边就可以方便获取了。它实质就是bind方法的调用者, 是一个函数对象。

b) var args = $A(arguments);这里因为arguments本就是一个类数组的对象,通过$A(arguments)将传入到bind方法的参数都转化为array.

c) var object = args.shift();通过截取args的第一个参数获取Target Object(目标对象),此时args为除了第一个参数以外的参数构成的数组(array)

d) 这是最关键的一步,返回一个新的函数对象(不带有任何的参数的函数),在此通过apply把__method(bind方法的调用者)绑定到Target Object(目标对象),并给与除了Target Object(目标对象)之外的所有参数构成的数组args.concat($A(arguments)),最终Target Object(目标对象)就可执行__method了。

如此费劲周折的绑定某一个函数所换来的优势是,从此你不需要顾及Function Context的混乱所带来的额外顾虑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值