关于JS函数的bind方法
昨天被人问到js的bind的作用是什么?
这个倒还能回答出来,bind 之后返回一个新的函数,这个函数可以保持传递的this上下文。
接着又问了,那么bind两次不同的上下文会怎样?
这个一下子就蒙了,因为平时也没这么用过,于是开始查一下资料。
首先在浏览器中测试一下。1
2
3
4
5
6
7function test(){
console.log(this.a)
}
var bind1 = test.bind({a:1}) //第一次 bind
var bind2 = bind1.bind({a:2}) // 第二次 bind
bind1()
bind2()
结果如下1
21
1
可以看到第二次bind并没有能再改变this的值。
查一下MDN,Function.prototype.bind() , 并没有解释bind两次会怎样。 但是他提供了一个Polyfill,可以了解下bind的实现。
1 | if (!Function.prototype.bind) { |
可以看到MDN提供的polyfill的实现是oThis
做为参数传进来,返回一个新的函数,这个时候函数是个闭包,仍然可以访问oThis
变量,然后调用call/apply来实现指定的上下文。 这种情况下,如果bind两次,相当于闭包套闭包,不管套几层,值都是第一次保存的this
值。 即上面polyfill的 oThis
变量。
光看polyfill是不够了,因为并不知道polyfill实现是不是标准。所以还是要看下规范。这里我们参考下 ES2015文档
可以直接看到 19.2.3.2 节 NOTE 2,If Target is an arrow function or a bound function then the thisArg passed to this method will not be used by subsequent calls to F.
如果调用bind的是一个箭头函数或者是已经bind过的函数(bound function),那么再次bind是不会起作用的。 可以看到规范已经定义了这样的行为产生的结果,我们可以直接记住这个结论。
但是这里值得注意的是,我们看到规范定义的bind操作 和 MDN 上提供的polyfill并不一致。polyfill没有完全实现ES2015规定的bind。
比如ES2015 规定了 bound function 的length 和 name 行为。
1 | Let targetHasLength be HasOwnProperty(Target, "length"). |
这里会规定bound function 的 length 属性,应该和bind之前的length一致。
再看name 的行为
1 | Let targetName be Get(Target, "name"). |
这里规定bound function 的name 应该走 SetFunctionName 方法,而这里SetFunctionName之后的返回值应该是 bound字符串 + 空格 + 原先函数的name
1 | ......忽略了一些描述 |
即1
2
3function a(){}
var b = a.bind()
console.log(b.name)
结果应该是1
bound a
而 MDN 的 polyfill
是没有实现这些细节的,所以用的时候如果依赖于这些,是要注意的。