昨天边参考es5-shim边自己实现Function.prototype.bind,发现有不少以前忽视了的地方,这里就作为一个小总结吧。
其实它就是用来静态绑定函数执行上下文的this属性,并且不随函数的调用方式而变化。 示例:
1
2
3
4
5
6
7
8
9 |
test( ‘Function.prototype.bind‘ , function (){ function
orig(){ return
this .x; }; var
bound = orig.bind({x: ‘bind‘ }); equal(bound(), ‘bind‘ , ‘invoke directly‘ ); equal(bound.call({x: ‘call‘ }), ‘bind‘ , ‘invoke by call‘ ); equal(bound.apply({x: ‘apply‘ }), ‘bind‘ , ‘invoke by apply‘ ); }); |
Function.prototype.bind是ES5的API,所以坑爹的IE6/7/8均不支持,所以才有了自己实现的需求。
只要在百度搜Function.prototype.bind的实现,一般都能搜到这段代码。
1
2
3
4
5
6
7
8 |
Function.prototype.bind = Function.prototype.bind || function (){ var
fn = this , presetArgs = [].slice.call(arguments); var
context = presetArgs.shift(); return
function (){ return
fn.apply(context, presetArgs.concat([].slice.call(arguments))); }; }; |
1
2
3
4 |
test( ‘function.length is not writable‘ , function (){ function
doStuff(){} ok(!Object.getOwnPropertyDescriptor(doStuff, ‘length‘ ).writable, ‘function.length is not writable‘ ); }); |
1
2
3
4
5
6 |
var
x = ‘global‘ ; void function (){ var
x = ‘local‘ ; eval( ‘console.log(x);‘ ); // 输出local ( new
Function( ‘console.log(x);‘ ))(); // 输出global }(); |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 |
Function.prototype.bind = Function.prototype.bind || function (){ var
fn = this , presetArgs = [].slice.call(arguments); var
context = presetArgs.shift(); var
strOfThis = fn.toString(); // 函数反序列化,用于获取this的形参 var
fpsOfThis = /^ function [^()]*\((.*?)\)/i.exec(strOfThis)[1].trim().split( ‘,‘ ); // 获取this的形参 var
lengthOfBound = Math.max(fn.length - presetArgs.length, 0); var
boundArgs = lengthOfBound && fpsOfThis.slice(presetArgs.length) || []; // 生成bound的形参 eval( ‘function bound(‘ <ul> <li>boundArgs.join( ‘,‘ )</li> <li> ‘){‘ </li> <li> ‘return fn.apply(context, presetArgs.concat([].slice.call(arguments)));‘ </li> <li> ‘}‘ ); return
bound;<br> }; |
test(‘ctor produced by native Function.prototype.bind‘, function(){
?var
Ctor = function(x, y){
?? this.x = x;
?? this.y = y;
? };
?
var scope = {x: ‘scopeX‘, y: ‘scopeY‘};
? var Bound = Ctor.bind(scope);
?
var ins = new Bound(‘insX‘, ‘insY‘);
? ok(ins.x === ‘insX‘ && ins.y
=== ‘insY‘ && scope.x === ‘scopeX‘ && scope.y === ‘scopeY‘, ‘no
presetArgs‘);
? Bound = Ctor.bind(scope, ‘presetX‘);
? ins = new
Bound(‘insY‘, ‘insOther‘);
? ok(ins.x === ‘presetX‘ && ins.y ===
‘insY‘ && scope.x === ‘scopeX‘ && scope.y === ‘scopeY‘, ‘with
presetArgs‘);
});
行为如下:
- this属性不会被绑定
- 预设实参有效
下面是具体实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 |
Function.prototype.bind = Function.prototype.bind || function (){ var
fn = this , presetArgs = [].slice.call(arguments); var
context = presetArgs.shift(); var
strOfThis = fn.toString(); // 函数反序列化,用于获取this的形参 var
fpsOfThis = /^ function [^()]*\((.*?)\)/i.exec(strOfThis)[1].trim().split( ‘,‘ ); // 获取this的形参 var
lengthOfBound = Math.max(fn.length - presetArgs.length, 0); var
boundArgs = lengthOfBound && fpsOfThis.slice(presetArgs.length) || []; // 生成bound的形参 eval( ‘function bound(‘ + boundArgs.join( ‘,‘ ) + ‘){‘ + ‘if (this instanceof bound){‘ + ‘var self = new fn();‘ + ‘fn.apply(self, presetArgs.concat([].slice.call(arguments)));‘ + ‘return self;‘ <br> + ‘}‘ + ‘return fn.apply(context, presetArgs.concat([].slice.call(arguments)));‘ + ‘}‘ ); return
bound;<br> }; |
- var self = new fn(),如果fn函数体存在实参为空则抛异常呢?
- bound函数使用字符串拼接不利于修改和检查,既不优雅又容易长虫。
针对第三阶段的问题,最后得到下面的实现方式
if(!Function.prototype.bind){
?var _bound =
function(){
?? if (this instanceof bound){
???var ctor =
function(){};
???ctor.prototype = fn.prototype;
???var self = new
ctor();
???fn.apply(self, presetArgs.concat([].slice.call(arguments)));
???return self;
??}
??return fn.apply(context,
presetArgs.concat([].slice.call(arguments)));
?}
?, _boundStr =
_bound.toString();
?Function.prototype.bind = function(){
?? var fn =
this, presetArgs = [].slice.call(arguments);
?? var context =
presetArgs.shift();
?? var strOfThis = fn.toString(); //
函数反序列化,用于获取this的形参
?? var fpsOfThis =
/^function[^()]((.?))/i.exec(strOfThis)[1].trim().split(‘,‘);//
获取this的形参
?? var lengthOfBound = Math.max(fn.length - presetArgs.length,
0);
?? var boundArgs = lengthOfBound &&
fpsOfThis.slice(presetArgs.length) || [];// 生成bound的形参
??//
通过函数反序列和字符串替换动态定义函数
?? var bound = eval(‘(0,‘ +
_boundStr.replace(‘function()‘, ‘function(‘ + boundArgs.join(‘,‘) + ‘)‘) + ‘)‘);
?? return bound;
? };
// 分别用impl1,impl2,impl3,impl4代表上述四中实现方式
var start, end, orig =
function(){};
start = (new Date()).getTime();
Function.prototype.bind
= impl1;
for(var i = 0, len = 100000; i++ < len;){
?orig.bind({})();
}
end = (new Date()).getTime();
console.log((end-start)/1000); // 输出1.387秒
start = (new
Date()).getTime();
Function.prototype.bind = impl2;
for(var i = 0, len =
100000; i++ < len;){
? orig.bind({})();
}
end = (new
Date()).getTime();
console.log((end-start)/1000); // 输出4.013秒
start =
(new Date()).getTime();
Function.prototype.bind = impl3;
for(var i = 0,
len = 100000; i++ < len;){
? orig.bind({})();
}
end = (new
Date()).getTime();
console.log((end-start)/1000); // 输出4.661秒
start =
(new Date()).getTime();
Function.prototype.bind = impl4;
for(var i = 0,
len = 100000; i++ < len;){
? orig.bind({})();
}
end = (new
Date()).getTime();
console.log((end-start)/1000); // 输出4.485秒
由此得知运行效率最快是第一阶段的实现,而且证明通过eval动态定义函数确实耗费资源啊!!! 当然我们可以通过空间换时间的方式(Momoized技术)来缓存bind的返回值来提高性能,经测试当第四阶段的实现方式加入缓存后性能测试结果为1.456,性能与第一阶段的实现相当接近了。
在这之前从来没想过一个Function.prototype.bind的polyfill会涉及这么多知识点,感谢es5-shim给的启发。 我知道还会有更优雅的实现方式,欢迎大家分享出来!一起面对javascript的痛苦与快乐!
原创文章,转载请注明来自^_^肥仔John[http://fsjohnhuang.cnblogs.com]本文地址:http://www.cnblogs.com/fsjohnhuang/p/3712965.html (本篇完)
?如果您觉得本文的内容有趣就扫一下吧!捐赠互勉!
??
Polyfill Function.prototype.bind的四个阶段,布布扣,bubuko.com
Polyfill Function.prototype.bind的四个阶段
原文:http://www.cnblogs.com/fsjohnhuang/p/3712965.html