官术网_书友最值得收藏!

3.2 高階函數(shù)

高階函數(shù)是指至少滿足下列條件之一的函數(shù)。

? 函數(shù)可以作為參數(shù)被傳遞;

? 函數(shù)可以作為返回值輸出。

JavaScript語言中的函數(shù)顯然滿足高階函數(shù)的條件,在實際開發(fā)中,無論是將函數(shù)當(dāng)作參數(shù)傳遞,還是讓函數(shù)的執(zhí)行結(jié)果返回另外一個函數(shù),這兩種情形都有很多應(yīng)用場景,下面就列舉一些高階函數(shù)的應(yīng)用場景。

3.2.1 函數(shù)作為參數(shù)傳遞

把函數(shù)當(dāng)作參數(shù)傳遞,這代表我們可以抽離出一部分容易變化的業(yè)務(wù)邏輯,把這部分業(yè)務(wù)邏輯放在函數(shù)參數(shù)中,這樣一來可以分離業(yè)務(wù)代碼中變化與不變的部分。其中一個重要應(yīng)用場景就是常見的回調(diào)函數(shù)。

1.回調(diào)函數(shù)

在ajax異步請求的應(yīng)用中,回調(diào)函數(shù)的使用非常頻繁。當(dāng)我們想在ajax請求返回之后做一些事情,但又并不知道請求返回的確切時間時,最常見的方案就是把callback函數(shù)當(dāng)作參數(shù)傳入發(fā)起ajax請求的方法中,待請求完成之后執(zhí)行callback函數(shù):

        var getUserInfo = function( userId, callback ){
            $.ajax( 'http://xxx.com/getUserInfo? ' + userId, function( data ){
              if ( typeof callback === 'function' ){
                  callback( data );
              }
            });
        }

        getUserInfo( 13157, function( data ){
            alert ( data.userName );
        });

回調(diào)函數(shù)的應(yīng)用不僅只在異步請求中,當(dāng)一個函數(shù)不適合執(zhí)行一些請求時,我們也可以把這些請求封裝成一個函數(shù),并把它作為參數(shù)傳遞給另外一個函數(shù),“委托”給另外一個函數(shù)來執(zhí)行。

比如,我們想在頁面中創(chuàng)建100個div節(jié)點,然后把這些div節(jié)點都設(shè)置為隱藏。下面是一種編寫代碼的方式:

        var appendDiv = function(){
            for ( var i = 0; i < 100; i++ ){
              var div = document.createElement( 'div' );
              div.innerHTML = i;
              document.body.appendChild( div );
              div.style.display = 'none';
            }
        };

        appendDiv();

把div.style.display = 'none’的邏輯硬編碼在appendDiv里顯然是不合理的,appendDiv未免有點個性化,成為了一個難以復(fù)用的函數(shù),并不是每個人創(chuàng)建了節(jié)點之后就希望它們立刻被隱藏。

于是我們把div.style.display = 'none’這行代碼抽出來,用回調(diào)函數(shù)的形式傳入appendDiv方法:

        var appendDiv = function( callback ){
          for ( var i = 0; i < 100; i++ ){
              var div = document.createElement( 'div' );
              div.innerHTML = i;
              document.body.appendChild( div );
              if ( typeof callback === 'function' ){
                  callback( div );
              }
          }
       };

       appendDiv(function( node ){
          node.style.display = 'none';
       });

可以看到,隱藏節(jié)點的請求實際上是由客戶發(fā)起的,但是客戶并不知道節(jié)點什么時候會創(chuàng)建好,于是把隱藏節(jié)點的邏輯放在回調(diào)函數(shù)中,“委托”給appendDiv方法。appendDiv方法當(dāng)然知道節(jié)點什么時候創(chuàng)建好,所以在節(jié)點創(chuàng)建好的時候,appendDiv會執(zhí)行之前客戶傳入的回調(diào)函數(shù)。

2. Array.prototype.sort

