Javascript中call(),apply(),bind()方法、区别与异同

一 call()方法:

 

call() 方法在使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或方法.

Call() 语法

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

Call() 参数

thisArg

在fun函数运行时指定的this值。需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,

则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。

arg1, arg2, ...

指定的参数列表。

Javascript中的call()方法

先不关注上面那些复杂的解释,一步步地开始这个过程。

Call()方法的实例

于是写了另外一个Hello,World:

 

function print(p1, p2) {
    console.log( p1 + ' ' + p2);
}
print("Hello", "World");
print.call(undefined, "Hello", "World");

 

两种方式有同样的输出结果,然而,相比之下call方法还传进了一个undefined。

接着,我们再来看另外一个例子。

 

var obj=function(){};
function print(p1, p2) {
    console.log( p1 + ' ' + p2);
}

print.call(obj, "Hello", "World");

 

只是在这里,我们传进去的还是一个undefined,因为上一个例子中的undefined是因为需要传进一个参数。

这里并没有真正体现call的用法,看看一个更好的例子。

 

function print(name) {
    console.log( this.p1 + ' ' + this.p2);
}

 

var h={p1:"hello", p2:"world", print:print};
h.print("fd");

var h2={p1:"hello", p2:"world"};
print.call(h2, "nothing");

 

call就用就是借用别人的方法、对象来调用,就像调用自己的一样。在h.print,当将函数作为方法调用时,this将指向相关的对象。

只是在这个例子中我们没有看明白,到底是h2调了print,还是print调用了h2。

于是引用了Mozilla的例子。

 

function Product(name, price) {
    this.name = name;
    this.price = price;

   if (price < 0)
        throw RangeError('Cannot create product "' + name + '" with a negative price');
    return this;
}

function Food(name, price) {
    Product.call(this, name, price);
    this.category = 'food';
}
Food.prototype = new Product();

var cheese = new Food('feta', 5);
console.log(cheese);

 

在这里我们可以真正地看明白,到底是哪个对象调用了哪个方法。

例子中,使用Food构造函数创建的对象实例都会拥有在Product构造函数中添加的 name 属性和 price 属性,但 category 属性是在各自的构造函数中定义的。

 

function print(name) {
    console.log( this.p1 + ' ' + this.p2);
}

var h2= function(no){
    this.p1 = "hello";
    this.p2 = "world";
    print.call(this, "nothing");
};
h2();

 

这里的h2作为一个接收者来调用函数print。正如在Food例子中,在一个子构造函数中,你可以通过调用父构造函数的 call 方法来实现继承。

 

二 apply()方法:

 

我首先从网上查到关于apply和call的定义,然后用示例来解释这两个方法的意思和如何去用.

apply:方法能劫持另外一个对象的方法,继承另外一个对象的属性.

 

Function.apply(obj,args)方法能接收两个参数
obj:这个对象将代替Function类里this对象
args:这个是数组,它将作为参数传给Function(args-->arguments)

 

call:和apply的意思一样,只不过是参数列表不一样.

 

 Function.call(obj,[param1[,param2[,…[,paramN]]]])
obj:这个对象将代替Function类里this对象
params:这个是一个参数列表

 

apply示例:

 

  1. <script type="text/javascript">  
  2.     /*定义一个人类*/  
  3.     function Person(name,age)  
  4.     {  
  5.         this.name=name;  
  6.         this.age=age;  
  7.     }  
  8.     /*定义一个学生类*/  
  9.     functionStudent(name,age,grade)  
  10.     {  
  11.         Person.apply(this,arguments);  
  12.         this.grade=grade;  
  13.     }  
  14.     //创建一个学生类  
  15.     var student=new Student("zhangsan",21,"一年级");  
  16.     //测试  
  17.     alert("name:"+student.name+"\n"+"age:"+student.age+"\n"+"grade:"+student.grade);  
  18.     //大家可以看到测试结果name:zhangsan age:21  grade:一年级  
  19.     //学生类里面我没有给name和age属性赋值啊,为什么又存在这两个属性的值呢,这个就是apply的神奇之处.  
  20. </script>  

分析: Person.apply(this,arguments);

this:在创建对象在这个时候代表的是student

arguments:是一个数组,也就是[“zhangsan”,”21”,”一年级”];

也就是通俗一点讲就是:用student去执行Person这个类里面的内容,在Person这个类里面存在this.name等之类的语句,这样就将属性创建到了student对象里面

 

call示例

在Studen函数里面可以将apply中修改成如下:

Person.call(this,name,age);

这样就ok了

 

 什么情况下用apply,什么情况下用call:

在给对象参数的情况下,如果参数的形式是数组的时候,比如apply示例里面传递了参数arguments,这个参数是数组类型,

并且在调用Person的时候参数的列表是对应一致的(也就是Person和Student的参数列表前两位是一致的) 就可以采用 apply ,

如果我的Person的参数列表是这样的(age,name),而Student的参数列表是(name,age,grade),这样就可以用call来实现了,

也就是直接指定参数列表对应值的位置(Person.call(this,age,name,grade));

 

apply的一些其他巧妙用法:

细心的人可能已经察觉到,在我调用apply方法的时候,第一个参数是对象(this), 第二个参数是一个数组集合, 在调用Person的时候,

他需要的不是一个数组,但是为什么他给我一个数组我仍然可以将数组解析为一个一个的参数,这个就是apply的一个巧妙的用处,

可以将一个数组默认的转换为一个参数列表([param1,param2,param3] 转换为 param1,param2,param3) 这个如果让我们用程序来实现将数组的每一个项,

来装换为参数的列表,可能都得费一会功夫,借助apply的这点特性,所以就有了以下高效率的方法:

 

 Math.max 可以实现得到数组中最大的一项

因为Math.max 参数里面不支持Math.max([param1,param2]) 也就是数组

但是它支持Math.max(param1,param2,param3…),所以可以根据刚才apply的那个特点来解决 var max=Math.max.apply(null,array),

这样轻易的可以得到一个数组中最大的一项(apply会将一个数组装换为一个参数接一个参数的传递给方法)

这块在调用的时候第一个参数给了一个null,这个是因为没有对象去调用这个方法,我只需要用这个方法帮我运算,得到返回的结果就行,.所以直接传递了一个null过去

 

Math.min  可以实现得到数组中最小的一项

同样和 max是一个思想 var min=Math.min.apply(null,array);

 

Array.prototype.push 可以实现两个数组合并

同样push方法没有提供push一个数组,但是它提供了push(param1,param,…paramN) 所以同样也可以通过apply来装换一下这个数组,即:

  1. vararr1=new Array("1","2","3");  
  2. vararr2=new Array("4","5","6");    
  3. Array.prototype.push.apply(arr1,arr2);  

也可以这样理解,arr1调用了push方法,参数是通过apply将数组装换为参数列表的集合.

通常在什么情况下,可以使用apply类似Math.min等之类的特殊用法:

 

一般在目标函数只需要n个参数列表,而不接收一个数组的形式([param1[,param2[,…[,paramN]]]]),可以通过apply的方式巧妙地解决这个问题!

 

三 bind()方法:

 

在讨论bind()方法之前我们先来看一道题目:

 

var altwrite = document.write;  
altwrite("hello");  


 

对于上面这道题目,答案并不是太难,主要考点就是this指向的问题,altwrite()函数改变this的指向global或window对象,导致执行时提示非法调用异常,正确的方案就是使用bind()方法:

altwrite.bind(document)("hello")  


当然也可以使用call()方法:

altwrite.call(document, "hello")  


本文的重点在于讨论bind()方法的实现,在开始讨论bind()的实现之前,我们先来看看bind()方法的使用:

绑定函数
bind()最简单的用法是创建一个函数,使这个函数不论怎么调用都有同样的this值。常见的错误就像上面的例子一样,将方法从对象中拿出来,

然后调用,并且希望this指向原来的对象。如果不做特殊处理,一般会丢失原来的对象。使用bind()方法能够很漂亮的解决这个问题:

 

this.num = 9;

var mymodule = {

 num: 81,

 getNum: function() { return this.num; }

};

module.getNum(); // 81

var getNum = module.getNum;

getNum(); // 9, 因为在这个例子中,"this"指向全局对象

// 创建一个'this'绑定到module的函数

var boundGetNum = getNum.bind(module);

boundGetNum(); // 8

 

偏函数(Partial Functions)

这是一个很好的特性,使用bind()我们设定函数的预定义参数,然后调用的时候传入其他参数即可:

 

function list() {

 return Array.prototype.slice.call(arguments);

}

var list1 = list(1, 2, 3); // [1, 2, 3]

// 预定义参数37

var leadingThirtysevenList = list.bind(undefined, 37);

var list2 = leadingThirtysevenList(); // [37]

var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]

 

和setTimeout一起使用

