2013年12月31日 星期二

LINQ 封裝 "computed" properties 五行封印術 - 看技術文章學英文第二彈 - 跨年特別篇


警告:此篇為翻譯文章,所有內容皆為原作者所有
此外歡迎指正破英文,如果哪邊有嚴重謬誤哪肯定是我把靈魂出賣給了 google 小姐 ... 這篇好難翻呀 我日 -.-




警告:此篇文章 內容艱澀 技術過硬
這篇 100% 是技術人員寫的 英文用字的 可讀性 可維護性 就跟"大部分的專案" 一樣好




這篇文章的

server 指得是 SQL SERVER 這類的東東

client 指得是 自定義的 C# 代碼

translation 指得是 將 C# 轉成 查詢端語法





客戶端 自訂屬性 和 遠端 LINQ provider


如果你寫了些自訂屬性 你將無法在 遠端 LINQ 操作中 使用他們 (EX:LINQ to Entities)

問題發生的原因在於 這些客製屬性無法 轉譯到 server (EX:SQL SERVER)

因為他們已經被編譯成 中介語言 (intermediate language - IL)

而不是 個 需要被 IQueryable 實作 translation 的 LINQ 表達式樹 (LINQ expression trees)

目前在 .NET 是沒法度 用些反向工程 將 IL 變回方法 (methods) 和 語法 (syntax)

讓我們可以如預期的轉換成查詢語法 (remote query)

這意味者你不得不將查詢寫成兩個部分

首先是 server (EX:SQL SERVER) 可以幹的 (LINQ 查詢)

再用 ToList 或者 AsEnumerable 查詢出中間結果 到 client

然後就只能在這邊進行些運算操作

如果你想要 激烈的 做些轉換 這會非常耗損效能

我們 (David, Colin Meek and myself) 想出了一個 非關 provider 一次性宣告 屬性的方法

這樣他們就可以在兩種情況下使用

他能在 LINQ to SQL, LINQ to Entities 和任何其他 LINQ 輕易的運算屬性

在 .NET 3.5 SP1 運作良好 :)


使用前



這邊我們 擴充 Employee 類別 增加 Age 和 FullName

我們只需要 名字帶有 "da" 的人

但是為了 做出選取 我們不得不 抓下所有東東 到 client


partial class Employee {
 public string FullName {
  get { return Forename + " " + Surname; }
 }

 public int Age {
  get { return DateTime.Now.Year - BirthDate.Year -
   (((DateTime.Now.Month < BirthDate.Month)
   || DateTime.Now.Month == BirthDate.Month && DateTime.Now.Day < BirthDate.Day) ? 1 : 0));
  }
 }
}
...
var employees = db.Employees.ToList().Where(e => e.FullName.Contains("da")).GroupBy(e => e.Age);


使用後


這邊 使用我們的方式

他發生在 server 端...

而且 LINQ to SQL, LINQ to Entities 都行的通


partial class Employee {
    private static readonly CompiledExpression fullNameExpression
     = DefaultTranslationOf.Property(e => e.FullName).Is(e => e.Forename + " " + e.Surname);
    private static readonly CompiledExpression ageExpression
     = DefaultTranslationOf.Property(e => e.Age).Is(e => DateTime.Now.Year - e.BirthDate.Value.Year - (((DateTime.Now.Month < e.BirthDate.Value.Month) || (DateTime.Now.Month == e.BirthDate.Value.Month && DateTime.Now.Day < e.BirthDate.Value.Day)) ? 1 : 0)));

    public string FullName {
        get { return fullNameExpression.Evaluate(this); }
    }

    public int Age {
        get { return ageExpression.Evaluate(this); }
    }
}
...
var employees = db.Employees.Where(e => e.FullName.Contains("da")).GroupBy(e => e.Age).WithTranslations();


使用注意事項


上述技巧的注意事項是 你需要確保 你的類別 在查詢之前 有初始化 (看看下面的替代方案)

