谈谈 Function.prototype.bind() 的 polyfill
为啥写这篇博客
以前一直是忽略了 bind
函数是怎么实现的,以致于看见下面这段代码时不知所措。
function largest(arr) {
return arr.map(Function.apply.bind(Math.max, null))
}
largest([[1,34],[456,2,3,44]) // [34, 456]
于是我便开始看 bind
的实现,其中也涉及了很多概念如下,是非常值得重温和学习的
-
作用域链
-
闭包
-
柯里化
-
new
操作做了什么 -
...
基础
从一段代码开始
var obj = {
a: 1,
print(x, y) {
console.log(this.a, x + y)
}
}
var test = {
a: 2
}
obj.print(1, 2) // 1 3
obj.print.apply(test, [1, 2]) // 2 3
obj.print.call(test, 1, 2) // 2 3
obj.print.bind(test)(1, 2) // 2 3
obj.print.bind(test, 1)(2) // 2 3
obj.print.bind(test, 1, 2)() // 2 3
目标
- 返回一个由指定的
this
值(第一个参数)和初始化参数的新函数(是原函数的拷贝) - 返回的新函数可以继续指定参数
bind
函数不可通过new
调用- 新的绑定函数也能使用
new
操作符创建对象,这样做时指定的this
无效
从零开始实现
下面代码实现了最简单的 bind
函数,返回新函数绑定了传入的 this 上下文以及给新函数传入了默认参数 arg
Function.prototype.bind = Function.prototype.bind || function(context) {
var target = this
var args = Array.prototype.slice.call(arguments, 1)
return () => target.apply(context, args)
}
然后我们要求新函数也可以传入参数,这时候就不能用箭头函数了
Function.prototype.bind = Function.prototype.bind || function(context) {
var target = this
var args = Array.prototype.slice.call(arguments, 1)
return function() {
var newArgs = Array.prototype.slice.call(arguments)
target.apply(context, newArgs.concat(args))
}
}
new fn.bind()
是非法的,非法调用的话要抛出异常
Function.prototype.bind = Function.prototype.bind || function(context) {
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable')
}
var target = this
var args = Array.prototype.slice.call(arguments, 1)
return function() {
var newArgs = Array.prototype.slice.call(arguments)
target.apply(context, newArgs.concat(args))
}
}
再来实现返回函数支持 new
操作。当代码 new Foo(...)
执行时:
- 一个新对象被创建。它继承自
Foo.prototype
。 - 调用构造函数
Foo
,并将this
绑定到新创建的对象。
Function.prototype.bind = Function.prototype.bind || function(context) {
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable')
}
var target = this
var args = Array.prototype.slice.call(arguments, 1)
var fn = function() {}
fn.prototype = target.prototype
fnBound.prototype = new fn()
// or use `Object.create()`
// fnBound.prototype = Object.create(target.prototype)
return function fnBound() {
var newArgs = Array.prototype.slice.call(arguments)
var finalArgs = newArgs.concat(args)
if (this instanceof fn) {
target.apply(this, finalArgs)
} else {
target.apply(context, finalArgs)
}
}
}
到了这里,代码其实和 MDN 里的 polyfill 差不多了,代码见链接
已经完美?
当我们认为这份代码已经完美时,这时应该看看 es5-shim 对 bind 的实现。搭配这篇博客《从一道面试题的进阶,到"我可能看了假源码"(2)》一起看效果更佳哦~