Array.prototype.sort接受一個函數(shù)當(dāng)作參數(shù),這個函數(shù)里面封裝了數(shù)組元素的排序規(guī)則。從Array.prototype.sort的使用可以看到,我們的目的是對數(shù)組進行排序,這是不變的部分;而使用什么規(guī)則去排序,則是可變的部分。把可變的部分封裝在函數(shù)參數(shù)里,動態(tài)傳入Array.prototype.sort,使Array.prototype.sort方法成為了一個非常靈活的方法,代碼如下:

        //從小到大排列

        [ 1, 4, 3 ].sort( function( a, b ){
            return a - b;
        });

        // 輸出: [ 1, 3, 4 ]

        //從大到小排列

        [ 1, 4, 3 ].sort( function( a, b ){
            return b - a;
        });

        // 輸出: [ 4, 3, 1 ]

3.2.2 函數(shù)作為返回值輸出

相比把函數(shù)當(dāng)作參數(shù)傳遞,函數(shù)當(dāng)作返回值輸出的應(yīng)用場景也許更多,也更能體現(xiàn)函數(shù)式編程的巧妙。讓函數(shù)繼續(xù)返回一個可執(zhí)行的函數(shù),意味著運算過程是可延續(xù)的。

1.判斷數(shù)據(jù)的類型

我們來看看這個例子,判斷一個數(shù)據(jù)是否是數(shù)組,在以往的實現(xiàn)中,可以基于鴨子類型的概念來判斷,比如判斷這個數(shù)據(jù)有沒有l(wèi)ength屬性,有沒有sort方法或者slice方法等。但更好的方式是用Object.prototype.toString來計算。Object.prototype.toString.call( obj )返回一個字符串,比如Object.prototype.toString.call( [1,2,3] )總是返回"[object Array]",而Object.prototype.toString.call( “str”)總是返回"[object String]"。所以我們可以編寫一系列的isType函數(shù)。代碼如下:

        var isString = function( obj ){
            return Object.prototype.toString.call( obj ) === '[object String]';
        };

        var isArray = function( obj ){
            return Object.prototype.toString.call( obj ) === '[object Array]';
        };

        var isNumber = function( obj ){
            return Object.prototype.toString.call( obj ) === '[object Number]';
        };

我們發(fā)現(xiàn),這些函數(shù)的大部分實現(xiàn)都是相同的,不同的只是Object.prototype.toString. call( obj )返回的字符串。為了避免多余的代碼,我們嘗試把這些字符串作為參數(shù)提前值入isType函數(shù)。代碼如下:

        var isType = function( type ){
            return function( obj ){
              return Object.prototype.toString.call( obj ) === '[object '+ type +']';
            }
        };
        var isString = isType( 'String' );
        var isArray = isType( 'Array' );
        var isNumber = isType( 'Number' );

        console.log( isArray( [ 1, 2, 3 ] ) );     // 輸出:true

我們還可以用循環(huán)語句,來批量注冊這些isType函數(shù):

        var Type = {};

        for ( var i = 0, type; type = [ 'String', 'Array', 'Number' ][ i++ ]; ){
            (function( type ){
              Type[ 'is' + type ] = function( obj ){
                  return Object.prototype.toString.call( obj ) === '[object '+ type +']';
                  }
              })( type )
        };

        Type.isArray( [] );     // 輸出:true
        Type.isString( "str" );     // 輸出:true

2. getSingle

下面是一個單例模式的例子,在第三部分設(shè)計模式的學(xué)習(xí)中,我們將進行更深入的講解,這里暫且只了解其代碼實現(xiàn):

        var getSingle = function ( fn ) {
            var ret;
            return function () {
              return ret || ( ret = fn.apply( this, arguments ) );
            };
        };

這個高階函數(shù)的例子,既把函數(shù)當(dāng)作參數(shù)傳遞,又讓函數(shù)執(zhí)行后返回了另外一個函數(shù)。我們可以看看getSingle函數(shù)的效果:

        var getScript = getSingle(function(){
            return document.createElement( 'script' );
        });

        var script1 = getScript();
        var script2 = getScript();
        alert ( script1 === script2 );    // 輸出:true

3.2.3 高階函數(shù)實現(xiàn)AOP

AOP(面向切面編程)的主要作用是把一些跟核心業(yè)務(wù)邏輯模塊無關(guān)的功能抽離出來,這些跟業(yè)務(wù)邏輯無關(guān)的功能通常包括日志統(tǒng)計、安全控制、異常處理等。把這些功能抽離出來之后,再通過“動態(tài)織入”的方式摻入業(yè)務(wù)邏輯模塊中。這樣做的好處首先是可以保持業(yè)務(wù)邏輯模塊的純凈和高內(nèi)聚性,其次是可以很方便地復(fù)用日志統(tǒng)計等功能模塊。