然而 很顯然的 你註冊的表達式(expression) 所指的 屬性(property) 必須能夠被 translated

所以你將需要約束你自己的方法(methods) 和 實作 IQueryable


這裡是一些上述例子的幾個替代方案。


註冊 表達式


你可以像這個例子一樣 在類別本身的註冊屬性

該屬性可以不靠任何反射 (reflection) 就能自行 計算表達式 (evaluate the expressions)

此外 如果 效能考量不是那麼重要的話

你可以在其他地方註冊它們

使用 方法 (methods) 透過反射 動態查看他們的值

例如
...
DefaultTranslationOf.Property(e => e.FullName).Is(e => e.Forename + " " + e.Surname);
var employees = db.Employees.Where(e => e.FullName.Contains("da")).GroupBy(e => e.Age).WithTranslations();
...
partial class Employee {
    public string FullName { get { return DefaultTranslationOf.Evaluate(this, MethodInfo.GetCurrentMethod());} }
}




如果 效能考量很重要

你可以獲得 重複運算的 完整代碼

一次從中介語言中的屬性 然後一次從 translation 的表達示

針對不同情境的不同對應


有時候 你的程式的某些部份 可能想在不同的情境下

使用不同的 translations 或 performance 等等

沒問題!

WithTranslations 方法 通常操作著預設的 translation 對應 (map)

(靠 DefaultTranslationOf 訪問)

但也有另一個使用 TranslationMap 的多載

讓你對付特定的情況

例如
var myTranslationMap = new TranslationMap();
myTranslationMap.Add(e => e.Name, e => e.FirstName + " " + e.LastName);
var results = (from e in db.Employees where e.Name.Contains("martin") select e).WithTranslations(myTranslationMap).ToList();



他是怎麼運作的


CompiledExpression<T, TResult>

我們需要做的第一件事是讓 自訂編寫的「計算」屬性

遠離 IL 然後 變回 表達式樹 (expression trees)

所以我們才能 translate 它們

因為我們也想 在 client 計算他們

所以我們需要在 執行時期 編譯他們

CompiledExpression 只是為了取得 表達式 Func<T, TResult> 而存在

允許編譯和計算 objects 針對不同的編譯版本

ExpressiveExtensions


這個小類別 提供 WithTranslations 擴充方法 和 internal TranslatingVisitor

以利走訪 透過 TranslationMap 實際註冊 Func<T, TResult> expressions 的 property

所以 底層的 LINQ provider 可以處理他

TranslationMap


我們需要有 編譯 表達式的 屬性對應

TranslationMap 為此存在

你可以手動建立一個 TranslationMap

將其傳入 WithTranslations

如果你想要依靠程式 在執行時期建立它們 或者 對不同情境使用不同方法

但是通常你會想用它...

DefaultTranslationOf


這個 helper 類別 讓你針對 default TranslationMap 註冊屬性

我們在 WithTranslations 沒有傳入值的時候使用

它也可以讓你查看 已註冊的屬性

所以你也可以計算他們

即使它耗費了一點反射的效能
public int Age { get { return DefaultTranslationOf.Evaluate(this, MethodInfo.GetCurrentMethod()); } }




有樂!


[)amien











最後...新年快樂 2014

2013年12月21日 星期六

安裝 VS 2013 後 無法連線 無法連DB 記憶體損毀 或者 跳 AccessViolationException 的解決方法

原本只是想裝個 VS 2013 玩玩

結果立馬悲劇

一開始無法連線

後來說啥記憶體損毀

最後跳 AccessViolationException

估狗半天英文的 要嘛沒解 要嘛誤導

就在想放棄 重灌 VS 的時候

心中有個聲音告訴我 對岸神人多 一定有解答

立馬搜尋條件 鎖中文

果真馬上有篇 CSDN

http://bbs.csdn.net/topics/390623185

用管理员身份运行CMD,输入netsh winsock reset并回车(注意,必须是已管理员身份运行,这个重置LSP连接)

果真有效

