javascript中call apply 与 bind方法详解
在JavaScript中,call、apply和bind是Function对象自带的三个方法,本文将通过几个场景的应用,来详细理解三个方法。
call()
call()方法在使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或方法。
当调用一个函数时,可以赋值一个不同的this对象。this引用当前对象,即call方法的第一个参数。
通过call方法,你可以在一个对象上借用另一个对象上的方法,比如Object.prototype.toString.call([]),就是一个Array对象借用了Object对象上的方法。
语法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,...
指定的参数列表。
例子
初级应用例子
functiona(){ //输出函数a中的this对象 console.log(this); } //定义函数b functionb(){} varobj={name:'这是一个屌丝'};//定义对象obj 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);//functionb(){} a.call(obj);//Object
使用call方法调用匿名函数并且指定上下文的this
在下面的例子中,当调用greet方法的时候,该方法的this值会绑定到i对象。
functiongreet(){ varreply=[this.person,'是一个轻量的',this.role].join(''); console.log(reply); } vari={functiongreet(){ varreply=[this.person,'是一个轻量的',this.role].join(''); console.log(reply); } vari={ person:'JSLite.io',role:'Javascript库。' }; greet.call(i); //JSLite.io是一个轻量的Javascript库。 person:'JSLite.io',role:'Javascript库。' }; greet.call(i); //JSLite.io是一个轻量的Javascript库。
使用call方法调用匿名函数
在下例中的for循环体内,我们创建了一个匿名函数,然后通过调用该函数的call方法,将每个数组元素作为指定的this值执行了那个匿名函数。这个匿名函数的主要目的是给每个数组元素对象添加一个print方法,这个print方法可以打印出各元素在数组中的正确索引号。当然,这里不是必须得让数组元素作为this值传入那个匿名函数(普通参数就可以),目的是为了演示call的用法。
varanimals=[ {species:'Lion',name:'King'}, {species:'Whale',name:'Fail'} ]; for(vari=0;i<animals.length;i++){ (function(i){ this.print=function(){ console.log('#'+i+''+this.species+':'+this.name); } this.print(); }).call(animals[i],i); } //#0Lion:King //#1Whale:Fail
使用call方法调用函数传参数
vara={ name:'JSLite.io',//定义a的属性 say:function(){//定义a的方法 console.log("Hi,I'mfunctiona!"); } }; functionb(name){ console.log("Postparams:"+name); console.log("I'm"+this.name); this.say(); } b.call(a,'test'); //Postparams:test //I'monepixel //I'mfunctiona!
apply()
语法与call()方法的语法几乎完全相同,唯一的区别在于,apply的第二个参数必须是一个包含多个参数的数组(或类数组对象)。apply的这个特性很重要,
在调用一个存在的函数时,你可以为其指定一个this对象。this指当前对象,也就是正在调用这个函数的对象。使用apply,你可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。
语法:fun.apply(thisArg[,argsArray])
注意:需要注意:Chrome14以及InternetExplorer9仍然不接受类数组对象。如果传入类数组对象,它们会抛出异常。
参数
thisArg
同上call的thisArg参数。
argsArray
一个数组或者类数组对象,其中的数组元素将作为单独的参数传给fun函数。如果该参数的值为null或undefined,则表示不需要传入任何参数。从ECMAScript5开始可以使用类数组对象。
例子
functionjsy(x,y,z){ console.log(x,y,z); } jsy.apply(null,[1,2,3]); //123
使用apply来链接构造器的例子
你可以使用apply来给一个对象链接构造器,类似于Java.在接下来的例子中我们会创建一个叫做construct的全局的Function函数,来使你能够在构造器中使用一个类数组对象而非参数列表。
Function.prototype.construct=function(aArgs){ varfConstructor=this, fNewConstr=function(){ fConstructor.apply(this,aArgs); }; fNewConstr.prototype=fConstructor.prototype; returnnewfNewConstr(); }; functionMyConstructor(){ for(varnProp=0;nProp<arguments.length;nProp++){ console.log(arguments,this) this["property"+nProp]=arguments[nProp]; } } varmyArray=[4,"Helloworld!",false]; varmyInstance=MyConstructor.construct(myArray); console.log(myInstance.property1);//logs"Helloworld!" console.log(myInstanceinstanceofMyConstructor);//logs"true" console.log(myInstance.constructor);//logs"MyConstructor"
使用apply和内置函数
聪明的apply用法允许你在某些本来需要写成遍历数组变量的任务中使用内建的函数。在接下里的例子中我们会使用Math.max/Math.min来找出一个数组中的最大/最小值。
//里面有最大最小数字值的一个数组对象 varnumbers=[5,6,2,3,7]; /*使用Math.min/Math.max在apply中应用*/ varmax=Math.max.apply(null,numbers); //一般情况是用Math.max(5,6,..)或者Math.max(numbers[0],...)来找最大值 varmin=Math.min.apply(null,numbers); //通常情况我们会这样来找到数字的最大或者最小值 //比对上面的栗子,是不是下面的看起来没有上面的舒服呢? max=-Infinity,min=+Infinity; for(vari=0;i<numbers.length;i++){ if(numbers[i]>max) max=numbers[i]; if(numbers[i]<min) min=numbers[i]; }
参数数组切块后循环传入
functionminOfArray(arr){ varmin=Infinity; varQUANTUM=32768; for(vari=0,len=arr.length;i<len;i+=QUANTUM){ varsubmin=Math.min.apply(null,arr.slice(i,Math.min(i+QUANTUM,len))); console.log(submin,min) min=Math.min(submin,min); } returnmin; } varmin=minOfArray([5,6,2,3,7]);
bind
bind()函数会创建一个新函数(称为绑定函数)
bind是ES5新增的一个方法
传参和call或apply类似
不会执行对应的函数,call或apply会自动执行对应的函数
返回对函数的引用
语法fun.bind(thisArg[,arg1[,arg2[,...]]])
下面例子:当点击网页时,EventClick被触发执行,输出JSLite.iop1p2,说明EventClick中的this被bind改变成了obj对象。如果你将EventClick.bind(obj,'p1','p2')变成EventClick.call(obj,'p1','p2')的话,页面会直接输出JSLite.iop1p2
varobj={name:'JSLite.io'}; /** *给document添加click事件监听,并绑定EventClick函数 *通过bind方法设置EventClick的this为obj,并传递参数p1,p2 */ document.addEventListener('click',EventClick.bind(obj,'p1','p2'),false); //当点击网页时触发并执行 functionEventClick(a,b){ console.log( this.name,//JSLite.io a,//p1 b//p2 ) } //JSLite.iop1p2
兼容
if(!Function.prototype.bind){ Function.prototype.bind=function(oThis){ if(typeofthis!=="function"){ //closestthingpossibletotheECMAScript5 //internalIsCallablefunction thrownewTypeError("Function.prototype.bind-whatistryingtobeboundisnotcallable"); } varaArgs=Array.prototype.slice.call(arguments,1), fToBind=this,//this在这里指向的是目标函数 fNOP=function(){}, fBound=function(){ returnfToBind.apply(thisinstanceoffNOP ?this//此时的this就是new出的obj :oThis||this,//如果传递的oThis无效,就将fBound的调用者作为this //将通过bind传递的参数和调用时传递的参数进行合并,并作为最终的参数传递 aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype=this.prototype; //将目标函数的原型对象拷贝到新函数中,因为目标函数有可能被当作构造函数使用 fBound.prototype=newfNOP(); //返回fBond的引用,由外部按需调用 returnfBound; }; }
兼容例子来源于:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
应用场景:继承
functionAnimal(name,weight){ this.name=name; this.weight=weight; } functionCat(){ //在call中将this作为thisArgs参数传递 //Animal方法中的this就指向了Cat中的this //所以Animal中的this指向的就是cat对象 //在Animal中定义了name和weight属性,就相当于在cat中定义了这些属性 //cat对象便拥有了Animal中定义的属性,从而达到了继承的目的 Animal.call(this,'cat','50'); //Animal.apply(this,['cat','50']); this.say=function(){ console.log("Iam"+this.name+",myweightis"+this.weight); } } //当通过new运算符产生了cat时,Cat中的this就指向了cat对象 varcat=newCat(); cat.say(); //输出=>Iamcat,myweightis50
原型扩展
在原型函数上扩展和自定义方法,从而不污染原生函数。例如:我们在Array上扩展一个forEach
functiontest(){ //检测arguments是否为Array的实例 console.log( argumentsinstanceofArray,//false Array.isArray(arguments)//false ); //判断arguments是否有forEach方法 console.log(arguments.forEach); //undefined //将数组中的forEach应用到arguments上 Array.prototype.forEach.call(arguments,function(item){ console.log(item);//1234 }); } test(1,2,3,4);