日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

一起Polyfill系列:Function.prototype.bind的四个阶段

發布時間:2023/11/30 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一起Polyfill系列:Function.prototype.bind的四个阶段 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

昨天邊參考es5-shim邊自己實現Function.prototype.bind,發現有不少以前忽視了的地方,這里就作為一個小總結吧。

一、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.length=Math.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的方式動態定義函數來。

  • 被忽略的細節3:eval和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操作符外的構造函數的用法
  • JScript(IE6/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

    創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

    總結

    以上是生活随笔為你收集整理的一起Polyfill系列:Function.prototype.bind的四个阶段的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。