雖然我不知這是幹啥的

但是有效



敬對岸神人!

2013年11月28日 星期四

AngularJS 玩弄手札 - 小心 filter 挖了個坑 然後在上面鋪上一些草皮

NG 很神 很淫蕩

但是不會有什麼東西只有好沒有壞

像是 filter 在不了解的情況下 就很容易不知不覺中招





首先先認清一下以下幾點事實





NG 神奇的 two-way binding 是靠 dirty check

dirty check 骨子裡是靠這頁 http://docs.angularjs.org/guide/scope 最下面的 digest loop 達成的

簡單來說

在 NG "裡面"(像是 controller)

當修改了 $scope 裡面的值

NG 會自動觸發 digest loop

然後自動幫我們更新 view 的 DOM



然而 在 NG "外面"(像是 jquery click)

即使修改了 $scope 裡面的值

NG 也"不會"自動觸發 digest loop

所以也"不會"自動幫我們更新 view 的 DOM

"但是"他會在下一次有人觸發 digest loop 的時候一起被更新



註:

一般 在NG外面 我們可以靠 $(element).scope() 取得 scope 物件

再靠 scope.$apply() 觸發 digest loop





再歸納一下上述事實要點

1. 天下沒白吃的午餐 神奇的魔法 two-way binding 這麼猛 是因為靠 dirty check

在我們觸發一些動作 事件 或 行為的時候 做檢查


2. 沒進入 digest loop 即使 scope 被滿門抄斬了 view 還在發夢





別忘了 今天要講的 好基友 filter





首先 filter 在大多數情況下非常好用 像是 formatter 跟 formatter 還有 formatter

沒錯 這意味著 當他被用在 非 formatter 的用途的時候 會變得有點糟糕

尤其是他跟另外一位 好基友 ngRepeat 一起撿肥皂的時候 更是隱藏殺機





下面是段很常見的應用 一個 array 搭 ngRepeat 產 view 然後 透過 filter 篩選項目

http://jsbin.com/oQumafE/2/embed?html,output

原本應該是這個

$scope.array = [
    { Name: '蓋倫', Title: '哥有四多藍' },
    { Name: '嘉文四世', Title: '坑爹的' },
    { Name: '趙信', Title: '長槍依在 菊花拿來' },
    { Name: '泰達米爾', Title: '五秒真男人' },
    { Name: '潘森', Title: '空降斯巴達' }
];

該死的 js逼 最近竟然不能放中文 ZZZ





為啥 filter 可以自動過濾 然後 改變 view ?

很顯然 他有進入 digest loop

不信邪 用這個可以試試

$scope.$watch(function () {
    console.log('digest!!!');
});


那 filter 又是怎樣知道 什麼時候該做過濾的檢查?

答案就是 他不知道

那...

沒錯 任何一次的 digest 都會讓 filter 跑一遍 他的 function

而且他可能不只跑一遍





所以我們有意無意的更新 scope 都會 瘋狂的跑 filter

要是我們 還不小心用了 跟 鍵盤 滑鼠 視窗 焦點 之類有關的 會猛觸發的東東

這下就悲劇了

http://jsbin.com/IseQIgo/2/embed?html,console,output



要是我們 還不小心用了 在 filter 裡面又 有意無意的更新 scope 那更慘了

這下就靠悲了

digest 觸發 filter 檢查 filter 又再觸發 digest

http://jsbin.com/UXiZawo/1/embed?html,console,output



甚至條件式隨機的 每次 return 的陣列都不同 也GG (其實我只想要每次看到隨機的資料而已呀 Q_Q)

http://jsbin.com/etojAYe/1/embed?html,console,output



就算上述 挑肛 的這些白目事都沒發生 下面這個一定天天上演

"邏輯有點兒牛逼 條件有些兒糾結 運算有點兒溫吞"

view 就會像被咬住一般 卡卡的 不太能動 無法回應

然後就是定番 電話響瞭~ 客戶怒瞭~ 奇摩幾差瞭~