在Java語言中,可以通過反射和動態(tài)代理機制來實現(xiàn)AOP技術(shù)。而在JavaScript這種動態(tài)語言中,AOP的實現(xiàn)更加簡單,這是JavaScript與生俱來的能力。

通常,在JavaScript中實現(xiàn)AOP,都是指把一個函數(shù)“動態(tài)織入”到另外一個函數(shù)之中,具體的實現(xiàn)技術(shù)有很多,本節(jié)我們通過擴展Function.prototype來做到這一點。代碼如下:

        Function.prototype.before = function( beforefn ){
            var __self = this;    // 保存原函數(shù)的引用
            return function(){    // 返回包含了原函數(shù)和新函數(shù)的"代理"函數(shù)
              beforefn.apply( this, arguments );     // 執(zhí)行新函數(shù),修正this
              return __self.apply( this, arguments );    // 執(zhí)行原函數(shù)
            }
        };

        Function.prototype.after = function( afterfn ){
            var __self = this;
            return function(){
              var ret = __self.apply( this, arguments );
              afterfn.apply( this, arguments );
              return ret;
            }
        };

        var func = function(){
            console.log( 2 );
        };

        func = func.before(function(){
            console.log( 1 );
        }).after(function(){
            console.log( 3 );
        });

        func();

我們把負責(zé)打印數(shù)字1和打印數(shù)字3的兩個函數(shù)通過AOP的方式動態(tài)植入func函數(shù)。通過執(zhí)行上面的代碼,我們看到控制臺順利地返回了執(zhí)行結(jié)果1、2、3。

這種使用AOP的方式來給函數(shù)添加職責(zé),也是JavaScript語言中一種非常特別和巧妙的裝飾者模式實現(xiàn)。這種裝飾者模式在實際開發(fā)中非常有用,我們將在第15章進行詳細的講解。有興趣的讀者可以提前翻閱第15章進行了解。

3.2.4 高階函數(shù)的其他應(yīng)用

前面我們已經(jīng)學(xué)習(xí)過高階函數(shù),本節(jié)我們再挑選一些常見的高階函數(shù)應(yīng)用進行介紹。

1. currying

首先我們討論的是函數(shù)柯里化(function currying)。currying的概念最早由俄國數(shù)學(xué)家Moses Sch?nfinkel發(fā)明,而后由著名的數(shù)理邏輯學(xué)家Haskell Curry將其豐富和發(fā)展,currying由此得名。

currying又稱部分求值。一個currying的函數(shù)首先會接受一些參數(shù),接受了這些參數(shù)之后,該函數(shù)并不會立即求值,而是繼續(xù)返回另外一個函數(shù),剛才傳入的參數(shù)在函數(shù)形成的閉包中被保存起來。待到函數(shù)被真正需要求值的時候,之前傳入的所有參數(shù)都會被一次性用于求值。

從字面上理解currying并不太容易,我們來看下面的例子。

假設(shè)我們要編寫一個計算每月開銷的函數(shù)。在每天結(jié)束之前,我們都要記錄今天花掉了多少錢。代碼如下:

        var monthlyCost = 0;

        var cost = function( money ){
            monthlyCost += money;
        };

        cost( 100 );    // 第1天開銷
        cost( 200 );    // 第2天開銷
        cost( 300 );    // 第3天開銷
        //cost( 700 );    // 第30天開銷

        alert ( monthlyCost );      // 輸出:600

通過這段代碼可以看到,每天結(jié)束后我們都會記錄并計算到今天為止花掉的錢。但我們其實并不太關(guān)心每天花掉了多少錢,而只想知道到月底的時候會花掉多少錢。也就是說,實際上只需要在月底計算一次。

如果在每個月的前29天,我們都只是保存好當(dāng)天的開銷,直到第30天才進行求值計算,這樣就達到了我們的要求。雖然下面的cost函數(shù)還不是一個currying函數(shù)的完整實現(xiàn),但有助于我們了解其思想:

        var cost = (function(){
            var args = [];

            return function(){
              if ( arguments.length === 0 ){
                  var money = 0;
                  for ( var i = 0, l = args.length; i < l; i++ ){
                      money += args[ i ];
                  }
                  return money;
              }else{
                  [].push.apply( args, arguments );
              }
            }

        })();

        cost( 100 );    // 未真正求值
        cost( 200 );    // 未真正求值
        cost( 300 );    // 未真正求值

        console.log( cost() );       // 求值并輸出:600

