谈谈JavaScript中的几种借用方法
前言
通过call()、apply()和bind()方法,我们可轻易地借用其它对象的方法,而无须从这些对象中继承它。
在JavaScript中借用方法
在JavaScript中,有时可以重用其它对象的函数或方法,而不一定非得是对象本身或原型上定义的。通过call()、apply()和bind()方法,我们可轻易地借用其它对象的方法,而无须继承这些对象。这是专业JavaScript开发者常用的手段。
原型方法
在JavaScript中,除了不可更改的原始数据类型,如string、number和boolean,几乎所有的数据都是对象。Array是一种适用于遍历和转换有序数列的对象,其原型上有slice、join、push和pop等好用的方法。
一个常用的例子是,当对象和数组都是列表类型的数据结构时,对象可以从数组“借用”方法。最常借用的方法是Array.prototype.slice。
functionmyFunc(){ //error,argumentsisanarraylikeobject,notarealarray arguments.sort(); //"borrow"theArraymethodslicefromitsprototype,whichtakesanarraylikeobject(key:value) //andreturnsarealarray varargs=Array.prototype.slice.call(arguments); //argsisnowarealArray,socanusethesort()methodfromArray args.sort(); } myFunc('bananas','cherries','apples');
借用方法之所以可行,是因为call和apply方法允许在不同上下文中调用函数,这也是重用已有功能而不必继承其它对象的好方法。实际上,数组在原型中定义了很多常用方法,比如join和filter也是:
//takesastring"abc"andproduces"a|b|c Array.prototype.join.call('abc','|'); //takesastringandremovesallnonvowels Array.prototype.filter.call('abcdefghijk',function(val){ return['a','e','i','o','u'].indexOf(val)!==-1; }).join('');
可以看出,不仅对象可以借用数组的方法,字符串也可以。但是因为泛型方法是在原型上定义的,每次想要借用方法时都必须使用String.prototype或Array.prototype。这样写很啰嗦,很快就会令人生厌。更有效的方法是使用字面量来达到同样的目的。
使用字面量借用方法
字面量是一种遵循JavaScript规则的语法结构,MDN这样解释:
在JavaScript中,使用字面量可以代表值。它们是固定值,不是变量,就是在脚本中按字面给出的。
字面量可以简写原型方法:
[].slice.call(arguments); [].join.call('abc','|'); ''.toUpperCase.call(['lowercase','words','in','a','sentence']).split(',');
这样看上去没有那么冗长了,但是必须直接在[]和""上操作以借用方法,仍然有点丑。可以利用变量保存对字面量和方法的引用,这样写起来更简便些:
varslice=[].slice; slice.call(arguments); varjoin=[].join; join.call('abc','|'); vartoUpperCase=''.toUpperCase; toUpperCase.call(['lowercase','words','in','a','sentence']).split(',');
有了借用方法的引用,我们就可以轻松地使用call()调用它了,这样也可以重用代码。秉着减少冗余的原则,我们来看看可否借用方法却不用每次调用都要写call()或者apply():
varslice=Function.prototype.call.bind(Array.prototype.slice); slice(arguments); varjoin=Function.prototype.call.bind(Array.prototype.join); join('abc','|'); vartoUpperCase=Function.prototype.call.bind(String.prototype.toUpperCase); toUpperCase(['lowercase','words','in','a','sentence']).split(',');
如你所见,现在可以使用Function.prototype.call.bind来静态绑定从不同原型“借来的”方法了。但是varslice=Function.prototype.call.bind(Array.prototype.slice)这句话实际是如何起作用的呢?
理解Function.prototype.call.bind
Function.prototype.call.bind乍一看有些复杂,但是理解它是如何起作用的会非常有益。
Function.prototype.call是一种引用,可以“call”函数并将设置其“this”值以在函数中使用。
注意“bind”返回一个存有其“this”值的新函数。因此.bind(Array.prototype.slice)返回的新函数的“this”总是Array.prototype.slice函数。
综上所述,新函数会调用“call”函数,并且其“this”为“slice”函数。调用slice()就会指向之前限定的方法。
自定义对象的方法
继承很棒,但是开发者通常在想要重用一些对象或模块间的通用功能时才会使用。没必要仅为代码重用使用继承,因为在多数情况下简单的借用方法会很复杂。
之前我们只讨论了借用原生方法,但是借用任何方法都是可以的。比如下面的代码可以计算积分游戏的玩家分数:
varscoreCalculator={ getSum:function(results){ varscore=0; for(vari=0,len=results.length;i<len;i++){ score=score+results[i]; } returnscore; }, getScore:function(){ returnscoreCalculator.getSum(this.results)/this.handicap; } }; varplayer1={ results:[69,50,76], handicap:8 }; varplayer2={ results:[23,4,58], handicap:5 }; varscore=Function.prototype.call.bind(scoreCalculator.getScore); //Score:24.375 console.log('Score:'+score(player1)); //Score:17 console.log('Score:'+score(player2));
虽然上面的例子很生硬,但是可以看出,就像原生方法一样,用户定义的方法也可以轻松借用。
总结
Call、bind和apply可以改变函数的调用方式,并且经常在借用函数时使用。多数开发者熟悉借用原生方法,但是较少借用自定义的方法。
近几年JavaScript的函数式编程发展不错,怎样使用Function.prototype.call.bind借用方法才更加简便?估计这样的话题会越来越常见。
以上就是JavaScript中的借用方法总结的全部内容,希望对大家了解到JavaScript中的借用方法有所帮助吧。