其實這不是 電腦慢 瀏覽器渣 而是我們的 filter 掃得太快啦 消化不良啦





結論

1. 當我們想要將 filter 用在 非 formatter 用途上的邏輯判斷 最好三思而後行

2. 小心 好基友 ngRepeat

3. 任何細小的風吹草動 都會造成 filter 瘋狂掃射

4. 想知道 filter 到底有多快槍 可以看看 歪果神人大大文章 How Often Do Filters Execute In AngularJS





現在我們知道哪邊有坑了 但是... 還沒有越過呀 需求仍未解





小伎倆

1. 設法把複雜噁心的 "過濾" 邏輯判斷 留在 controller 裡面

2. 主動控制 觸發時機 才進行 "過濾" 邏輯判斷

3. 真的沒技 就用 $scope.$watch() 吧

雖然 watch 多了也不好 至少在 watch 裡面可以判斷 "有其必要性" 才進行 "過濾" 邏輯判斷

4. 留心那些 機關槍事件 設法實作 debounce (像這個)防止短時間內 "過濾" 邏輯判斷 被掃射

5. 打破 NG 崇拜 其實真的沒有必要任何功能都用 NG 實作

雖然死忠狂熱分子 視 J蛞蝓 如大敵 無法忍受 用了 NG 還出現 J蛞蝓

但是顯而易見 沒有銀彈 任何東西 都有長短處

NG 強在 使用 model 來 two way binding 的概念 (這是讓我們能夠快速開發的主要火力)

但是有些功能實在不必 NG 像是 廣告輪播、DOM 特效 或者其他任何 "無須" 做資料交換 對應顯示 的功能





光說不練 LIKE 政O 不是好漢 放兩個 陽春 debounce 實作



隨機條件 (原本會因為 每次結果都不同 造成 digest 10 error)


<!DOCTYPE html>
<html ng-app="app">
<head>
    <script src="Scripts/underscore.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script>
    <meta charset="utf-8" />
    <title>JS Bin</title>
</head>
<body>
 
    <div ng-controller="ctrl">

        <input type="text" ng-model="keyWord" />

        <ul>
            <li ng-repeat="item in array | random">
                <span>[{{item.Name}}] {{item.Title}}</span>
            </li>
        </ul>

        <input type="button" value="jquery outside apply" class="outside" />

        <input type="button" ng-click="onClick()" value="click me" />

        <input type="button" ng-mouseover="onMouseover()" value="mouseover me" />

        <input type="button" ng-mouseenter="onMouseenter()" value="mouseenter me" />

        <input type="button" ng-mouseleave="onMouseleave()" value="mouseleave me" />

    </div>

    <script>
        angular.module('app', [])
            .controller('ctrl', function ($scope) {

                $scope.array = [
                     { Name: 'Garen', Title: 'I got four Doran' },
                     { Name: 'Jarvan IV', Title: 'Pit his father' },
                     { Name: 'Xin Zhao', Title: 'Hold pike, ass back' },
                     { Name: 'Tryndamere', Title: 'Five seconds real men' },
                     { Name: 'Pantheon', Title: 'Airborne Spartans' }
                ];

                $scope.onClick = function () {
                    //TODO Something...
                };

                $scope.onMouseover = function () {
                    //TODO Something...
                };

                $scope.onMouseenter = function () {
                    //TODO Something...
                };

                $scope.onMouseleave = function () {
                    //TODO Something...
                };
            })
            .factory('debounceFactory', function () {
                return function (predicate, wait) {
                    var debounce;
                    var arr = [];
                    return function (data) {
                        clearTimeout(debounce);
                        debounce = setTimeout(function () {
                            arr.length = 0;
                        }, wait);
                        if (arr.length === 0) {
                            for (var i = 0; i < data.length; i++) {
                                if (predicate(data[i])) {
                                    arr.push(data[i]);
                                }
                            }
                        }
                        return arr;
                    };
                }
            })
            .filter('random', function (debounceFactory) {
                var counter = 0;
                var fn = debounceFactory(function (x) {
                    return ~~(Math.random() * 2) === 0;
                });
                return function (data) {
                    console.log(++counter);
                    return fn(data);
                };
            });
    </script>
    <script>
        $('.outside').click(function () {
            var scope = $('[ng-controller="ctrl"]').scope();

            scope.onClick();

            scope.$apply();
        });
    </script>