接下來我們編寫一個通用的function currying(){}, function currying(){}接受一個參數(shù),即將要被currying的函數(shù)。在這個例子里,這個函數(shù)的作用遍歷本月每天的開銷并求出它們的總和。代碼如下:

        var currying = function( fn ){
            var args = [];

            return function(){
              if ( arguments.length === 0 ){
                  return fn.apply( this, args );
              }else{
                  [].push.apply( args, arguments );
                  return arguments.callee;
              }
            }

        };

        var cost = (function(){
            var money = 0;

            return function(){
              for ( var i = 0, l = arguments.length; i < l; i++ ){
                  money += arguments[ i ];
              }
              return money;
            }

        })();

        var cost = currying( cost );    // 轉(zhuǎn)化成currying函數(shù)

        cost( 100 );    // 未真正求值
        cost( 200 );    // 未真正求值
        cost( 300 );    // 未真正求值
        alert ( cost() );     // 求值并輸出:600

至此,我們完成了一個currying函數(shù)的編寫。當(dāng)調(diào)用cost()時,如果明確地帶上了一些參數(shù),表示此時并不進行真正的求值計算,而是把這些參數(shù)保存起來,此時讓cost函數(shù)返回另外一個函數(shù)。只有當(dāng)我們以不帶參數(shù)的形式執(zhí)行cost()時,才利用前面保存的所有參數(shù),真正開始進行求值計算。

2. uncurrying

在JavaScript中,當(dāng)我們調(diào)用對象的某個方法時,其實不用去關(guān)心該對象原本是否被設(shè)計為擁有這個方法,這是動態(tài)類型語言的特點,也是常說的鴨子類型思想。

同理,一個對象也未必只能使用它自身的方法,那么有什么辦法可以讓對象去借用一個原本不屬于它的方法呢?

答案對于我們來說很簡單,call和apply都可以完成這個需求:

        var obj1 = {
            name: 'sven'
        };

        var obj2 = {
            getName: function(){
              return this.name;
            }
        };

        console.log( obj2.getName.call( obj1 ) );     // 輸出:sven

我們常常讓類數(shù)組對象去借用Array.prototype的方法,這是call和apply最常見的應(yīng)用場景之一:

        (function(){
            Array.prototype.push.call( arguments, 4 );    // arguments借用Array.prototype.push方法
            console.log( arguments );      // 輸出:[1, 2, 3, 4]
        })( 1, 2, 3 );

在我們的預(yù)期中,Array.prototype上的方法原本只能用來操作array對象。但用call和apply可以把任意對象當(dāng)作this傳入某個方法,這樣一來,方法中用到this的地方就不再局限于原來規(guī)定的對象,而是加以泛化并得到更廣的適用性。

Array.prototype上的方法可以操作任何對象的原理可參閱2.2節(jié)。

那么有沒有辦法把泛化this的過程提取出來呢?本小節(jié)講述的uncurrying就是用來解決這個問題的。uncurrying的話題來自JavaScript之父Brendan Eich在2011年發(fā)表的一篇Twitter。以下代碼是uncurrying的實現(xiàn)方式之一:

        Function.prototype.uncurrying = function () {
            var self = this;
            return function() {
              var obj = Array.prototype.shift.call( arguments );
              return self.apply( obj, arguments );
          };
        };

在講解這段代碼的實現(xiàn)原理之前,我們先來瞧瞧它有什么作用。

在類數(shù)組對象arguments借用Array.prototype的方法之前,先把Array.prototype.push.call這句代碼轉(zhuǎn)換為一個通用的push函數(shù):

        var push = Array.prototype.push.uncurrying();

        (function(){
            push( arguments, 4 );
            console.log( arguments );     // 輸出:[1, 2, 3, 4]
        })( 1, 2, 3 );

通過uncurrying的方式,Array.prototype.push.call變成了一個通用的push函數(shù)。這樣一來,push函數(shù)的作用就跟Array.prototype.push一樣了,同樣不僅僅局限于只能操作array對象。而對于使用者而言,調(diào)用push函數(shù)的方式也顯得更加簡潔和意圖明了。

