【AngularJS的概念及其單元測試】之過濾器

1、過濾器的基本概念

AngularJS的過濾器用於處理數據,以及將數據格式化後呈現給用戶。通常用於HTML文檔的表達式中,或直接用於控制器與服務中的數據。使用過濾器的好處是能夠將常見的格式化操做和轉換邏輯封裝在單獨的可重用組件中。html

在HTML中使用過濾器的語法是管道式語法(pipe syntax):{ {expression | filter} }
也能夠鏈式使用多個過濾器,將過濾的結果傳遞給下一個過濾器:{ {expression | filter1 | filter2} }
如將obj.name變量的值做如下處理,先轉換成小寫,而且只顯示前五個字符:java

{ {obj.name | lowercase | limitTo: 5} }

其中,obj.name的值是不會改變的。express

經常使用的內置過濾器

1. currency

函數源碼:json

currencyFilter.$inject = ['$locale'];
function currencyFilter($locale) {
    var formats = $locale.NUMBER_FORMATS;
    return function(amount, currencySymbol){
        if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM;     // 其中默認的formats.CURRENCY_SYM 爲 '$'
        return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2).replace(/\u00A4/g, currencySymbol);
    };
}

第二個參數currencySymbol是可選的,表明貨幣符號,沒有指定則使用默認的'$'。數組

2. number

函數源碼:瀏覽器

numberFilter.$inject = ['$locale'];
function numberFilter($locale) {
    var formats = $locale.NUMBER_FORMATS;
    return function(number, fractionSize) {
        return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,fractionSize);
    };
}

經過添加分割符來將數字轉換成易讀的格式。也可接受一個參數fractionSize來決定保留小數點後幾位。(fraction,分數)數據結構

3. lowercase/uppercase

函數源碼:異步

function valueFn(value) {return function() {return value;};}
var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;};
var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;};

var lowercaseFilter = valueFn(lowercase);
var uppercaseFilter = valueFn(uppercase);

最終的實際結果是 isString(string) ? string.toLowerCase() : string;
其中isString是AngularJS自定義的一個公共API,源碼很簡單,只是作個類型判斷:ide

function isString(value){return typeof value === 'string';}

4. json

函數源碼:

function jsonFilter() {
    return function(object) {
        return toJson(object, true);
    };
}

其中,toJson也是AngularJS自定義的一個公共API,官方介紹是:

Serializes input into a JSON-formatted string. Properties with leading $ characters will be stripped since angular uses this notation internally.
將輸入序列化爲JSON格式的字符串。(將對象解析成json)由於Angular內部使用了\(符號,因此以\)開頭的屬性將會被剝離掉(即不保留這個屬性)。

實現源碼:

function toJson(obj, pretty) {
    if (typeof obj === 'undefined') return undefined;
    return JSON.stringify(obj, toJsonReplacer, pretty ? '  ' : null);
}

使用方式:

angular.toJson(obj, [pretty]);

參數:
objObject | Array | Date | String | Number
pretty(optional) :Boolean
-- If set to true, the JSON output will contain newlines and whitespace.
若是將第二個參數pretty設置爲true,那麼就會保留對象中應有的換行和空格(不必定按照原始對象書寫的格式)。

var obj = {
    $name: 'lau',
    age: 18
};

angular.toJson(obj);    // '{"age": 18}'    不保留以$開頭的屬
angular.toJson(obj, true);
// 結果以下
'{
    "age": 18
}'

5. date

日期格式過濾器是高度自定義的,是一個功能很是強大的過濾器,能夠接收一個日期對象或表明日期的長整型,而後將數據轉換成可讀的字符串顯示在視圖中。
函數接口:

function(date, format) {}

6. limitTo

函數接口:

function limitToFilter(){
    return function(input, limit) {}
}

接受字符串或數組,而後根據開始索引或結束索引返回輸入值的子集。
當接受的參數是一個數字時,則當輸入值是數組時,它返回相應的元素個數,當輸入值是字符串,返回相應的字符個數。若是參數是負數,那麼會從後往前數。

