网站开发学哪种语言,兰州北山生态建设局网站,国内做网站网站代理怎么样,优秀网站参考昨天边参考es5-shim边自己实现Function.prototype.bind#xff0c;发现有不少以前忽视了的地方#xff0c;这里就作为一个小总结吧。 一、Function.prototype.bind的作用 其实它就是用来静态绑定函数执行上下文的this属性#xff0c;并且不随函数的调用方式而变化。 示例发现有不少以前忽视了的地方这里就作为一个小总结吧。 一、Function.prototype.bind的作用 其实它就是用来静态绑定函数执行上下文的this属性并且不随函数的调用方式而变化。 示例 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的实现一般都能搜到这段代码。 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)));};}; 它能恰好的实现Function.prototype.bind的功能定义但通过看es5-shim源码就会发现这种方式忽略了一些细节。 第二阶段 被忽略的细节1函数的length属性用于表示函数的形参。 而第一阶段的实现方式调用bind所返回的函数的length属性只能为0而实际上应该为fn.length-presetArgs.length才对啊。所以es5-shim里面就通过bound.lengthMath.max(fn.length-presetArgs.length, 0)的方式重设length属性。被忽略的细节2函数的length属性值是不可重写的使用现代浏览器执行下面的代码验证吧 test(function.length is not writable, function(){function doStuff(){}ok(!Object.getOwnPropertyDescriptor(doStuff, length).writable, function.length is not writable);}); 因此es5-shim中的实现方式是无效的。既然不能修改length的属性值那么在初始化时赋值总可以吧也就是定义函数的形参个数于是我们可通过eval和new Function的方式动态定义函数来。 被忽略的细节3eval和new Function中代码的执行上下文的区别。 简单来说在函数体中调用eval其代码的执行上下文会指向当前函数的执行上下文而new Function或Function中代码的执行上下文将一直指向全局的执行上下文。 举个栗子 var x global;void function(){var x local;eval(console.log(x);); // 输出local(new Function(console.log(x);))(); // 输出global}(); 因此这里我们要是用eval来动态定义函数了。 具体实现 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(,) ){ return fn.apply(context, presetArgs.concat([].slice.call(arguments))); });return bound; }; 现在成功设置了函数的length属性了。不过还有些遗漏。 第三阶段 被忽视的细节4通过Function.prototype.bind生成的构造函数。我在日常工作中没这样用过不过这种情况确实需要考虑下面我们先了解原生的Function.prototype.bind生成的构造函数的行为吧请用现代化浏览器执行下面的代码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属性不会被绑定预设实参有效 下面是具体实现 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; } return fn.apply(context, presetArgs.concat([].slice.call(arguments))); });return bound; }; 现在连构造函数作为使用方式都考虑到了应该算是功德圆满了吧NO上面的实现只是基础的实现而已并且隐藏一些bugs 潜伏的bugs列表 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性能与第一阶段的实现相当接近了。 五、本文涉及的知识点 eval的用法new Function的用法除new操作符外的构造函数的用法JScriptIE6/7/8下诡异的命名函数表达式Momoized技术六、总结 在这之前从来没想过一个Function.prototype.bind的polyfill会涉及这么多知识点感谢es5-shim给的启发。 我知道还会有更优雅的实现方式欢迎大家分享出来一起面对javascript的痛苦与快乐 原创文章转载请注明来自^_^肥仔John[http://fsjohnhuang.cnblogs.com] 本文地址http://www.cnblogs.com/fsjohnhuang/p/3712965.html 本篇完 如果您觉得本文的内容有趣就扫一下吧捐赠互勉 转载于:https://www.cnblogs.com/fsjohnhuang/p/3712965.html