我們還可以一次性地把Array.prototype上的方法“復(fù)制”到array對象上,同樣這些方法可操作的對象也不僅僅只是array對象:

        for ( var i = 0, fn, ary = [ 'push', 'shift', 'forEach' ]; fn = ary[ i++ ]; ){
            Array[ fn ] = Array.prototype[ fn ].uncurrying();
        };

        var obj = {
            "length": 3,
            "0": 1,
            "1": 2,
            "2": 3
        };

        Array.push( obj, 4 );     // 向?qū)ο笾刑砑右粋€元素
        console.log( obj.length );    // 輸出:4

        var first = Array.shift( obj );    // 截取第一個元素
        console.log( first );     // 輸出:1
        console.log( obj );    // 輸出:{0: 2, 1: 3, 2: 4, length: 3}

        Array.forEach( obj, function( i, n ){
            console.log( n );      // 分別輸出:0, 1, 2
        });

甚至Function.prototype.call和Function.prototype.apply本身也可以被uncurrying,不過這沒有實用價值,只是使得對函數(shù)的調(diào)用看起來更像JavaScript語言的前身Scheme:

        var call = Function.prototype.call.uncurrying();
        var fn = function( name ){
            console.log( name );
        };
        call( fn, window, 'sven' );     // 輸出:sven

        var apply = Function.prototype.apply.uncurrying();
        var fn = function( name ){
            console.log( this.name );     // 輸出:"sven"
            console.log( arguments );     // 輸出: [1, 2, 3]
        };
        apply( fn, { name: 'sven' }, [ 1, 2, 3 ] );

目前我們已經(jīng)給出了Function.prototype.uncurrying的一種實現(xiàn)。現(xiàn)在來分析調(diào)用Array.prototype.push.uncurrying()這句代碼時發(fā)生了什么事情:

        Function.prototype.uncurrying = function () {
            var self = this;     // self此時是Array.prototype.push
            return function() {
              var obj = Array.prototype.shift.call( arguments );
              // obj是{
              //    "length": 1,
              //    "0": 1
              // }
              // arguments對象的第一個元素被截去,剩下[2]
              return self.apply( obj, arguments );
              // 相當(dāng)于Array.prototype.push.apply( obj, 2 )
            };
        };

        var push = Array.prototype.push.uncurrying();
        var obj = {
            "length": 1,
            "0": 1
        };

        push( obj, 2 );
        console.log( obj );     // 輸出:{0: 1, 1: 2, length: 2}

除了剛剛提供的代碼實現(xiàn),下面的代碼是uncurrying的另外一種實現(xiàn)方式:

        Function.prototype.uncurrying = function(){
            var self = this;
            return function(){
              return Function.prototype.call.apply( self, arguments );
            }
        };

3.函數(shù)節(jié)流

JavaScript中的函數(shù)大多數(shù)情況下都是由用戶主動調(diào)用觸發(fā)的,除非是函數(shù)本身的實現(xiàn)不合理,否則我們一般不會遇到跟性能相關(guān)的問題。但在一些少數(shù)情況下,函數(shù)的觸發(fā)不是由用戶直接控制的。在這些場景下,函數(shù)有可能被非常頻繁地調(diào)用,而造成大的性能問題。下面將列舉一些這樣的場景。

(1) 函數(shù)被頻繁調(diào)用的場景

? window.onresize事件。我們給window對象綁定了resize事件,當(dāng)瀏覽器窗口大小被拖動而改變的時候,這個事件觸發(fā)的頻率非常之高。如果我們在window.onresize事件函數(shù)里有一些跟DOM節(jié)點相關(guān)的操作,而跟DOM節(jié)點相關(guān)的操作往往是非常消耗性能的,這時候瀏覽器可能就會吃不消而造成卡頓現(xiàn)象。

? mousemove事件。同樣,如果我們給一個div節(jié)點綁定了拖曳事件(主要是mousemove),當(dāng)div節(jié)點被拖動的時候,也會頻繁地觸發(fā)該拖曳事件函數(shù)。

? 上傳進度。微云的上傳功能使用了公司提供的一個瀏覽器插件。該瀏覽器插件在真正開始上傳文件之前,會對文件進行掃描并隨時通知JavaScript函數(shù),以便在頁面中顯示當(dāng)前的掃描進度。但該插件通知的頻率非常之高,大約一秒鐘10次,很顯然我們在頁面中不需要如此頻繁地去提示用戶。