一般情况下setTimeout()的this指向window或global对象。当使用类的方法时需要this指向类实例,就可以使用bind()将this绑定到回调函数来管理实例。

 

function Bloomer() {

 this.petalCount = Math.ceil(Math.random() * 12) + 1;

}

// 1秒后调用declare函数

Bloomer.prototype.bloom = function() {

 window.setTimeout(this.declare.bind(this), 1000);

};

Bloomer.prototype.declare = function() {

 console.log('我有 ' + this.petalCount + ' 朵花瓣!');

};

注意:对于事件处理函数和setInterval方法也可以使用上面的方法

 

绑定函数作为构造函数

绑定函数也适用于使用new操作符来构造目标函数的实例。当使用绑定函数来构造实例,注意:this会被忽略,但是传入的参数仍然可用。

function Point(x, y) {

 this.x = x;

 this.y = y;

}

Point.prototype.toString = function() {

 return this.x + ',' + this.y;

};

var p = new Point(1, 2);

p.toString(); // '1,2'

var emptyObj = {};

var YAxisPoint = Point.bind(emptyObj, 0/*x*/);

// 实现中的例子不支持,

// 原生bind支持:

var YAxisPoint = Point.bind(null, 0/*x*/);

var axisPoint = new YAxisPoint(5);

axisPoint.toString(); // '0,5'

axisPoint instanceof Point; // true

axisPoint instanceof YAxisPoint; // true

new Point(17, 42) instanceof YAxisPoint; // true

 

 

上面例子中Point和YAxisPoint共享原型,因此使用instanceof运算符判断时为true。

捷径

bind()也可以为需要特定this值的函数创造捷径。

例如要将一个类数组对象转换为真正的数组,可能的例子如下:

 

var slice = Array.prototype.slice;

// ...

slice.call(arguments);

 

如果使用bind()的话,情况变得更简单:

 

var unboundSlice = Array.prototype.slice;

var slice = Function.prototype.call.bind(unboundSlice);

// ...

slice(arguments);

 

实现

上面的几个小节可以看出bind()有很多的使用场景,但是bind()函数是在 ECMA-262 第五版才被加入;它可能无法在所有浏览器上运行。这就需要我们自己实现bind()函数了。

首先我们可以通过给目标函数指定作用域来简单实现bind()方法:

 

Function.prototype.bind = function(context){

 self = this; //保存this,即调用bind方法的目标函数

 return function(){

   return self.apply(context,arguments);

 };

};

 

考虑到函数柯里化的情况,我们可以构建一个更加健壮的bind():

 

Function.prototype.bind = function(context){

 var args = Array.prototype.slice.call(arguments, 1),

 self = this;

 return function(){

   var innerArgs = Array.prototype.slice.call(arguments);

   var finalArgs = args.concat(innerArgs);

   return self.apply(context,finalArgs);

 };

};

 

这次的bind()方法可以绑定对象,也支持在绑定的时候传参。

继续,Javascript的函数还可以作为构造函数,那么绑定后的函数用这种方式调用时,情况就比较微妙了,需要涉及到原型链的传递:

 

Function.prototype.bind = function(context){

 var args = Array.prototype.slice(arguments, 1),

 F = function(){},

 self = this,

 bound = function(){

   var innerArgs = Array.prototype.slice.call(arguments);

   var finalArgs = args.concat(innerArgs);

   return self.apply((this instanceof F ? this : context), finalArgs);

 };

 

 F.prototype = self.prototype;

 bound.prototype = new F();

 return bound;

};

 

这是《JavaScript Web Application》一书中对bind()的实现:通过设置一个中转构造函数F,使绑定后的函数与调用bind()的函数处于同一原型链上,

用new操作符调用绑定后的函数,返回的对象也能正常使用instanceof,因此这是最严谨的bind()实现。

对于为了在浏览器中能支持bind()函数,只需要对上述函数稍微修改即可:

 

Function.prototype.bind = function (oThis) {

  if (typeof this !== "function") {

   throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");

  }

 var aArgs = Array.prototype.slice.call(arguments, 1),

    fToBind = this,

    fNOP = function () {},

    fBound = function () {

     return fToBind.apply(

       this instanceof fNOP && oThis ? this : oThis || window,

       aArgs.concat(Array.prototype.slice.call(arguments))

     );

    };

fNOP.prototype = this.prototype;

  fBound.prototype = new fNOP();

return fBound;

 };

 

以上这篇浅析Javascript中call(),apply(),bind()方法;希望能给大家一个参考。

 

 

 

 

 

 

 

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值