{ {'greeting' | limitTo: 3} }   // 'gre'
{ {[1, 2, 3, 4, 5] | 3} }       // [1, 2, 3]

7. orderBy

函數接口:

function orderByFilter($parse){
    return function(array, sortPredicate, reverseOrder) {}
}

這是一個比較複雜的過濾器,能夠根據事先定義好的比較大小表達式 [或一組表達式] 將數組進行排序。第二個參數是一個可選的布爾型,表示數組是否須要進行反序。

<ul>
    <li ng-repeat="note in notes | orderBy: sortOrder">
        { {note.name} } - { {note.location} }
    </li>
</ul>
$scope.notes = [
        {name: 'zhangjiang', location: 'shanghai'},
        {name: 'huangshan', location: 'anhui'},
        {name: 'dali', location: 'yunnan'},
        {name: 'dali', location: 'china'},
        {name: 'dali', location: 'earth'}
    ];
$scope.sortOrder = ['+name', '-location'];

最簡單的比較大小表達式是一個字符串(它是一個對象的鍵),根據這個字段進行排名。也能夠在字段名以前添加+-符號表示按照升序仍是降序排列。
還能夠是函數,根據函數的返回值斷定比較結果(經過簡單的<>=進行比較)

8. filter

函數接口:

function filterFilter() {
    return function(array, expression, comparator) {}
}

filter是Angular中最複雜的過濾器。經過斷言或函數來決定數組中哪些元素是符合要求的,將添加到結果集中,而哪些是將會被過濾掉的 —— 一般與ng-repeat配合過濾過濾數組元素。

過濾表達式:

  • string
    AngularJS會掃描數組中的每一個對象的鍵值,若是其中包含指定的字符串,則這個元素就符合要求。若是要取相反的結果集,能夠再表達式前加 前綴。

  • object
    AngularJS會掃描數組中的每一個對象的鍵值,對於好比{size: 'M'},AngularJS會查找每一個對象中是否包含了size這個鍵名,而它的值中是否包含了M這個字符(不必定正好是M)。

  • function
    使用函數制定過濾規則是功能最強大、最靈活的選項。function過濾器具備高度的擴展性,可以根據業務邏輯處理許多複雜的狀況。
    數組中的每個元素都會調用一次這個過濾函數,返回false的結果即該元素將會被過濾掉。

<button ng-click="currentFilter = 'string'">Filter with String</button>
<button ng-click="currentFilter = 'object'">Filter with Object</button>
<button ng-click="currentFilter = 'function'">Filter with Function</button>
<ul>
    <li ng-repeat="note in notes | filter: filterOptions[currentFilter]">
        { {note.name} } - { {note.location} }
    </li>
</ul>
$scope.notes = [
    {name: 'zhangjiang', location: 'shanghai'},
    {name: 'huangshan', location: 'anhui'},
    {name: 'dali', location: 'yunnan'},
    {name: 'dali', location: 'china'},
    {name: 'dali', location: 'earth'}
];

$scope.currentFilter = 'string';
$scope.filterOptions = {
    'string': 'zhang',
    'object': {name: 'dali', location: 'n'},
    'function': function(note) {
        return note.name === 'dali';
    }
};

不一樣按鈕的顯示結果是:

您的瀏覽器不支持 iframe 標籤。


在控制器和服務中使用過濾器

AngularJS可以經過依賴注入在任何地方使用過濾器。這樣,咱們不須要訪問DOM節點和UI就能夠根據業務邏輯需求在Javascript代碼中使用過濾器了。

使用方式:任何過濾器(不管是內置的仍是自定義的)都來能夠經過在名稱中添加"Filter"後綴並請求注入到控制器或服務中,以下:

angular.module('myModule', [])
.controller('myController', ['filterFilter', function(filterFilter){
    this.filterArray = filterFilter(this.notes, 'ch');
}]);

