Function direct call vs func.call() vs func.bind() in JavaScript
JavaScript 有三种函数调用方式 (其他奇葩的不讨论) :
func()
func.call()
和func.apply()
var binded = func.bind()
第一种很好理解,就是直接调用。
第二种很常用,经常用于调整函数的 this
指向的对象。如:
function say_hello () {
return 'Hello, ' + this.name
}
say_hello.call({ name: 'John' }) // 'Hello, John'
第三种是 [EcmaScript 5] 新加入的 [Function.prototype.bind()]。它和 call()
类似,但它不是直接调用,而是返回一个新的函数:
function withdrawCash (amount) {
if (this.bank.cash < amount) {
throw new Error('You do not have enough money')
}
this.bank.cash = this.bank.cash - amount
console.log('Cash ' + amount + ' out')
}
var John = withdrawCash.bind({
bank: {
cash: 1000
}
})
var Marry = withdrawCash.bind({
bank: {
cash: 10000
}
})
John(1000) // 'Cash 1000 out'
Marry(10000) // 'Cash 10000 out'
Marry(1) // throw 'You do not have enough money'
withdrawCash(100) // throw 'TypeError: Cannot read property 'cash' of undefined'
从例子可以看到,John
和 Marry
的数据是分开,相当于为他们单独定义了 withdrawCash()
。如果用 call()
实现的话就是这样:
var John = {
bank: {
cash: 1000
}
}
var Marry = {
bank: {
cash: 10000
}
}
withdrawCash.call(John, 1000)
withdrawCash.call(Marry, 10000)
withdrawCash.call(Marry, 1)
虽然可以达到一样的效果,但是 withdrawCash.call()
明显不如 var func_name = withdrawCash.bind(BankInfo)
易读和易维护,而且后续调用时更简单,不需要再次指定 call()
的第一个参数,减少 typo 造成的隐患。
看到这里,有人会想:「既然 bind()
比 call()
好,那以后就用它好了」。
虽然不少浏览器已经支持,也有 shim 可用,而且至少 Node 环境是可以无障碍地使用,但我还是要说,慎用 bind()
,因为它有性能问题。
这是在 jspert.com 上的测试结果:
这是在 Node 上的测试结果:
{ http_parser: '2.3',
node: '0.11.14',
v8: '3.26.33',
uv: '1.0.0',
zlib: '1.2.3',
modules: '14',
openssl: '1.0.1i' }
✓ direct call x 68,038,524 ops/sec ±0.88% (94 runs sampled)
✓ call() x 17,655,227 ops/sec ±0.87% (95 runs sampled)
✓ bind() x 4,607,937 ops/sec ±0.91% (93 runs sampled)
Fastest is direct call
虽然 bind()
的性能是超过了每秒百万次,但如果一处代码多次使用,性能就会急剧下降:
func.call(John)
func.call(Marry)
func.bind(John)();
func.bind(Marry)();
✓ call() x 9,328,537 ops/sec ±0.88% (93 runs sampled)
✓ bind() x 221,863 ops/sec ±1.92% (85 runs sampled)
因此建议:
- 尽量不要依赖函数内的
this
,尽量采用直接传入参数来传递对象的方式 - 在
this
对象固定,且会在多处使用的时候,优先考虑用类的形式来实现 - 多个函数的
this
不固定的场合,如 [Koa Middleware],通过call()
来传递this
对象
[EcmaScript 5]: http://www.ecma-international.org/ecma-262/5.1
[Function.prototype.bind()]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
[Koa Middleware]: http://koajs.com/#application