JavaScript——call()、apply()和bind()
TOC在JavaScript中每个函数都包含两个非继承而来的函数apply()和call()这两个函数的作用是一样的都是为了改变函数运行时的上下文而存在的实际就是改变函数体内this的指向。而bind()函数也可以达到这个目的但是在处理方式上与call()函数和apply()函数有一定的区别接下来我们就详细看下3者的使用方式。1、call()call()函数调用一个函数时会将该函数的执行对象上下文改变为另一个对象。其语法如下所示。function.call(thisArg,arg1,arg2,...)function为需要调用的函数。thisArg表示的是新的对象上下文函数中的this将指向thisArg如果thisArg为null或者undefined则this会指向全局对象。arg1arg2...表示的是函数所接收的参数列表。可以通过下面的实例看看call()函数的用法。//定义一个add()函数functionadd(x,y){returnxy;}//通过call()函数进行add()函数的调用functionmyAddCall(x,y){//调用add()函数的call()函数returnadd.call(this,x,y);}console.log(myAddCall(10,20));//30myAddCall()函数自身是不具备运算能力的但是我们在myAddCall()函数中通过调用add()函数的call()函数并传入this值将执行add()函数的主体改变为myAddCall()函数自身然后传入参数x和y这就使得myAddCall()函数拥有add()函数计算求和的能力。在实际计算时就为10 20 30。2、apply()apply()函数的作用域与call()函数是一致的只是在传递参数的形式上存在差别。其语法格式如下。function.apply(thisArg,[argsArray])function与thisArg参数与call()函数中的解释一样。·[argsArray]表示的是参数会通过数组的形式进行传递如果argsArray不是一个有效的数组或者arguments对象则会抛出一个TypeError异常。要想实现与call()函数中一样的实例效果可以使用apply()函数编写以下代码。//定义一个add()函数functionadd(x,y){returnxy;}//通过apply()函数进行add()函数的调用functionmyAddApply(x,y){//调用add()函数的apply()函数returnadd.apply(this,[x,y]);}console.log(myAddApply(10,20));//30与call()函数相比apply()函数只需要将add()函数接收的参数使用数组的形式传递即可即使用[x, y]的形式运行后的结果为10 20 30。3、bind()bind()函数创建一个新的函数在调用时设置this关键字为提供的值在执行新函数时将给定的参数列表作为原函数的参数序列从前往后匹配。其语法格式如下。function.bind(thisArg,arg1,arg2,...)事实上bind()函数与call()函数接收的参数是一样的。其返回值是原函数的副本并拥有指定的this值和初始参数。如果我们想要实现上面实例的效果可以编写以下代码。//定义一个add()函数functionadd(x,y){returnxy;}//通过bind()函数进行add()函数的调用functionmyAddBind(x,y){//调用bind()函数得到一个新的函数letbindAddFnadd.bind(this,x,y);//执行新的函数returnbindAddFn();}console.log(myAddBind(10,20));//304、比较三者的相同之处是都会改变函数调用的执行主体修改this的指向。不同之处表现在以下两点。第一点是关于函数立即执行call()函数与apply()函数在执行后会立即调用前面的函数而bind()函数不会立即调用它会返回一个新的函数可以在任何时候进行调用。第二点是关于参数传递call()函数与bind()函数接收的参数相同第一个参数表示将要改变的函数执行主体即this的指向从第二个参数开始到最后一个参数表示的是函数接收的参数而对于apply()函数第一个参数与call()函数、bind()函数相同第二个参数是一个数组表示的是接收的所有参数如果第二个参数不是一个有效的数组或者arguments对象则会抛出一个TypeError异常。5、使用案例5.1、求数组中的最大项和最小项Array数组本身没有max()函数和min()函数无法直接获取到最大值和最小值但是Math却有求最大值和最小值的max()函数和min()函数。我们可以使用apply()函数来改变Math.max()函数和Math.min()函数的执行主体然后将数组作为参数传递给Math.max()函数和Math.min()函数。vararr[3,5,7,2,9,11];// 求数组中的最大值console.log(Math.max.apply(null,arr));// 11// 求数组中的最小值console.log(Math.min.apply(null,arr));// 2apply()函数的第一个参数为null这是因为没有对象去调用这个函数我们只需要这个函数帮助我们运算得到返回结果。第二个参数是数组本身就是需要参与max()函数和min()函数运算的数据运算结束后得到返回值表示数组的最大值和最小值。5.2、类数组对象转换为数组对象函数的参数对象arguments是一个类数组对象自身不能直接调用数组的方法但是我们可以借助call()函数让arguments对象调用数组的slice()函数从而得到一个真实的数组后面就能调用数组的函数。任意个数字的求和的代码如下所示。// 任意个数字的求和functionsum(){// 将传递的参数转换为数组vararrArray.prototype.slice.call(arguments);// 调用数组的reduce()函数returnarr.reduce(function(pre,cur){returnprecur;},0)}sum(1,2);// 3sum(1,2,3);// 6sum(1,2,3,4);// 105.3、用于继承// 父类functionAnimal(age){// 属性this.ageage;// 实例函数this.sleepfunction(){returnthis.name正在睡觉;}}// 子类functionCat(name,age){// 使用call()函数实现继承Animal.call(this,age);this.namename||tom;}varcatnewCat(tony,11);console.log(cat.sleep());// tony正在睡觉console.log(cat.age);// 11其中关键的语句是子类中的Animal.call(this, age)在call()函数中传递this表示的是将Animal构造函数的执行主体转换为Cat对象从而在Cat对象的this上会增加age属性和sleep函数子类实际相当于如下代码。functionCat(name,age){// 来源于对父类的继承this.ageage;this.sleepfunction(){returnthis.name正在睡觉;};// Cat自身的实例属性this.namename||tom;}5.4、执行匿名函数假如存在这样一个场景有一个数组数组中的每个元素是一个对象对象是由不同的属性构成现在我们想要调用一个函数输出每个对象的各个属性值。我们可以通过一个匿名函数在匿名函数的作用域内添加print()函数用于输出对象的各个属性值然后通过call()函数将该print()函数的执行主体改变为数组元素这样就可以达到目的了。varanimals[{species:Lion,name:King},{species:Whale,name:Fail}];for(vari0;ianimals.length;i){(function(i){this.printfunction(){console.log(#i this.species: this.name);};this.print();}).call(animals[i],i);}在上面的代码中在call()函数中传入animals[i]这样匿名函数内部的this就指向animals[i]在调用print()函数时this也会指向animals[i]从而能输出speices属性和name属性。5.5、bind()函数配合setTimeout在默认情况下使用setTimeout()函数时this关键字会指向全局对象window。当使用类的函数时需要this引用类的实例我们可能需要显式地把this绑定到回调函数以便继续使用实例。// 定义一个函数functionLateBloomer(){this.petalCountMath.ceil(Math.random()*12)1;}// 定义一个原型函数LateBloomer.prototype.bloomfunction(){// 在一秒后调用实例的declare()函数很关键的一句window.setTimeout(this.declare.bind(this),1000);};// 定义原型上的declare()函数LateBloomer.prototype.declarefunction(){console.log(I am a beautiful flower with this.petalCount petals!);};// 生成LateBloomer的实例varflowernewLateBloomer();flower.bloom();// 1秒后调用declare()函数在上面的代码中关键的语句在bloom()函数中我们期望通过一个定时器设置在1秒后调用实例的declare()函数。很多人可能会写出下面这样的代码。LateBloomer.prototype.bloomfunction(){window.setTimeout(this.declare,1000);};此时当我们调用setTimeout()函数时由于其调用体是window因此在setTimeout()函数内部的this指向的是window而不是对象的实例。这样在1秒后调用declare()函数时其中的this将无法访问到petalCount属性从而返回“undefined”输出结果如下所示。Iam a beautiful flowerwithundefined petals!因此我们需要手动修改this的指向而通过bind()函数能够达到这个目的。通过bind()函数传入实例的this值这样在setTimeout()函数内部调用declare()函数时declare()函数中的this就会指向实例本身从而就能访问到petalCount属性。LateBloomer.prototype.bloomfunction(){window.setTimeout(this.declare.bind(this),1000);};I am a beautiful flower with 4 petals!