我是FE,也是Fe

          前端來源于不斷的點滴積累。我一直在努力。

          統計

          留言簿(15)

          閱讀排行榜

          評論排行榜

          underscore中的function類函數解析

          underscore是一個非常不錯的基礎javascript庫,他提供了很多實用的方法,彌補了javascript原生 API調用的一些不足,讀他的文檔的時候,讀到array,object,uitlity的時候已經非常興奮了。覺得足夠用了。但是我看到function這部分的時候,發現這些函數真的非常的有意義,結合源代碼來看看這部分function相關的功能。

          _.bind方法

          最常見的方法。作用是改變默認的function中的this指向。需要說明的是在ECMA 5這個版本中function已經自帶了一個bind方法,參見這里(該文章具體介紹了bind的集中使用場景)。bind的使用方法是:
          _.bind(function, object, [*arguments]) 

           下面是一個使用demo:

          var func = function(greeting){ 
              
          //this指向的是bind的第二個參數
               //  greeting 是bind的第三個參數
              return greeting + ': ' + this.name 
          };
          // bind返回的是一個新的function對象
          var newfunc = _.bind(func, {name : 'moe'}, 'hi');

          func();


          原本以為這個bind的源碼會很簡單,無非就是用apply返回新的function,但是看源碼發現挺講究:

           

          _.bind = function bind(func, context) {
              
          var bound, args;
              
          //如果function存在原生的bind方法使用原生的bind
              if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
              
          //不是function,拋異常
              if (!_.isFunction(func)) throw new TypeError;
              
          //將后面的參數轉化成數組
              args = slice.call(arguments, 2);
              
          return bound = function() {
                
          //如果當前的this已經指向的一個function的實例,就不需要再改變this的指向,因為此時的function已經作為一個構造函數在使用
                if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
                
          //否則function視為構造函數使用,要保證this為構造函數的實例
                ctor.prototype = func.prototype;
                
          var self = new ctor;
                
          //將function強制轉換成一個類的構造函數
                var result = func.apply(self, args.concat(slice.call(arguments)));
                
          //Object(result) === result 只有當result是Object時才會成立,基本的數據類型如number,string則不成立
                if (Object(result) === result) return result;
                
          return self;
              };
            };
          代碼實現還是考慮了普通函數調用,構造函數調用,通過成員函數調用的情況,邏輯實現的很全面。

          _.bindAll

          bindAll方法可以將一個對象中所有的成員函數的this都指向這個對象,什么情況下對象的成員函數的this不指向對象呢?比如:

          var buttonView = {
            label   : 'underscore',
            onClick : 
          function(){ alert('clicked: ' + this.label); },
            onHover : 
          function(){ console.log('hovering: ' + this.label); }
          };
          _.bindAll(buttonView);
          //當成員函數作為事件監聽的時候,因為默認的事件監聽,this都會指向當前事件源
          //
          bindAll之后可以保證onClick中的this仍指向buttonView
          jQuery('#underscore_button').bind('click', buttonView.onClick);

          _.memoize(function, [hashFunction])

          該方法可以緩存函數返回結果,如果一個函數計算需要很長的時間,多次反復計算可以只計算一次緩存結果,默認的緩存key是函數調用時的第一個參數,也可以自己定義function(第二個參數)來計算key

          _.memoize = function(func, hasher) {
              
          var memo = {};//緩存存放位置
               //_.indentity默認取數組第一個元素
              hasher || (hasher = _.identity);
              
          return function() {
                
          var key = hasher.apply(this, arguments);
                
          return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
              };
            };

          delay : _.delay(function, wait, [*arguments])

          delay方法在指定的wait后面執行函數與setTimeout功能一致

          defer: _.defer(function, [*arguments])

          defer也是延遲執行方法,不同的是他能保證在當前堆棧中的所有的代碼跑完之后再執行function。其實就是setTimeout(fn,1);

          throttle:_.throttle(function, wait)

          throttle這個單詞的意思是使減速,用于控制頻繁觸發的 function的的頻率,比如,拖動頁面滾動條時scroll方法會以很高的頻率觸發,如果在scroll的處理事件中做了很費時的操作,會導致瀏覽器假死,如果使用了throttle后,function被觸發的頻率可以降低。

          document.body.onscroll = _.throttle(function(){
                  console.log(
          "scrolling:"+(document.body.scrollTop|| document.body.scrollTop);
              },
          100); 

          scroll事件默認50ms觸發一次,但是使用throttle之后事件觸發頻率為100ms一次

          debounce: _.debounce(function, wait, [immediate])

          debounce 本意是“使反跳”,這個翻譯是在讓人看不明白。同樣用于處理頻繁觸發的事件,處理方法時,對于頻繁處理的時間,只在第一次觸發(是否觸發取決于immdiate 參數),和事件頻繁觸發最后一次觸發(有最多wait的延時)。拿滾動事件為例,滾動事件50ms觸發一次,如果設置wait為100ms。則在最后一次觸發scroll事件時,也就是停止滾動時,在100ms后觸發function。如果immediate參數為true,開始滾動時也會觸發function

          document.body.onscroll = _.debounce(function(){
                  
          //一次滾動過程觸發兩次該函數
                  console.log("scrolling:"+(document.body.scrollTop|| document.body.scrollTop);
              },
          100,true);

          在整個滾動過程中觸發function,對于只關注整個滾動前后變化的處理非常有用。

          下面是_.debounce和_.throttle的源碼:

          _.debounce = function(func, wait, immediate) {
              
          var timeout, result;
              
          return function() {
                    
          var context = this, args = arguments;
                    
          var later = function() {
                      timeout 
          = null;//最后一次調用時清除延時
                      if (!immediate) result = func.apply(context, args);
                    };
                    
          var callNow = immediate && !timeout;
                    
          //每次func被調用,都是先清除延時再重新設置延時,這樣只有最后一次觸發func再經過wait延時后才會調用func
                    clearTimeout(timeout);//
                    timeout = setTimeout(later, wait);
                    
          //如果第一次func被調用 && immediate ->立即執行func
                    if (callNow) result = func.apply(context, args);
                    
          return result;
              };
          };

          _.throttle 
          = function(func, wait) {
              
          var context, args, timeout, throttling, more, result;

              
          //延時wait后將more  throttling 設置為false
              var whenDone = _.debounce(function(){ 
                  more 
          = throttling = false
              }, wait);
              
          return function() {
                  context 
          = this; args = arguments;
                  
          var later = function() {
                      timeout 
          = null;
                      
          if (more) { //more:最后一次func調用時,確保還能再調用一次
                            result = func.apply(context, args);
                      }
                      whenDone();
                  };
                  
          if (!timeout) timeout = setTimeout(later, wait);
                  
          if (throttling) {
                      more 
          = true;
                  } 
          else {
                      
          //每次觸發func 有會保證throttling 設置為true
                      throttling = true;
                      result 
          = func.apply(context, args);
                  }
                  
          //每次觸發func 在 wait延時后將 more  throttling 設置為false
                  whenDone();
                  
          return result;
              };
          };

          once: _.once(function)

          once能確保func只調用一次,如果用func返回一個什么對象,這個對象成了單例。源碼也比較簡單,無非就是用一個標志位來標示是否運行過,緩存返回值

          _.once = function(func) {
              
          var ran = false, memo;
              
          return function() {
                
          if (ran) return memo;
                ran 
          = true;
                memo 
          = func.apply(this, arguments);
                func 
          = null;
                
          return memo;
              };
            };

          wrap: _.wrap(function, wrapper)

          wrap可以將函數再包裹一層,返回一個新的函數,新的函數里面可以調用原來的函數,可以將原函數的處理結果再處理一次返回。類似與AOP切面。在函數處理前/后動態的添加一些額外的處理,下面是一個使用demo

          var hello = function(name) { return "hello: " + name; };
          //wrap返回一個新的函數
          hello = _.wrap(hello, function(func) {
            
          // 在新函數內部可以繼續調用原函數
            return "before, " + func("moe"+ ", after";
          });
          hello();

          wrap的源碼:

          _.wrap = function(func, wrapper) {
              
          return function() {
               
          //將原函數當新函數的一個參數傳入
                var args = [func];
                push.apply(args, arguments);
                
          return wrapper.apply(this, args);
              };
            };

          compose: _.compose(*functions)

          將多個函數處理過程合并,每個函數可以調用前面函數的運行結果,_.compose(func1,func2);相當于func1(func2())。看看他的源碼:

          _.compose = function() {
              
          var funcs = arguments;
              
          return function() {
                
          var args = arguments;
                
          //循環調用參數中的function
                for (var i = funcs.length - 1; i >= 0; i--) {
                  args 
          = [funcs[i].apply(this, args)];
                }
                
          return args[0];//先調用的函數結果最為下一個函數的參數
              };
            };

          after:_.after(count, function)

          創建一個新的函數,當func反復調用時,count次才調用一次,比如:

          function a(){
              alert(
          "a");
          }

          var afterA = _.after(3,a);
          afterA();
          //調用
          afterA();//不alert
          afterA();//不alert
          afterA();//調用


          源碼:

          _.after = function(times, func) {
              
          if (times <= 0return func();
              
          return function() {
                
          if (--times < 1) {
                  
          return func.apply(this, arguments);
                }
              };
            };


          總結:
          上面這些函數在開發中經常能用到,能解決很多特定的問題。undercore的源碼也看得出非常老道,可以非常好的學習資料。

          參考資料:
          ES5中的bind介紹
          從underscore.js的源碼學習javascript
          帶注釋的underscore源碼



          posted on 2012-11-08 13:14 衡鋒 閱讀(2882) 評論(2)  編輯  收藏 所屬分類: javascriptWeb開發

          評論

          # re: underscore中的function類函數解析 2013-08-09 14:24 digno

          function a(som){
          alert(som);
          }
          afterA = _.after(3,a);
          afterA("a"); // no
          afterA("b"); // no
          afterA("c"); // yes
          afterA("d"); // yes
          afterA("e"); // yes
          afterA("f"); // yes   回復  更多評論   

          # re: underscore中的function類函數解析[未登錄] 2013-10-21 16:39 wj

          @digno

          很對  回復  更多評論   

          主站蜘蛛池模板: 达尔| 鄯善县| 湘潭市| 潜江市| 武隆县| 堆龙德庆县| 新营市| 秦安县| 佳木斯市| 通榆县| 塔河县| 丰宁| 综艺| 农安县| 广元市| 芮城县| 沙洋县| 华蓥市| 工布江达县| 阳山县| 即墨市| 灵丘县| 承德县| 油尖旺区| 读书| 青河县| 南漳县| 民县| 彰化市| 永善县| 凤山县| 洪洞县| 谷城县| 景谷| 青州市| 五原县| 古丈县| 宁明县| 祥云县| 巴林左旗| 柳林县|