初涉JavaScript模式 (10) : 函數 【進階用法】

寫在前面

不知不覺寫到第10篇了。這篇寫起來很忐忑,終於和高級搭上邊了(呵呵),這篇咱們 主要 說一下 JS 方法的部分高級用法(我知道的),筆者水平有限,不免有錯。廢話很少少,進入正文。javascript

初始化

咱們在看一些別人寫的優秀的代碼,特別是組件時,咱們常常能發現有init或initializa這樣的方法,它通常執行的都是初始化。那初始化通常都有幾種呢,咱們來一一介紹:java

初始化對象

初始化對象,顧名思義,就是對帶着一堆具體邏輯的對象進行初始化。直接上代碼:編程

```javascript           
           var Wr = {
           	name: "WeiRan",
           	age: 21,
           	init: function() {
           		// body ...                  
           		console.log("初始化完成!");
           	}
           }
           Wr.init(); //初始化完成!
```

這種方法,有個弊端就是可能會污染全局做用域。這樣有時候就能夠採用另外一種方法來初始化:數組

```javascript
          ({
           	name: "WeiRan",
           	age: 21,
           	init: function() {
           		// body ...               
           		console.log("初始化完成!");
           	}
           }).init(); //初始化完成!
```

這種方法的有點和上篇所說的即時函數的有點是相同的:能夠在執行一次性的初始化任務時保護全局命名空間,若是初始化任務更加複雜,他會使整個初始化過程顯得更有結構化。瀏覽器

值得注意的是,這種模式主要適用於一次性的任務,並且在init完畢之後也沒有該對象的訪問(若是想有,也能夠有,在init方法底部加個return this便可)緩存

初始化分支

初始化分支也叫作加載時分支[load-time branching] ,是一種優化模式。當知道某些屬性在整個程序生命週期中都不會發生變化時,該模式就顯得很重要了。瀏覽器特性嗅探就是一個典型的例子,一般狀況下在site上寫JavaScript要考慮到兼容性,例如addEventListener在早期的IE上就是不支持的,在IE上支持的是attachEvent(更早的甚至是on+type),而若是咱們每次綁定事件的時候都要作個判斷,這樣會產生大量的冗[rǒng]餘代碼,可是初始化分支能夠很好的解決這個問題。代碼以下:markdown

```javascript
           //初始化分支
           if(typeof window.addEventListener === "function"){
               utils.addListener = function(el, type, fn){
                   el.addEventListener(type, fn, false);
               }
               utils.removeListener = function(el, type, fn){
                   el.removeEventListener(type, fn, false);
               }
           }
          // if IE
          else if(typeof document.attachEvent === "function"){
              utils.addListener = function(el, type, fn){
                  el.attachEvent('on' + type, fn);
              }
              utils.removeListener = function(el, type, fn){
                  el.detachEvent('on' + type, fn);
              }
          }
          //long long ago Browser
          else{
              utils.addListener = function(el, type, fn){
                  el['on' + type] = fn;
              }
              utils.removeListener = function(el, type, fn){
                  el['on' + type] = null;
              }
          }
```

初始化模塊

在上一篇中,我寫了一個叫作暴露接口的用法,在 @北川 的糾正下,我發現我寫的不是很嚴謹,對於用法也寫的太含糊,特地去詳細瞭解了下 Module模式。在這裏謝謝北川的指正。關於詳細的Module模式,我會在後面專門寫一篇。在回到咱們的正文,在模塊化編程中(利用r.js 或者 seaJS),咱們常常要對某個模塊進行初始化,而這時,這種模式就顯得更有用了。代碼以下:閉包

```javascript
           function Wr(n){
               var name;
               return {
                   init : function(n){
                       name = n;
                   },
                   getName : function(){
                       console.log(name);
                   }
              }
          }
          var wr1 = new  Wr();
          wr1.init("Weiran");
          wr1.getName(); //WeiRan
```

上面init很簡單,可是具體到工做中,可能初始化作的就不會這麼簡單,總之它很好的提升了模塊的可擴展性。讓咱們的代碼結構更加清晰。app

緩存函數結果

對於一些操做很是很是複雜的函數,咱們不必每次都去真的執行一遍。舉個例子,咱們在百度搜索一個關鍵詞,百度並非真的去搜一遍,而是去看之前有木有查過,而後才返回結果(這就是有時候,百度的結果有點舊了)。在函數中也是同樣,對於邏輯極度複雜,可是參數變化不大的函數(結果也不常常變化),咱們能夠緩存計算結果。代碼以下:模塊化