</body>
</html>



變動原本物件 (原本會因為 scope 連動 造成 digest 10 error)


<!DOCTYPE html>
<html ng-app="app">
<head>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script>
    <meta charset="utf-8" />
    <title>JS Bin</title>
</head>
<body>
 
    <div ng-controller="ctrl">

        <input type="text" ng-model="keyWord" />

        <ul>
            <li ng-repeat="item in array | tick">
                <span>[{{item.Name}}] {{item.Title}}</span>
            </li>
        </ul>

        <input type="button" value="jquery outside apply" class="outside" />

        <input type="button" ng-click="onClick()" value="click me" />

        <input type="button" ng-mouseover="onMouseover()" value="mouseover me" />

        <input type="button" ng-mouseenter="onMouseenter()" value="mouseenter me" />

        <input type="button" ng-mouseleave="onMouseleave()" value="mouseleave me" />

    </div>

    <script>
        angular.module('app', [])
            .controller('ctrl', function ($scope) {

                $scope.array = [
                     { Name: 'Garen', Title: 'I got four Doran' },
                     { Name: 'Jarvan IV', Title: 'Pit his father' },
                     { Name: 'Xin Zhao', Title: 'Hold pike, ass back' },
                     { Name: 'Tryndamere', Title: 'Five seconds real men' },
                     { Name: 'Pantheon', Title: 'Airborne Spartans' }
                ];

                $scope.onClick = function () {
                    //TODO Something...
                };

                $scope.onMouseover = function () {
                    //TODO Something...
                };

                $scope.onMouseenter = function () {
                    //TODO Something...
                };

                $scope.onMouseleave = function () {
                    //TODO Something...
                };
            })
            .factory('debounceFactory', function () {
                return function (predicate, wait) {
                    var debounce;
                    var arr = [];
                    return function (data) {
                        clearTimeout(debounce);
                        debounce = setTimeout(function () {
                            arr.length = 0;
                        }, wait);
                        if (arr.length === 0) {
                            for (var i = 0; i < data.length; i++) {
                                if (predicate(data[i])) {
                                    arr.push(data[i]);
                                }
                            }
                        }
                        return arr;
                    };
                }
            })
            .filter('tick', function (debounceFactory) {
                var counter = 0;
                var fn = debounceFactory(function (x) {
                    x.Name = x.Name.split('').reverse().join('');
                    x.Title += ' la~';
                    return true;
                });
                return function (data) {
                    console.log(++counter);
                    return fn(data);
                };
            });
    </script>
    <script>
        $('.outside').click(function () {
            var scope = $('[ng-controller="ctrl"]').scope();

            scope.onClick();

            scope.$apply();
        });
    </script>
</body>
</html>

2013年11月14日 星期四

2013年10月21日 星期一

第一次互毆就上手 Swap Between Both

有時候 我們會遇到互毆的需求

像是我們要讓 A 跟 B 互毆

印出

AAA hit BBB!!!

BBB hit AAA!!!





這麼簡單一片蛋糕都不到的難度

            Member memberA = new Member { ID = 1, Name = "AAA" };

            Member memberB = new Member { ID = 2, Name = "BBB" };

            Debug.WriteLine(string.Format("{0} hit {1}!!!", memberA.Name, memberB.Name));


            Debug.WriteLine(string.Format("{0} hit {1}!!!", memberB.Name, memberA.Name));




但是別忘了 需求有時候是 會長大的幸福

重複代碼不說 有時候遇到邏輯有點兇殘的時候