參數:

  1. 第一個參數是須要過濾的值。

  2. 其他參數是過濾器所須要的參數,對於某些過濾器來講是可選的。參數的前後順序能夠參考過濾器文檔。
    通用函數接口:
function(startTime, arg1, arg2, arg3){}

HTML文本上使用時:{ {startTime | timeAgo: arg1 : arg2 : arg3} }

  1. 過濾器的返回值是咱們所須要的最終輸出結果。

關於過濾器的幾個要點

1. 視圖中的過濾器在每一個digest週期都會執行

這是最重要的一點,咱們在視圖中直接使用過濾器,那麼每次在digest週期都會從新計算值,這樣,隨着數據的增加,咱們必需要當心UI中的過濾器可能帶來的額外計算致使性能的損失。

2. 過濾器必須快如閃電

正是因爲上面的狀況,因此在理想狀況下,過濾函數要可以在1ms內執行數次,因此一些比較耗時的操做(如DOM節點操做,異步調用等)就不該該出如今過濾器中。

3. 將過濾器置入控制器和服務中以得到最佳性能

若是須要處理大量的複雜數組和數據結構,同時又想利用過濾器的模塊化和重用性,那麼能夠考慮在控制器或服務中直接使用過濾器。在數據沒有變化的狀況下,就不會從新計算,這樣能夠節省CPU週期。

2、過濾器的單元測試

須要測試的過濾器timeAgo

這個過濾器的功能是根據當前時間來判斷要顯示的事件是多久之前,而且根據一個可選參數optShowSecondsMessage來判斷是否包含顯示seconds ago,仍是隻包含顯示minutes ago以上的級別。

你的瀏覽器不支持iframe

過濾器的單元測試比較簡單,測試流程與控制器徹底相同。一樣須要將過濾器注入單元測試,而後在過濾器中直接調用它們,傳入各類不一樣的參數並觀察運行結果是否在全部的分支條件下都正確。

describe('timeAgo Filter', function(){
    beforeEach(module('filtersApp'));

    var filter;
    beforeEach(inject(function(timeAgoFilter){
        filter = timeAgoFilter;
    }));

    it('should respond based on timestamp', function(){
        // new Date().getTime()函數每次返回的結果都不同,致使沒法肯定ut的結果。理想狀況下,咱們須要在timeAgo過濾器中注入dateProvider.
        // 這裏使用簡潔的作法,咱們須要假設測試在幾ms內就完成
        var currentTime = new Date.getTime();

        currentTime -= 10000;   // 10ms之前
        expect(filter(currentTime).toEqual('seconds ago'));

        var fewMinutesAgo = current - 1000*60*2;    // 2分鐘之前
        expect(filter(fewMinutesAgo).toEqual('minutes ago'));

        var fewHoursAgo = current - 1000*60*60*2;   // 2小時之前
        expect(filter(fewHoursAgo).toEqual('hours ago'));

        var fewMonthAgo = current - 1000*60*60*30*2;    // 2月之前
        expect(filter(fewMonthAgo).toEqual('months ago'));
    });

    // 上面的測試用例中沒有測試可選參數,下面須要進行額外的測試
    it('should respond based on timestamp & optional arguments', function(){
        var currentTime = new Date.getTime();

        currentTime -= 10000;   // 10ms之前
        expect(filter(currentTime, false).toEqual('minutes ago'));

        var fewMinutesAgo = current - 1000*60*2;    // 2分鐘之前
        expect(filter(fewMinutesAgo, false).toEqual('minutes ago'));

        var fewHoursAgo = current - 1000*60*60*2;   // 2小時之前
        expect(filter(fewHoursAgo, false).toEqual('hours ago'));

        var fewMonthAgo = current - 1000*60*60*30*2;    // 2月之前
        expect(filter(fewMonthAgo, fasle).toEqual('months ago'));
    });
});

3、參考

AngularJS:Up & Running (AngularJS即學即用)

相關文章
相關標籤/搜索