昨天边参考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