Javascript如何使用.call()和.apply()编写更好的代码
小编给大家分享一下Javascript如何使用.call()和.apply()编写更好的代码,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!
Javascript函数对象的原型公开了两个有价值的方法,分别是call()和apply()。
首先,让我们了解每种方法的作用。
.call()作用call()函数用于通过为其提供的this
的上下文来调用函数。它允许我们通过在特定函数内显式提供用于this
事件的对象来调用函数。
为了更好地了解为什么存在这种方法,请考虑以下示例:
functionsayHello(){console.log(`Hello,${this.name}`);}sayHello();//Hello,undefined
如你所见,this
函数内部指的是全局作用域。在上面的代码中,sayHello
函数试图在全局范围内查找名为name
变量。由于不存在这样的变量,它打印出undefined
.如果我们定义了一个在全局范围内调用的name
变量,该函数将按预期工作,如下所示:
constname='archeun';functionsayHello(){console.log(`Hello,${this.name}`);}sayHello();//Hello,archeun
如果我们严格在上面的代码中使用了模式,它实际上会抛出一个运行时错误,因为this
将是未定义的。
这里的缺点是sayHello
函数假定this
变量的范围,我们无法控制它。根据我们执行它的词法范围,该函数的行为会有所不同。这时候call()
方法派上用场了。如你所知,它允许我们显式注入我们需要用于this
函数内部变量的对象:
考虑下面的例子:
constname='archeun';functionsayHello(){console.log(`Hello,${this.name}`);}sayHello();//Hello,archeunconstvisitor={name:'Mylord!'}/***Thefirstparameterofthecallmethodis,*theobjecttobeusedforthe`this`contextinsidethefunction.*Sowhenthe`sayHello`functionencounters`this.name`,itnowknows*torefertothe`name`keyofthe`visitor`objectwepassed*tothe`call`method.*/sayHello.call(visitor);//Hello,Mylord!/***Herewedonotprovidethe`this`context.*ThisisidenticaltocallingsayHello().*Thefunctionwillassumetheglobalscopefor`this`.*/sayHello.call();//Hello,archeun
除了this
作为方法的第一个参数传递的上下文之外,call()
还接受被调用函数的参数。在第一个参数之后,我们传递给call()
方法的所有其他参数都将作为参数传递给被调用函数。
functionsayHello(greetingPrefix){console.log(`${greetingPrefix},${this.name}`);}constvisitor={name:'Mylord!'}/***Here`Hello`willbepassedastheargumenttothe*`greetingPrefix`parameterofthe`sayHello`function*/sayHello.call(visitor,'Hello');//Hello,Mylord!/***Here`Howdy`willbepassedastheargumenttothe*`greetingPrefix`parameterofthe`sayHello`function*/sayHello.call(visitor,'Howdy');//Howdy,Mylord!使用方法1. 可重用的上下文无关函数
我们可以编写一个函数并在不同的this
上下文中调用它:
functionsayHello(greetingPrefix){console.log(`${greetingPrefix},${this.name}`);}constmember={name:'Well-knownmember'}constguest={name:'Randomguest'}/***`sayHello`functionwillrefertothe`member`object*wheneveritencouneters`this`*/sayHello.call(member,'Hello');//Hello,Well-knownmember/***`sayHello`functionwillrefertothe`guest`object*wheneveritencouneters`this`*/sayHello.call(guest,'Howdy');//Howdy,Randomguest
如你所见,如果使用得当,这会提高代码的可重用性和可维护性。
2. 构造函数链我们可以使用call()
方法来链接通过函数创建的对象的构造函数。使用该函数创建对象时,函数可以采用另一个函数作为其构造函数。如下例所示,Dog
和Fish
都调用Animal
函数来初始化它们的公共属性,即name
和noOfLegs
:
functionAnimal(name,noOfLegs){this.name=name;this.noOfLegs=noOfLegs;}functionDog(name,noOfLegs){//ReuseAnimalfunctionastheDogconstructorAnimal.call(this,name,noOfLegs);this.category='mammals';}functionFish(name,noOfLegs){//ReuseAnimalfunctionastheFishconstructorAnimal.call(this,name,noOfLegs);this.category='fish';}consttiny=newDog('Tiny',4);constmarcus=newFish('Marcus',0);console.log(tiny);//{name:"Tiny",noOfLegs:4,category:"mammals"}console.log(marcus);//{name:"Marcus",noOfLegs:0,category:"fish"}
这也是代码重用的一种变体。这种模式还使我们能够用其他语言编写接近 OOP 原则的代码。
3. 使用对象调用匿名函数匿名函数继承调用它们的词法作用域。我们可以使用call()
方法将this
作用域显式注入匿名函数。考虑下面的例子:
constanimals=[{type:'Dog',name:'Tiny',sound:'Bowwow'},{type:'Duck',name:'Marcus',sound:'Quack'}];for(leti=0;i<animals.length;i++){/***Thisanonymousfunctionnowhasaccesstoeachanimalobject*through`this`.*/(function(i){this.makeSound=function(){console.log(`${this.name}says${this.sound}!`);}this.makeSound();}).call(animals[i],i);}//TinysaysBowwow!//MarcussaysQuack!
在这里,我们不必实现一个专门的函数来将makeSound
方法附加到每个动物对象上。这使我们无法编写和命名一次性使用的实用程序函数。
这些是我们可以有效地使用call()
方法使我们的代码干净、可重用和可维护的几种方法。
apply()
在功能方面几乎与call()
方法相同。唯一的区别是它接受一个类似数组的对象作为它的第二个参数。
/***After`this`contextargument*`call`acceptsalistofindividualarguments.*Therefore,if`args`isanarray,wecanusethe*`ES6`spreadoperatortopassindividualelements*asthelistofarguments*/func.call(context,...args);/***After`this`contextargument*`apply`acceptsasinglearray-likeobject*asitssecondargument.*/func.apply(context,args);
除了apply()
如何处理被调用方参数外,该功能与call()
方法相同。但是,由于这种差异,我们可以将其用于不同于call()
的用例。
Array.prototype.push
函数可用于将元素推送到数组的末尾。例如:
constnumbers=[1,2,3,4];numbers.push('a','b','c');//pushelementsonbyoneconsole.log(numbers);//[1,2,3,4,"a","b","c"]
如果你想将一个数组的所有元素推送到另一个数组,该怎么办?像下面这样:
constnumbers=[1,2,3,4];constletters=['a','b','c'];numbers.push(letters);console.log(numbers);//[1,2,3,4,["a","b","c"]]
这并不是我们想要的。它将整个字母数组作为单个元素附加到数字数组。我们本可以使用concat()
方法,但它将创建数组的副本并返回它。我们也不需要。我们还可以在字母数组上循环并单独推送每个元素。但还有一种更优雅的方式:
constnumbers=[1,2,3,4];constletters=['a','b','c'];numbers.push.apply(numbers,letters);console.log(numbers);//[1,2,3,4,"a","b","c"]
如果我们有特权使用ES6扩展运算符,我们可以通过这样做来实现这一点,
constnumbers=[1,2,3,4];constletters=['a','b','c'];numbers.push(...letters);console.log(numbers);//[1,2,3,4,"a","b","c"]2.apply()与接受参数列表的内置函数一起使用
对于任何接受参数列表的函数,例如Math.max
我们可以有效地使用 apply
。考虑以下。
如果你想找出一组数字的最小值和最大值,下面是老派的做法:
letmin=+Infinity;letmax=-Infinity;constnumbers=[4,5,1,2,8,3,4,6,3];for(leti=0;i<numbers.length;i++){if(numbers[i]>max){max=numbers[i];}if(numbers[i]<min){min=numbers[i];}}console.log(`Min:${min},Max:${max}`);//Min:1,Max:8
我们可以apply()
以更优雅的方式实现相同的效果,如下所示:
constnumbers=[4,5,1,2,8,3,4,6,3];min=Math.min.apply(null,numbers);max=Math.max.apply(null,numbers);console.log(`Min:${min},Max:${max}`);//Min:1,Max:8
与前一种情况相同,如果我们可以使用ES6扩展运算符,我们可以通过执行以下操作来实现相同的效果:
constnumbers=[4,5,1,2,8,3,4,6,3];min=Math.min(...numbers);max=Math.max(...numbers);console.log(`Min:${min},Max:${max}`);//Min:1,Max:8
看完了这篇文章,相信你对“Javascript如何使用.call()和.apply()编写更好的代码”有了一定的了解,如果想了解更多相关知识,欢迎关注亿速云行业资讯频道,感谢各位的阅读!
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。