Wo Ta Ma 的有時候忘了 現在到底誰是誰? 誰是攻誰是受? 誰又要對誰做什麼





此時我們冷靜的來瞧瞧有啥蛛絲馬跡

1.兩個人(物件)要做同一件事 例如 都是要打人

2.唯一的差異是 立場不同 A要打B B要打A

3.先後順序不是很重要 但是兩個人要確實都賞對方一拳





原先的代碼有什麼缺點?

1.相似內容重複代碼

2.不夠直覺 邏輯複雜時難以釐清兩者關係





若是有一個方法可以使用 我們希望他長什麼樣子?

1.泛型是基本 畢竟要什麼都吃

2.同樣的動作 我只想做一遍

3.其實當下我只在乎 哪個主動 哪個被動 骨子裡誰是誰 誰又是誰 我 TMD 一點也不在乎





綜合以上結論這個方法大概就長這樣

        public void Main()
        {
            Member memberA = new Member { ID = 1, Name = "AAA" };

            Member memberB = new Member { ID = 2, Name = "BBB" };

            SwapBetweenBoth<Member>(
                (from, to) =>
                {
                    Debug.WriteLine(string.Format("{0} hit {1}!!!", from.Name, to.Name));
                },
                memberA,
                memberB
            );
        }

        public void SwapBetweenBoth<T>(Action<T, T> action, T one, T another)
        {
            T[] array = { one, another };
            for (int i = 0; i < 2; i++)
            {
                T from = array[i];

                T to = array[(i + 1) % 2];

                action(from, to);
            }

        }




或許有人會問 這樣真的有比較好嗎?

但是至少自我感覺良好呀 哈哈哈

2013年9月16日 星期一

AngularJS 與肥 Lists 的愛恨糾葛 - 看技術文章學英文第一彈

原文在此:AngularJS Performance Tuning for Long Lists

警告:此篇為翻譯文章,所有內容皆為原作者所有

此外歡迎指正破英文,如果哪邊有嚴重謬誤哪肯定是我把靈魂出賣給了 google 小姐





AngularJS Lists 效能調教

AnglarJS 棒棒!但是當他在處裡包含複雜 data 結構的肥 Lists的時候,會像駭客任務一樣產生子彈時間般的世界都靜止了,我們在將核心移植到AnglarJS的時候遭遇這個鳥問題。照理來說應該要很滑溜的顯示500 rows,但是第一次親密接觸的時候花了七秒鐘才顯示出來,這下悲劇惹!
我們發現實作上兩項主要的效能問題,一個是關於ng-repeat directive,另一個是關於filtering
接下來的文章記錄了我們使用各種伎倆,來解決或減輕親密接觸時感受不好的經驗談,這會給你一些點子跟提示,讓你知道什麼東東你可以自個兒玩玩,什麼東西最好不要亂玩。

為神馬ng-repeat 碰上肥lists會這麼慢?

AngularJS ng-repeat directive 大約在 2500 two-way data binding 的時候就快升天了。你可以看看Misko Hevery 的文章(See further reading no 2)。這是因為 AngularJS 使用髒檢查(dirty checking)來監聽所有改變,每次的監聽都要消耗時間,所以包含複雜 data 結構的肥 Lists 將會搞爛你的 APP

先來瞧瞧效能發生啥事

Time logging directive:

我們寫了個簡單的directive來測量list 渲染出來需要花多少時間,他會記錄ng-repeat生出$last浪費多少光陰,資料的參考會存在TimeTracker-service裡面,所以他跟從server讀取data的時間是獨立開來紀錄的。

// Post repeat directive for logging the rendering time
angular.module('siApp.services').directive('postRepeatDirective',
    ['$timeout', '$log', 'TimeTracker',
    function ($timeout, $log, TimeTracker) {
        return function (scope, element, attrs) {
            if (scope.$last) {
                $timeout(function () {
                    var timeFinishedLoadingList = TimeTracker.reviewListLoaded();
                    var ref = new Date(timeFinishedLoadingList);
                    var end = new Date();
                    $log.debug("## DOM rendering list took: " + (end - ref) + " ms");
                });
            }
        };
    }
]);

