的简单的文章真的很难写,因为网上相关的文章太多了。无意中看到前几天阮老师写的一篇文章里的一句话:“博客对我来说,首先是知识管理工具,其次是交流工具。我的技术文章主要是用来整理我也有的东西不懂的知识,只写自己没有完全掌握的东西,自己精通的东西,常常没有动力去写。炫耀从来不是我的动力,好奇心才是”非常赞同这句话,也让我下定决心要把这篇文章写好。网上的文章虽然很多,但大多是复制粘贴的,理解起来比较费劲。希望通过这篇文章,能够清晰地提高自己对apply、call、bind的理解,并列举其中的一些妙用,加深记忆。apply,call在javascript中,call和apply都是为了改变函数运行时的上下文而存在的,换句话说就是改变函数体内this的指针。JavaScript的一大特点是函数有“定义时上下文”、“运行时上下文”、“上下文可以改变”等概念。先举个栗子:functionfruits(){}fruits.prototype={color:"red",say:function(){console.log("Mycoloris"+this.color);}}varapple=newfruits;apple.say();//Mycolorisred但是如果我们有一个对象banana={color:"yellow"},我们不想在它上面重新定义say方法,那么我们可以通过call或者apply来使用apple的say方法:banana={color:"yellow""}apple.say.call(banana);//Mycolorisyellowapple.say.apply(banana);//Mycolorisyellow所以可以看出call和apply出现动态改变this,当一个对象做nothaveamethod(Banana在这个栗子中没有say方法),但是别人有(apple在这个栗子中有say方法),我们可以用call或者apply来操作其他对象的方法。apply和call的区别apply和call是完全一样的,只是接受参数的方式不同,比如一个函数定义如下:varfunc=function(arg1,arg2){};可以这样调用:func.call(this,arg1,arg2);func.apply(this,[arg1,arg2])其中this是你要指定的上下文,可以是任意JavaScript对象(JavaScript中一切都是对象),call需要依次传入参数,apply将参数放入数组。在JavaScript中,函数的参数个数是不固定的,所以如果要应用条件,请在参数明确的情况下使用call。不确定的时候就用apply,然后把参数push到数组里传进去。当参数个数不确定的时候,函数也可以通过arguments数组遍历所有的参数。为了巩固和加深记忆,下面列出一些常用的用法:1、添加vararray1=[12,"foo",{name"Joe"},-2458];vararray2=["Doe",555,100];数组。arrays之间的原型.push.apply(array1,array2);/*array1的值为[12,"foo",{name"Joe"},-2458,"Doe",555,100]*/2。获取数组中的***值和最小值varnumbers=[5,458,120,-215];varmaxInNumbers=Math.max.apply(Math,numbers),//458maxInNumbers=Math.max.call(Math,5,458,120,-215);//458number本身没有max方法,但是Math有,所以我们可以利用它调用或应用的方法。3.验证是否为数组(前提是toString()方法没有被覆盖)functionisArray(obj){returnObject.prototype.toString.call(obj)==='[objectArray]';}4.Class(伪)数组使用数组方法vardomNodes=Array.prototype.slice.call(document.getElementsByTagName("*"));Javascript中有一种叫做伪数组的对象结构。比较特殊的是arguments对象,还有调用getElementsByTagName、document.childNodes等,返回的NodeList对象都是伪数组。无法应用Array下的push、pop等方法。但是我们可以使用Array.prototype.slice.call将其转化为一个真正数组的length属性的对象,这样domNodes就可以应用Array下的所有方法。深入理解apply和call的使用我们借用一道面试题来更深入地理解apply和call。定义一个日志方法,以便它可以代理console.log方法。常见的解决方法是:functionlog(msg) {console.log(msg);}log(1);//1log(1,2);//1上面的方法可以解决最基本的需求,但是当传入的参数个数不确定时,上面的方法就会失效。这时候可以考虑使用apply或者call,注意这里传入的参数有多少。不确定,所以最好用apply,方法如下:functionlog(){console.log.apply(console,arguments);};log(1);//1log(1,2);//12接下来的需求是为每条日志消息加上“(app)”前缀,例如:log("helloworld");//如何更优雅地做(app)helloworld?这时候就需要想到arguments参数是一个伪数组,通过Array.prototype.slice.call转换为标准数组,然后使用数组方法unshift,像这样:functionlog(){varargs=Array.prototype.slice.call(arguments);args.unshift('(app)');console.log.apply(console,args);};bind说完apply和call,再说说bind。bind()方法与apply和call很相似,同样可以改变函数体中this的指针。MDN的解释是:bind()方法会创建一个新的函数,称为绑定函数,当调用这个绑定函数时,绑定函数将使用创建时传递给bind()方法的第一个参数作为this,第二个并且后来传递给bind()方法的参数加上运行时绑定函数本身的参数作为原函数的参数,以便调用原函数。让我们直接看看如何使用它。在常见的单例模式中,我们通常使用_this、that、self等来保存this,以便我们在改变上下文后可以继续引用它。像这样:varfoo={bar:1,eventBind:function(){var_this=this;$('.someClass').on('click',function(event){/*Actontheevent*/console.log(_this.bar);//1});}}由于Javascript独特的机制,上下文从eventBind:function(){}过渡到$('.someClass').on('click',function(event){})有了变化,上面用变量保存this的方法有用,没有问题。当然使用bind()可以更优雅的解决这个问题:varfoo={bar:1,eventBind:function(){$('.someClass').on('click',function(event){/*Actontheevent*/console.log(this.bar);//1}.bind(this));}}上面代码中,bind()创建了一个函数,当click事件被绑定调用时,其this关键字就会被设置为传入的值(在本例中为调用bind()时传入的参数)。因此,在这里我们将所需的上下文this(实际上是foo)传递给bind()函数。然后,当执行回调函数时,this将指向foo对象。再举个简单的栗子:varbar=function(){console.log(this.x);}bar();//undefinedvarfunc=bar.bind(foo);func();//3这里我们新建了一个函数func,当使用bind()创建绑定函数时,当它被执行时,它的this将被设置为foo而不是像我们调用bar()时那样的全局范围。有一个有趣的问题,如果连续两次bind(),或者连续三次bind(),输出值是多少?像这样:varbar=function(){console.log(this.x);}varfoo={x:3}varsed={x:4}varfunc=bar.bind(foo).bind(sed);func();//?varfiv={x:5}varfunc=bar.bind(foo)。绑定(sed).bind(fiv);func();//?答案是它仍然会两次输出3,而不是预期的4和5。原因是,在Javascript中,多次bind()是无效的。更深层次的原因是,bind()的实现相当于用函数在里面包裹了一个call/apply,而第二次bind()相当于又把第一次bind()包裹了一遍,所以第二次之后的bind不是有效的。比较apply、call、bind那么,apply、call、bind有哪些相同点和不同点呢?什么时候用apply、call,什么时候用bind。举个简单的栗子:varobj={x:81,};varfoo={getX:function(){returnthis.x;}}console.log(foo.getX.bind(obj)());//81console.log(foo.getX.call(obj));//81console.log(foo.getX.apply(obj));//81的三个输出都是81,但是要注意bind()方法的使用,后面的括号太多了。换句话说,区别在于当你想执行回调而不是在更改上下文后立即执行时,请使用bind()方法。而apply/call立即执行函数。再总结一下:apply、call、bind都是用来改变函数的this对象的指向;apply、call、bind的第一个参数是this要指向的对象,也就是你要指定的context;apply、call、bind可以使用后续参数传递参数;bind返回对应的函数,方便后面调用;立即申请并致电。本文示例中出现的所有代码都可以在我的github上下载。