(2) 函數(shù)節(jié)流的原理

我們整理上面提到的三個場景,發(fā)現(xiàn)它們面臨的共同問題是函數(shù)被觸發(fā)的頻率太高。

比如我們在window.onresize事件中要打印當(dāng)前的瀏覽器窗口大小,在我們通過拖曳來改變窗口大小的時候,打印窗口大小的工作1秒鐘進行了10次。而我們實際上只需要2次或者3次。這就需要我們按時間段來忽略掉一些事件請求,比如確保在500ms內(nèi)只打印一次。很顯然,我們可以借助setTimeout來完成這件事情。

(3) 函數(shù)節(jié)流的代碼實現(xiàn)

關(guān)于函數(shù)節(jié)流的代碼實現(xiàn)有許多種,下面的throttle函數(shù)的原理是,將即將被執(zhí)行的函數(shù)用setTimeout延遲一段時間執(zhí)行。如果該次延遲執(zhí)行還沒有完成,則忽略接下來調(diào)用該函數(shù)的請求。throttle函數(shù)接受2個參數(shù),第一個參數(shù)為需要被延遲執(zhí)行的函數(shù),第二個參數(shù)為延遲執(zhí)行的時間。具體實現(xiàn)代碼如下:

        var throttle = function ( fn, interval ) {

            var __self = fn,    // 保存需要被延遲執(zhí)行的函數(shù)引用
              timer,      // 定時器
              firstTime = true;    // 是否是第一次調(diào)用

            return function () {
              var args = arguments,
                  __me = this;

              if ( firstTime ) {    // 如果是第一次調(diào)用,不需延遲執(zhí)行
                  __self.apply(__me, args);
                  return firstTime = false;
              }

              if ( timer ) {    // 如果定時器還在,說明前一次延遲執(zhí)行還沒有完成
                  return false;
              }

              timer = setTimeout(function () {  // 延遲一段時間執(zhí)行
                  clearTimeout(timer);
                  timer = null;
                  __self.apply(__me, args);

              }, interval || 500 );

          };

        };

        window.onresize = throttle(function(){
          console.log( 1 );
        }, 500 );

4.分時函數(shù)

在前面關(guān)于函數(shù)節(jié)流的討論中,我們提供了一種限制函數(shù)被頻繁調(diào)用的解決方案。下面我們將遇到另外一個問題,某些函數(shù)確實是用戶主動調(diào)用的,但因為一些客觀的原因,這些函數(shù)會嚴(yán)重地影響頁面性能。

一個例子是創(chuàng)建WebQQ的QQ好友列表。列表中通常會有成百上千個好友,如果一個好友用一個節(jié)點來表示,當(dāng)我們在頁面中渲染這個列表的時候,可能要一次性往頁面中創(chuàng)建成百上千個節(jié)點。

在短時間內(nèi)往頁面中大量添加DOM節(jié)點顯然也會讓瀏覽器吃不消,我們看到的結(jié)果往往就是瀏覽器的卡頓甚至假死。代碼如下:

        var ary = [];

        for ( var i = 1; i <= 1000; i++ ){
            ary.push( i );     // 假設(shè)ary裝載了1000個好友的數(shù)據(jù)
        };

        var renderFriendList = function( data ){
            for ( var i = 0, l = data.length; i < l; i++ ){
              var div = document.createElement( 'div' );
              div.innerHTML = i;
              document.body.appendChild( div );
            }
        };

        renderFriendList( ary );

這個問題的解決方案之一是下面的timeChunk函數(shù),timeChunk函數(shù)讓創(chuàng)建節(jié)點的工作分批進行,比如把1秒鐘創(chuàng)建1000個節(jié)點,改為每隔200毫秒創(chuàng)建8個節(jié)點。

timeChunk函數(shù)接受3個參數(shù),第1個參數(shù)是創(chuàng)建節(jié)點時需要用到的數(shù)據(jù),第2個參數(shù)是封裝了創(chuàng)建節(jié)點邏輯的函數(shù),第3個參數(shù)表示每一批創(chuàng)建的節(jié)點數(shù)量。代碼如下:

        var timeChunk = function( ary, fn, count ){

            var obj,
              t;

            var len = ary.length;

            var start = function(){
              for ( var i = 0; i < Math.min( count || 1, ary.length ); i++ ){
                  var obj = ary.shift();
                  fn( obj );
              }
            };

            return function(){
              t = setInterval(function(){
                if ( ary.length === 0 ){  // 如果全部節(jié)點都已經(jīng)被創(chuàng)建好
                    return clearInterval( t );
                }
                start();
              }, 200 );    // 分批執(zhí)行的時間間隔,也可以用參數(shù)的形式傳入

            };

        };