//Use in HTML:
//<tr ng-repeat="item in items" post-repeat-directive>...</tr>


Chrome 的時間軸開發者工具

Chrome 開發者工具 timeline tab,你可以看到事件和瀏覽器每秒記憶體的分配。記憶體工具對於偵查記憶體消耗和一頁需要多少記憶體十分有效。每秒幀數低於30是頁面閃爍是大部分的問題點,幀數工具提供了渲染效能的觀察,此外他還顯示 JavaScript 消耗了多少 CPU 的時間。

最基本的調教伎倆 - 限制 List Size

限制顯示時 List Size 是舒緩問題的最佳的方式,你可以玩玩分頁,或者無限捲捲。

分頁

我們使用 AngularJS 玩分頁是靠”limitTo filter (1.1.4才有 :P) 搭配自製”startFromfilter。它可以讓我們限制List Size 來減少渲染時間,他是最有效減少渲染時間的方法。
如果你不能或者不想這麼幹,但是你有filtering慢的症狀,看看步驟五,然後使用ng-show 來隱藏要過濾掉的元素。

//Pagination in controller
$scope.currentPage = 0;
$scope.pageSize = 75;
$scope.numberOfPages = function() {
    return Math.ceil($scope.displayedItemsList.length/ $scope.pageSize);
};