```javascript
           var fun1 = function(param){
               if(!fun1.cache[param]){
                   var result = {};
                   // 複雜的邏輯 ...
                   fun1.cache[param] = result;  //把計算結果緩存
               }
               return fun1.cache[param];
           }
```

上面的代碼假定該函數只須要一個參數,若是有更多及更復雜的參數,一般的作法是將參數序列化。代碼以下:

```javascript
           var func2 = function(){
               //JSON 序列化
               var cacheKey = JSON.stringify(Array.prototype.slice.call(arguments)),
                   result;
               if(!func2.cache[cacheKey]){
                   result = {};
                   // 複雜的邏輯
                   func2.cache[cacheKey] = result;
               }
              return func2.cache[cacheKey];
          };
          func2.cache = {};
```

值得注意的是,在JSON序列化的過程當中,由於本質上都是被序列化成了字符串,全部對象也被序列化成了字符串,對於兩個不一樣對象但具備相同的屬性,這兩個對象就會被當成同一個,共享一條緩存條目。

柯里化

柯里化(curry),貌似很高端的樣子,那他是什麼呢,咱們先假設有一個需求(這裏我借用騰訊的案例。):

假設在實現一個計算每個月花費的函數, 天天結束前咱們都要記錄今天花了多少錢, 但咱們只關心月底的花費總值, 無需天天都計算一次.

若是使用curry怎麼實現,廢話很少說,直接上代碼:

```javascript
           var curry = function(fn){
               var args = [];
               return function(){
                   if(arguments.length === 0){
                       return fn.apply(this,args);
                   }
                   args.push.apply(args, arguments);
                   return fn;
               }
          }
          var exc = curry(function(){
              var nums = [].slice.call(arguments),
                  result = 0;
              nums.forEach(function(val){
                  result += val;
              });
              return result;
          });
          exc(500);
          exc(1000);
          console.log(exc()); //1500
```

初看上去仍是有點繞的,咱們切開來看,咱們傳給curry一個匿名函數,curry返回一個通過處理的函數。而重點來了,咱們來看curry是怎麼處理的,首先是個判斷,判斷這個函數接受的參數是不是空[arguments.length === 0] 若是不爲空,把當前的參數push到args裏面去,這裏要注意的是curry 返回的是一個方法,因此就造成了一個閉包,而閉包的一個特性就是能夠訪問返回他的函數的變量和方法,故這個方法能夠改變args的值(把當前參數push到返回他的函數的args裏去),若是爲空的話,他纔會執行fn並把參數數組經過apply的形式傳給fn。

這種方式的最大好處就是能夠延遲到最後一刻才一塊兒計算, 在不少場合能夠避免無謂的計算, 節省性能, 也是實現惰性求值的一種方案.

柯里化就是用閉包原理實現函數參數的合併,而後再運行函數。

若是以上還不夠明確,咱們在來看一個例子:

```javascript
           //Curry 
           var curry = function(fn){
               var slice = Array.prototype.slice,
                   storedArgs = slice.call(arguments, 1);
               return function(){
                   var newArgs = slice.call(arguments);
                   var args = storedArgs.concat(newArgs);
                   return fn.apply(null, args);
               }; 
          }
          var result = curry(function(x, y){
              return x + y;
          }, 5)(10);
          console.log(result); //15
```

上面的代碼,須要注意的是 storedArgs = slice.call(arguments, 1) 這段代碼的意思是把fn以外的參數存儲起來(咱們第一次傳個curry的那個5),而後咱們調用這個通過curry處理事後的函數時,他會把咱們新傳的參數和原來咱們傳的那個5結合組成新的參數數組進行處理[var args = storedArgs.concat(newArgs)],具體處理的代碼是fn.apply(null, args)。

當咱們發現正在調用一個函數,而且傳遞的大部分參數都是相同的,那麼該函數使用curry化是一個很好的方案。能夠經過將一個curry化,從而動態產生一個新的函數,這個新的函數會保存重複的參數(所以沒必要每次都傳遞這些參數),而且還會使用預填充原始函數所指望的完整參數列表。

結語

原本準備也把反柯里化寫了,奈何最後發現,對於這東西,本身還不知道怎麼把它完整的表訴清楚,可能仍是本身沒徹底吃透吧,因此也暫時不寫了,JS的路還很長啊。

若是你在文中發現錯誤,歡迎指正。

相關文章
相關標籤/搜索