最后我們進行一些小測試,假設(shè)我們有1000個好友的數(shù)據(jù),我們利用timeChunk函數(shù),每一批只往頁面中創(chuàng)建8個節(jié)點:

        var ary = [];

        for ( var i = 1; i <= 1000; i++ ){
            ary.push( i );
        };

        var renderFriendList = timeChunk( ary, function( n ){
            var div = document.createElement( 'div' );
            div.innerHTML = n;
            document.body.appendChild( div );
        }, 8 );

        renderFriendList();

5.惰性加載函數(shù)

在Web開發(fā)中,因為瀏覽器之間的實現(xiàn)差異,一些嗅探工作總是不可避免。比如我們需要一個在各個瀏覽器中能夠通用的事件綁定函數(shù)addEvent,常見的寫法如下:

        var addEvent = function( elem, type, handler ){
            if ( window.addEventListener ){
              return elem.addEventListener( type, handler, false );
              }
              if ( window.attachEvent ){
                  return elem.attachEvent( 'on' + type, handler );
              }
        };

這個函數(shù)的缺點是,當(dāng)它每次被調(diào)用的時候都會執(zhí)行里面的if條件分支,雖然執(zhí)行這些if分支的開銷不算大,但也許有一些方法可以讓程序避免這些重復(fù)的執(zhí)行過程。

第二種方案是這樣,我們把嗅探瀏覽器的操作提前到代碼加載的時候,在代碼加載的時候就立刻進行一次判斷,以便讓addEvent返回一個包裹了正確邏輯的函數(shù)。代碼如下:

        var addEvent = (function(){
            if ( window.addEventListener ){
              return function( elem, type, handler ){
                  elem.addEventListener( type, handler, false );
              }
            }
            if ( window.attachEvent ){
              return function( elem, type, handler ){
                  elem.attachEvent( 'on' + type, handler );
              }
            }
        })();

目前的addEvent函數(shù)依然有個缺點,也許我們從頭到尾都沒有使用過addEvent函數(shù),這樣看來,前一次的瀏覽器嗅探就是完全多余的操作,而且這也會稍稍延長頁面ready的時間。

第三種方案即是我們將要討論的惰性載入函數(shù)方案。此時addEvent依然被聲明為一個普通函數(shù),在函數(shù)里依然有一些分支判斷。但是在第一次進入條件分支之后,在函數(shù)內(nèi)部會重寫這個函數(shù),重寫之后的函數(shù)就是我們期望的addEvent函數(shù),在下一次進入addEvent函數(shù)的時候,addEvent函數(shù)里不再存在條件分支語句:

        <html>
            <body>
              <div id="div1">點我綁定事件</div>
            <script>

            var addEvent = function( elem, type, handler ){
              if ( window.addEventListener ){
                  addEvent = function( elem, type, handler ){
                      elem.addEventListener( type, handler, false );
                  }
              }else if ( window.attachEvent ){
                  addEvent = function( elem, type, handler ){
                      elem.attachEvent( 'on' + type, handler );
                  }
              }

              addEvent( elem, type, handler );
            };
              var div = document.getElementById( 'div1' );

              addEvent( div, 'click', function(){
                  alert (1);
              });

              addEvent( div, 'click', function(){
                  alert (2);
              });

            </script>
            </body>
        </html>
主站蜘蛛池模板: 河池市| 讷河市| 本溪市| 芜湖县| 吴桥县| 陆川县| 叶城县| 平和县| 牙克石市| 咸丰县| 合水县| 买车| 龙海市| 鹤壁市| 财经| 清徐县| 黑山县| 长岭县| 衡阳市| 屏山县| 体育| 弥渡县| 瑞丽市| 云霄县| 古丈县| 肇东市| 子洲县| 榆中县| 西充县| 儋州市| 资溪县| 北辰区| 彭泽县| 伽师县| 天台县| 康乐县| 伊春市| 博白县| 青河县| 温州市| 新营市|