//Start from filter
angular.module('app').filter('startFrom', function() {
    return function(input, start) {        
        return input.slice(start);
    };

//Use in HTML
//Pagination buttons
//<button ng-repeat="i in getNumber(numberOfPages()) track by $index" ng-click="setCurrentPage($index)">{{$index + 1}}</button

//Displayed list
//<tr ng-repeat="item in displayedItemsList | startFrom: currentPage * pageSize  | limitTo:pageSize" /tr>


無限捲捲

使用無限捲捲不是我們的使用情境的選項,如果你想深入的了解一下無限捲捲,下面這個連結將帶領你前往AngularJS infinite scrolling 專案:

調教方針

1.渲染List的時候不要使用data binding

這個是最顯而易見的解法,data-binding最有可能是效能問題的起點。拋棄data binding是絕對沒問題的,如果你只是要一次性的顯示這些資料,而且不需要處理更新或變化。悲劇的是你的List將失去控制,所以這個親密接觸的方法也不是我們的選項。
有興趣點這個:

2. 不要使用行內方法呼叫計算data

為了在controller裡面直接過濾List,不要使用方法來取得過濾後的集合,ng-repeat會在每次$diges時計算每個expression,這將會十分頻繁。在我們的範例裡filteredItems() 返回濾後的集合,如果這個evaluation 很慢,這將會立馬拖慢APP速度。

<li ng-repeat="item in filteredItems()"> <!--Bad idea, since very often evaluated.-->
<li ng-repeat="item in items"> <!--Way to go! -->


3. 用兩個List (一個給view 顯示,一個給data)

區分開顯示用的List跟資料用的List是實用的模式,這讓你可以偷偷的玩一些filters 和快取住集合再交給 view。下面是一個非常基本的範例,filteredLists 變數用來接住集合,applyFilter 方法負責處理mapping.

/* Controller */
// Basic list
var items = [{name:"John", active:true }, {name:"Adam"}, {name:"Chris"}, {name:"Heather"}];

// Init displayedList
$scope.displayedItems = items;

// Filter Cache
var filteredLists['active'] = $filter('filter')(items, {"active" : true});

// Apply the filter
$scope.applyFilter = function(type) {
    if (filteredLists.hasOwnProperty(type)){ // Check if filter is cached
        $scope.displayedItems = filteredLists[type];
    } else {
        /* Non cached filtering */
    }
}

// Reset filter
$scope.resetFilter = function() {
    $scope.displayedItems = items;
}

/* View */
//<button ng-click="applyFilter('active')">Select active</button>
//<ul><li ng-repeat="item in displayedItems">{{item.name}}<li></ul>


4.使用ng-if 代替 ng-show顯示額外的樣板

如果你的directives templates 需要秀一些額外的資訊,例如點list item 的後顯示,你最好使用 ng-if(1.1.5才有:P)ng-if 不給渲染(ng-show比較起來)。因此額外的DOM elements data-bindings 將會在需要的時候才計算。

<li ng-repeat="item in items">
    <p>{{ item.title }} </p>
    <button ng-click="item.showDetails = !item.showDetails">Show details</buttons>
    <div ng-if="item.showDetails">
        {{item.details}}
    </div>
</li>


5. 不要使用ng-mouseenter, ng-mouseleave, 之類的東東

內建的 ng-mouseenter 會造成閃爍,主要是因為瀏覽器低於每秒30幀,使用正港的jQuery來造動畫和滑過效果來解決這個問題吧。如果要注意最後的DOM改變狀態,記得要使用jQuery live() function包裝mouse 事件。

6. 調教filtering的小提示: 使用ng-show隱藏要排除的元素

對肥List 使用filters 同樣令人髮指。因為每個 filter 會對原本的 List 產生一個子集合,在許多情況下,當 data 沒有改變的情況下 filter 返回相同的東西,因此預先過濾 data 再套用到view 能省不少時間。
ng-repeat中套用filters的時候,每個filter回傳原集合的子集,AngularJS會在$destroy的時候移除排除掉的DOM元素,也會移除他們的 $scope。當filter 輸入改變,子集會跟著改變,然後元素必須被重新連結或者再次$destroyed。在大多數情況下,這是絕對沒問題的,但是當使用者頻繁的使用filters,或者List 非常肥,這會持續連結和銷毀DOM元素而影響效能。要加快過濾的話你可以使用ng-show ng-hide directives。在controller裡面計算和賦予旗標給每個需要過濾的項目,根據旗標觸發ng-show,因此他只會增加class elements 代替移除子集、$scope DOM
1.           一個觸發 ng-show 的方法是使用表達式語法,ng-show 的值是通過filter 語法計算出來。
可以參閱這兒 plunkr example

<input ng-model="query"></input>
<li ng-repeat="item in items" ng-show="([item.name] | filter:query).length">{{item.name}}</li>

2.           另一個方法是使用attribute 傳遞ng-show 然後用另一個子controller來計算,這是種比較複雜的方式,然而Ben Nadel有更妙的的建議在他的 blog post

7. 調教filtering的小提示: Debounce input

延續第六點,另一個解決連續filtering的方法是防止input抖動。舉例來說,如果使用者KEY了一些關鍵字,filter只需要在停止輸入之後再開始作用即可。
一個防抖動的好解法 - debounce service
套用在你的view controller 像這樣:

/* Controller */
// Watch the queryInput and debounce the filtering by 350 ms.
$scope.$watch('queryInput', function (newValue, oldValue) {
    if (newValue === oldValue) { return; }
    $debounce(applyQuery, 350);
});
var applyQuery = function () {
    $scope.filter.query = $scope.query;
};

/* View */
//<input ng-model="queryInput"/>
//<li ng-repeat= item in items | filter:filter.query>{{ item.title }} </li>

Further reading

§  1. Project organization with huge apps: http://briantford.com/blog/huuuuuge-angular-apps.html
§  2. Stackoverflow answer of Misko Hevery concerning angular data-binding performance: http://stackoverflow.com/questions/9682092/databinding-in-angularjs/9693933#9693933
§  3. Short article with different approaches increase ng-repeat performance:http://www.williambrownstreet.net/blog/?p=437
§  5. Good article on using the scope: http://thenittygritty.co/angularjs-pitfalls-using-scopes
§  6. AngularJS project for dynamic templates http://onehungrymind.com/angularjs-dynamic-templates
§  Rendering without data-binding: https://github.com/Pasvaz/bindonce