【初探Underscore】再說模版引擎

前言

Underscore 是一個JavaScript實用庫,提供了相似Prototype.js (或 Ruby)的一些功能,可是沒有擴展任何JavaScript內置對象。javascript

它彌補了部分jQuery沒有實現的功能,同時又是Backbone.js必不可少的部分。css

Underscore提供了80多個函數,包括經常使用的: mapselectinvoke — 固然還有更多專業的輔助函數,如:函數綁定, JavaScript模板功能, 強類型相等測試, 等等.html

在新的瀏覽器中, 有許多函數若是瀏覽器自己直接支持,將會採用原生的,如 forEachmapreducefilter,everysome 和 indexOfjava

咱們使用Underscore通常配合backbone,用得最多的仍是裏面的模板引擎,咱們今天就來一窺underscore的神祕面紗jquery

PS:老規矩,咱們仍是看着API 一個個來看源碼,若是確實有用的就寫demo,我估計只會多模板引擎一塊深刻研究git

本文參考了愚人碼頭翻譯的中文apigithub

template(templateString, [data], [settings])

模板引擎是underscore在咱們項目中用得最多的東西,誇張點來講,除了模板引擎,其它東西咱們都想將之移除正則表達式

由於zepto自己提供了不少不錯的方法,而移除其它無用方法後能夠節約13-5k的流量,是一個不錯的選擇json

模板引擎是實現數據與行爲分離的一大利器,其實大概兩年前我就幹過這個事情了api

兩年前的模板引擎

當時的想法很傻很天真,作了最簡單的對象替換,還恬不知恥的說本身用了模板預加載的模式,如今看來確實不錯。。。。適合我坑爹的個性

 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 2 <html xmlns="http://www.w3.org/1999/xhtml">
 3 <head>
 4     <title></title>
 5     <script src="../zepto/zepto.js" type="text/javascript"></script>
 6     <script type="text/javascript">
 7         $(document).ready(function () {
 8             var data = [];
 9             var i;
10             for (i = 0; i < 10; i++) {
11                 var temp = {};
12                 temp.name = "name_" + i.toString();
13                 temp.age = "age_" + i.toString();
14                 temp.home = "home_" + i.toString();
15                 temp.test = "test_" + i.toString();
16                 data.push(temp);
17             }
18             var template = "<div>{name}</div><div>{age}</div><div>{home}</div><div>{test}</div><hr/>"
19             var wl2 = $("#wl2");
20 
21             //如今作法
22             function update() {
23                 var now = new Date();
24                 var beginTime = now.getTime();
25 
26                 var templateObj = [];
27                 var reg = /\{[A-Za-z]*\}/;
28                 var para = reg.exec(template);
29                 var tempHtml = template;
30                 while (para && para.length > 0) {
31                     var len = para.index;
32                     var temp = {};
33                     temp.html = tempHtml.substr(0, len);
34                     temp.field = para[0].substr(1, para[0].length - 2); ;
35                     templateObj.push(temp);
36                     tempHtml = tempHtml.substr(len + para[0].length);
37                     para = reg.exec(tempHtml);
38                 }
39                 var end = {};
40                 end.html = tempHtml;
41                 templateObj.push(end);
42 
43                 var html = "";
44                 $.each(data, function (index, dataItem) {
45                     var tempHtm = "";
46                     $.each(templateObj, function (i, item) {
47                         if (item.field) {
48                             tempHtm = tempHtm + item.html + dataItem[item.field];
49                         } else {
50                             tempHtm = tempHtm + item.html;
51                         }
52                     });
53                     html += tempHtm;
54                 });
55                 wl2.append(html);
56             }
57             update();
58         });
59      
60     </script>
61 </head>
62 <body>
63     <div id="wl2">
64     </div>
65 </body>
66 </html>
View Code

當時想法比較簡單,並且核心代碼也比較少,提供一個模板加一個二維數據便可

① 第一步首先是解析模板,將模板變爲字符串數組

 1 var templateObj = [];
 2 var reg = /\{[A-Za-z]*\}/;
 3 var para = reg.exec(template);
 4 var tempHtml = template;
 5 while (para && para.length > 0) {
 6     var len = para.index;
 7     var temp = {};
 8     temp.html = tempHtml.substr(0, len);
 9     temp.field = para[0].substr(1, para[0].length - 2); ;
10     templateObj.push(temp);
11     tempHtml = tempHtml.substr(len + para[0].length);
12     para = reg.exec(tempHtml);
13 }
14 var end = {};
15 end.html = tempHtml;
16 templateObj.push(end);
<div>{name}</div><div>{age}</div><div>{home}</div><div>{test}</div><hr/>

上面一段模板解析結束後就變成了這個樣子了:

能夠看到已經將須要替換的標識給取了出來,接下來就進入第二步

② 如今就只須要將模板中的標識變爲data的數據便可

 1 var html = "";
 2 $.each(data, function (index, dataItem) {
 3     var tempHtm = "";
 4     $.each(templateObj, function (i, item) {
 5         if (item.field) {
 6             tempHtm = tempHtm + item.html + dataItem[item.field];
 7         } else {
 8             tempHtm = tempHtm + item.html;
 9         }
10     });
11     html += tempHtm;
12 });
13 wl2.append(html);

這個代碼自己不難,可是仍是有不少缺陷的,好比說模板中的js就沒法解析,當時爲了解決這個問題還用到了eval本身注入本身......

如今回過頭來,項目中的underscore模板用得很不錯,因此今天咱們首要來看看他的模板方法

初試模板

 1 var compiled = _.template("hello: <%= name %>");
 2 compiled({name: 'moe'});
 3 => "hello: moe"
 4 
 5 var list = "<% _.each(people, function(name) { %> <li><%= name %></li> <% }); %>";
 6 _.template(list, {people: ['moe', 'curly', 'larry']});
 7 => "<li>moe</li><li>curly</li><li>larry</li>"
 8 
 9 var template = _.template("<b><%- value %></b>");
10 template({value: '<script>'});
11 => "<b>&lt;script&gt;</b>"

這就是underscore的模板相關的語法,只要提供模板,而後template一下,再給他個data就好了

最帥的就是其中可使用js了,只不過js須要放到<%%>裏面,因而咱們來一點點看他的源碼

事實上underscore的機制就是執行<%%>中的邏輯便可,其它都是配套的,或者其它都是字符串,由咱們選擇是否打印與否

源碼分析

 1 _.templateSettings = {
 2     evaluate: /<%([\s\S]+?)%>/g,
 3     interpolate: /<%=([\s\S]+?)%>/g,
 4     escape: /<%-([\s\S]+?)%>/g
 5 };
 6 var noMatch = /(.)^/;
 7 
 8 var escapes = {
 9     "'": "'",
10     '\\': '\\',
11     '\r': 'r',
12     '\n': 'n',
13     '\t': 't',
14     '\u2028': 'u2028',
15     '\u2029': 'u2029'
16 };
17 
18 var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
19 
20 _.template = function (text, data, settings) {
21     var render;
22     settings = _.defaults({}, settings, _.templateSettings);
23 
24     var matcher = new RegExp([
25 (settings.escape || noMatch).source,
26 (settings.interpolate || noMatch).source,
27 (settings.evaluate || noMatch).source
28 ].join('|') + '|$', 'g');
29 
30     var index = 0;
31     var source = "__p+='";
32     text.replace(matcher, function (match, escape, interpolate, evaluate, offset) {
33         source += text.slice(index, offset)
34 .replace(escaper, function (match) { return '\\' + escapes[match]; });
35 
36         if (escape) {
37             source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
38         }
39         if (interpolate) {
40             source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
41         }
42         if (evaluate) {
43             source += "';\n" + evaluate + "\n__p+='";
44         }
45         index = offset + match.length;
46         return match;
47     });
48     source += "';\n";
49 
50     if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
51 
52     source = "var __t,__p='',__j=Array.prototype.join," +
53 "print=function(){__p+=__j.call(arguments,'');};\n" +
54 source + "return __p;\n";
55 
56     try {
57         render = new Function(settings.variable || 'obj', '_', source);
58     } catch (e) {
59         e.source = source;
60         throw e;
61     }
62 
63     if (data) return render(data, _);
64     var template = function (data) {
65         return render.call(this, data, _);
66     };
67 
68     template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
69 
70     return template;
71 };

不得不說這段代碼很經典啊,兩年前我絕對不會想到如此重要的模板功能竟會只有這點代碼......事實上看源碼以前也不知道

① 參數說明

template自己是一個方法,能夠提供三個參數,第一個爲模板文本,第二個爲對應模板數據,第三個爲基本配置信息,通常不予傳遞

若是咱們傳遞了data的話,會直接返回解析後的html字符串,沒有傳遞的話就會返回一個編譯過的模板

這裏用到了defaults方法,咱們來看看他是幹什麼的:

_.defaults(object, *defaults)

defaults對象填充objectundefined屬性。而且返回這個object。一旦這個屬性被填充,再使用defaults方法將不會有任何效果。

var iceCream = {flavor: "chocolate"};
_.defaults(iceCream, {flavor: "vanilla", sprinkles: "lots"});
=> {flavor: "chocolate", sprinkles: "lots"}

② 正則分析

這裏用到的幾個正則都是匹配所有字符,而且不會保存分組信息,首先看一個簡單的

_.templateSettings = {
evaluate    : /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g,
escape      : /<%-([\s\S]+?)%>/g
};

以上三個最後會被造成一個正則字符串組:

1 var matcher = new RegExp([
2     (settings.escape || noMatch).source,
3     (settings.interpolate || noMatch).source,
4     (settings.evaluate || noMatch).source
5 ].join('|') + '|$', 'g');
6 //=>/<%-([\s\S]+?)%>|<%=([\s\S]+?)%>|<%([\s\S]+?)%>|$/g

這個正則會由左向右的順序匹配,若是成功了就不找後面的了,而後下面開始解析模板

③ 模板解析階段

咱們先無論他的邏輯,咱們用這個正則來匹配下咱們的代碼看看有些什麼東西

matcher.exec(text)
["<%-name %>", "name ", undefined, undefined]

以上爲他輸出的結果,第一個是匹配到的對象,第一個參數爲匹配到的字符串,第二個爲括號中的字符串,如今咱們回到程序

程序中用了replace方法,該方法可以使用正則表達式,第二個參數爲一個函數,這裏就須要各位對replace方法比較熟悉了:

function函數具備幾個參數:

第一個參數爲每次匹配的全文本($&)。
中間參數爲子表達式匹配字符串,個數不限,也就是括號中的東西
倒數第二個參數爲匹配文本字符串的匹配下標位置。
最後一個參數表示字符串自己。

好比我這裏第一次的匹配就應該是這個樣子:

["<%-name %>", "name ", undefined, undefined, 5, 
"<div><%-name %>
{name}</div><div><%=age %>{age}</div><div><%=home %>{home}</div><div>{test}</div>,
<%if(name == "name_0") %>,我是葉小釵,<%else %>,我是素還真"]

咱們來看看他的replace:

 1 text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
 2     source += text.slice(index, offset)
 3     .replace(escaper, function(match) { return '\\' + escapes[match]; });
 4 
 5     if (escape) {
 6     source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
 7     }
 8     if (interpolate) {
 9     source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
10     }
11     if (evaluate) {
12     source += "';\n" + evaluate + "\n__p+='";
13     }
14     index = offset + match.length;
15     return match;
16 });

他正則有三個括號,因此他參數也相對而言有三個對應參數了,只不事後面的爲空罷了,可是隨着匹配類型不一樣,爲空的順序會不一樣

PS:各位注意這裏的位置參數offset,後面有大用的,這裏與我原來的作法相似

好了,咱們這裏來看他是如何一步步解析的,其實這裏有個原則就是,若是能有js解析的必定仍是用js簡單,好比咱們原來字符串轉換json使用eval

④ 解析細節

按照咱們給出的模板,第一次應該被解析到<%-name%>,這個東西插入的html會被轉義好比

//<script>=>&lt;script&gt;

他這裏又分了幾步走,第一步是將匹配字符串以前的字符串給保存起來,這裏對應的「<div>」,

text.slice(index, offset)//text.slice(0, 5)=><div>

要注意的是,他這裏作了字符串轉義處理

1 source += text.slice(index, offset)
2 .replace(escaper, function(match) { return '\\' + escapes[match]; });
3 //好比字符串中具備\n會被換成\\n,裏面的引號也會作處理
1 if (escape) {
2     source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
3 }
4 /*
5 "__p+='<div>'+
6 ((__t=(name ))==null?'':_.escape(__t))+
7 '"
8 *

第一步處理後,咱們的字符串變成了這樣,而後咱們的index固然會後移:

index = offset + match.length;

其中<%-str%>與<%=str%>都比較簡單,特殊的狀況發生在第三個地方

1 if (evaluate) {
2     source += "';\n" + evaluate + "\n__p+='";
3 }

各位看到了,在模板中出現js表達式時候(非-、=狀況),時候後面多了一個__p+=,這個東西學問就大了,由於這裏字符串會從新多出一行

"__p+='<div>'+
((__t=(name ))==null?'':_.escape(__t))+
'{name}</div><div>'+
((__t=(age ))==null?'':__t)+
'{age}</div><div>'+
((__t=(home ))==null?'':__t)+
'{home}</div><div>{test}</div>,';
if(name == "name_0") 
__p+='"

這個就比較經典了,若是使用js 的eval解析的話,前面會做爲一個語句,後面會做爲一個新的語句,整個模板解析結束即是這個東西了:

"__p+='<div>'+
((__t=(name ))==null?'':_.escape(__t))+
'{name}</div><div>'+
((__t=(age ))==null?'':__t)+
'{age}</div><div>'+
((__t=(home ))==null?'':__t)+
'{home}</div><div>{test}</div>,';
if(name == "name_0") 
__p+=',我是葉小釵,';
else 
__p+=',我是素還真';
"

而對javascript function方法比較熟悉的朋友會知道,javascript其實自己就是使用Function構造函數搞出來的,好比說:

1 function test() {
2     alert('葉小釵');
3 }
4 var test1 = new Function('alert(\'葉小釵\')');
5 test();
6 test1();

test1與test2其實是等價的,而上面模板解析出來的字符串,不難聯想會被咱們封裝爲一個函數

PS:最簡單的解析果真是javascript的本身的解析!

render = new Function(settings.variable || 'obj', '_', source);

這裏underscore堅決果斷的將解析結束的模板封裝爲了一個函數,render最後造成的函數體以下:

 1 function anonymous(obj, _ /**/) {
 2     var __t, __p = '', __j = Array.prototype.join, print = function () { __p += __j.call(arguments, ''); };
 3     with (obj || {}) {
 4         __p += '<div>' +
 5         ((__t = (name)) == null ? '' : _.escape(__t)) +
 6         '{name}</div><div>' +
 7         ((__t = (age)) == null ? '' : __t) +
 8         '{age}</div><div>' +
 9         ((__t = (home)) == null ? '' : __t) +
10         '{home}</div><div>{test}</div>,';
11         if (name == "name_0")
12             __p += ',我是葉小釵,';
13         else
14             __p += ',我是素還真';
15     }
16     return __p;
17 }

不得不說,這個template代碼寫的真好......,這裏還有一點須要注意的就是這裏with語句的做用於問題,沒有with的話可能就須要作其它處理了,也許是call

函數封裝結束只等待調用便可:

var template = function(data) {
    return render.call(this, data, _);
};

至此,咱們隊template的分析結束,我也爲本身兩年前的問題畫下圓滿的句號...... 

Collections

each(list, iterator, [context])

underscore也對each作了實現,若是引用了zepto/jquery包的話能夠更加精簡:

 1 var each = _.each = _.forEach = function(obj, iterator, context) {
 2   if (obj == null) return;
 3   if (nativeForEach && obj.forEach === nativeForEach) {
 4     obj.forEach(iterator, context);
 5   } else if (obj.length === +obj.length) {
 6     for (var i = 0, length = obj.length; i < length; i++) {
 7       if (iterator.call(context, obj[i], i, obj) === breaker) return;
 8     }
 9   } else {
10     var keys = _.keys(obj);
11     for (var i = 0, length = keys.length; i < length; i++) {
12       if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
13     }
14   }
15 };
_.each([1, 2, 3], alert);
=> alerts each number in turn...
_.each({one: 1, two: 2, three: 3}, alert);
=> alerts each number value in turn...

因爲ECMAScript 5中的array提供了新的ForEach方法,這裏若是支持最新的方法,使用便可

第一個參數爲對象集合,第二個爲回調函數,第三個爲回調函數執行做用域,此方法與zepto相似又多了一個參數,這裏不細究

map(list, iterator, [context])

碼頭哥的解釋是,經過變換函數(iterator替代器、回調函數),將list集合值映射到新數組

1 _.map = _.collect = function(obj, iterator, context) {
2   var results = [];
3   if (obj == null) return results;
4   if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
5   each(obj, function(value, index, list) {
6     results.push(iterator.call(context, value, index, list));
7   });
8   return results;
9 };
_.map([1, 2, 3], function(num){ return num * 3; });
=> [3, 6, 9]
_.map({one: 1, two: 2, three: 3}, function(num, key){ return num * 3; });
=> [3, 6, 9]

ECMAScript5 array一樣實現了相關功能,其實就是用於處理數組的,根據處理結果返回新的數組,這個不予深究,想作精簡也能夠換下

reduce(list, iterator, memo, [context]) 

 1 _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
 2   var initial = arguments.length > 2;
 3   if (obj == null) obj = [];
 4   if (nativeReduce && obj.reduce === nativeReduce) {
 5     if (context) iterator = _.bind(iterator, context);
 6     return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
 7   }
 8   each(obj, function(value, index, list) {
 9     if (!initial) {
10       memo = value;
11       initial = true;
12     } else {
13       memo = iterator.call(context, memo, value, index, list);
14     }
15   });
16   if (!initial) throw new TypeError(reduceError);
17   return memo;
18 };
var sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, 0);
=> 6

該函數將list中的集合歸結爲單獨一個值,每一個值會被回調函數處理

這個方法有點迷糊,咱們稍微來理一理(這裏忽略系統採用原生ECMAScript 5 的作法):

① each方法中的回調第一個參數爲當前值,第二個爲index(這點好像與zepto不一樣),第三個爲數組對象自己

② 若是傳入參數過少(可能不含有回調函數或者傳入失誤,這裏作容錯處理,不予關注)

③ 調用回調函數返回處理的數值,由此邏輯結束

這裏邏輯亂主要是傳入的參數過多,並且參數有所重複,因此我開始有點迷糊

find(list, iterator, [context])

遍歷數組,返回第一個回調檢測爲真的值

 1 _.find = _.detect = function(obj, iterator, context) {
 2   var result;
 3   any(obj, function(value, index, list) {
 4     if (iterator.call(context, value, index, list)) {
 5       result = value;
 6       return true;
 7     }
 8   });
 9   return result;
10 };
var even = _.find([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> 2

比較簡單,不予理睬,感受何map有點相似的感受

filter(list, iterator, [context])

遍歷list,返回回調函數返回true的值

1 _.filter = _.select = function(obj, iterator, context) {
2   var results = [];
3   if (obj == null) return results;
4   if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
5   each(obj, function(value, index, list) {
6     if (iterator.call(context, value, index, list)) results.push(value);
7   });
8   return results;
9 };
var evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> [2, 4, 6]

簡單來講就是過濾數組

where(list, properties)

遍歷list,返回一個數組,這個數組包含對象所列出屬性全部的值

1 _.where = function(obj, attrs, first) {
2   if (_.isEmpty(attrs)) return first ? void 0 : [];
3   return _[first ? 'find' : 'filter'](obj, function(value) {
4     for (var key in attrs) {
5       if (attrs[key] !== value[key]) return false;
6     }
7     return true;
8   });
9 };
_.where(listOfPlays, {author: "Shakespeare", year: 1611});
=> [{title: "Cymbeline", author: "Shakespeare", year: 1611},
    {title: "The Tempest", author: "Shakespeare", year: 1611}]

這個函數會調用find或者filter作篩選,自己意義不大

reject(list, iterator, [context])

返回list中沒有經過iterator真值檢測的元素集合,與filter相反。

1 _.reject = function(obj, iterator, context) {
2   return _.filter(obj, function(value, index, list) {
3     return !iterator.call(context, value, index, list);
4   }, context);
5 };
var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> [1, 3, 5]

every(list, [iterator], [context])

 1 _.every = _.all = function(obj, iterator, context) {
 2   iterator || (iterator = _.identity);
 3   var result = true;
 4   if (obj == null) return result;
 5   if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
 6   each(obj, function(value, index, list) {
 7     if (!(result = result && iterator.call(context, value, index, list))) return breaker;
 8   });
 9   return !!result;
10 };
_.every([true, 1, null, 'yes'], _.identity);
=> false

some(list, [iterator], [context])

 1 var any = _.some = _.any = function(obj, iterator, context) {
 2   iterator || (iterator = _.identity);
 3   var result = false;
 4   if (obj == null) return result;
 5   if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
 6   each(obj, function(value, index, list) {
 7     if (result || (result = iterator.call(context, value, index, list))) return breaker;
 8   });
 9   return !!result;
10 };
_.some([null, 0, 'yes', false]);
=> true

若是list中有任何一個元素經過 iterator 的真值檢測就返回true。一旦找到了符合條件的元素, 就直接中斷對list的遍歷. 若是存在原生的some方法,就使用原生的some

contains(list, value)

1 _.contains = _.include = function(obj, target) {
2   if (obj == null) return false;
3   if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
4   return any(obj, function(value) {
5     return value === target;
6   });
7 };
_.contains([1, 2, 3], 3);
=> true

若是list包含指定的value則返回true(愚人碼頭注:使用===檢測)。若是list 是數組,內部使用indexOf判斷。

PS:搞了這麼久,這個東西比較靠譜,較實用

invoke(list, methodName, [*arguments])

list的每一個元素上執行methodName方法。 任何傳遞給invoke的額外參數,invoke都會在調用methodName方法的時候傳遞給它。

1 _.invoke = function(obj, method) {
2   var args = slice.call(arguments, 2);
3   var isFunc = _.isFunction(method);
4   return _.map(obj, function(value) {
5     return (isFunc ? method : value[method]).apply(value, args);
6   });
7 };
_.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
=> [[1, 5, 7], [1, 2, 3]]

pluck(list, propertyName)

pluck也許是map最常使用的用例模型的版本,即萃取對象數組中某屬性值,返回一個數組。

1 _.pluck = function(obj, key) {
2   return _.map(obj, function(value){ return value[key]; });
3 };
var stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}];
_.pluck(stooges, 'name');
=> ["moe", "larry", "curly"]

max(list, [iterator], [context])

返回list中的最大值。若是傳遞iterator參數,iterator將做爲list排序的依據。

 1 _.max = function(obj, iterator, context) {
 2   if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
 3     return Math.max.apply(Math, obj);
 4   }
 5   if (!iterator && _.isEmpty(obj)) return -Infinity;
 6   var result = {computed : -Infinity, value: -Infinity};
 7   each(obj, function(value, index, list) {
 8     var computed = iterator ? iterator.call(context, value, index, list) : value;
 9     computed > result.computed && (result = {value : value, computed : computed});
10   });
11   return result.value;
12 };
var stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}];
_.max(stooges, function(stooge){ return stooge.age; });
=> {name: 'curly', age: 60};

該方法源碼內部原理就是求數組最大值......

min(list, [iterator], [context])

返回list中的最小值。若是傳遞iterator參數,iterator將做爲list排序的依據。

 1 _.min = function(obj, iterator, context) {
 2   if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
 3     return Math.min.apply(Math, obj);
 4   }
 5   if (!iterator && _.isEmpty(obj)) return Infinity;
 6   var result = {computed : Infinity, value: Infinity};
 7   each(obj, function(value, index, list) {
 8     var computed = iterator ? iterator.call(context, value, index, list) : value;
 9     computed < result.computed && (result = {value : value, computed : computed});
10   });
11   return result.value;
12 };
var numbers = [10, 5, 100, 2, 1000];
_.min(numbers);
=> 2

sortBy(list, iterator, [context])

返回一個排序後的list拷貝副本。若是有iterator參數,iterator將做爲list排序的依據。迭代器也能夠是字符串的屬性的名稱進行排序的(好比 length)。

 1 _.sortBy = function(obj, value, context) {
 2   var iterator = lookupIterator(value);
 3   return _.pluck(_.map(obj, function(value, index, list) {
 4     return {
 5       value: value,
 6       index: index,
 7       criteria: iterator.call(context, value, index, list)
 8     };
 9   }).sort(function(left, right) {
10     var a = left.criteria;
11     var b = right.criteria;
12     if (a !== b) {
13       if (a > b || a === void 0) return 1;
14       if (a < b || b === void 0) return -1;
15     }
16     return left.index - right.index;
17   }), 'value');
18 };
_.sortBy([1, 2, 3, 4, 5, 6], function(num){ return Math.sin(num); });
=> [5, 4, 6, 3, 1, 2]

groupBy(list, iterator, [context])

把一個集合分組爲多個集合,經過 iterator 返回的結果進行分組. 若是 iterator 是一個字符串而不是函數, 那麼將使用 iterator 做爲各元素的屬性名來對比進行分組.

 1 var group = function(behavior) {
 2   return function(obj, value, context) {
 3     var result = {};
 4     var iterator = value == null ? _.identity : lookupIterator(value);
 5     each(obj, function(value, index) {
 6       var key = iterator.call(context, value, index, obj);
 7       behavior(result, key, value);
 8     });
 9     return result;
10   };
11 };
12 _.groupBy = group(function(result, key, value) {
13   (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
14 });
_.groupBy([1.3, 2.1, 2.4], function(num){ return Math.floor(num); });
=> {1: [1.3], 2: [2.1, 2.4]}

_.groupBy(['one', 'two', 'three'], 'length');
=> {3: ["one", "two"], 5: ["three"]}

indexBy(list, iterator, [context])

給定一個list,和 一個用來返回一個在列表中的每一個元素鍵 的iterator 函數(或屬性名), 返回一個每一項索引的對象。和groupBy很是像,可是當你知道你的鍵是惟一的時候可使用indexBy 。

1 _.indexBy = group(function(result, key, value) {
2   result[key] = value;
3 });
var stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}];
_.indexBy(stooges, 'age');
=> {
  "40": {name: 'moe', age: 40},
  "50": {name: 'larry', age: 50},
  "60": {name: 'curly', age: 60}
}

countBy(list, iterator, [context])

排序一個列表組成一個組,而且返回各組中的對象的數量的計數。相似groupBy,可是不是返回列表的值,而是返回在該組中值的數目。

1 _.countBy = group(function(result, key) {
2   _.has(result, key) ? result[key]++ : result[key] = 1;
3 });
_.countBy([1, 2, 3, 4, 5], function(num) {
  return num % 2 == 0 ? 'even': 'odd';
});
=> {odd: 3, even: 2}

shuffle(list)

返回一個隨機亂序的 list 副本, 使用 Fisher-Yates shuffle 來進行隨機亂序.

 1 _.shuffle = function(obj) {
 2   var rand;
 3   var index = 0;
 4   var shuffled = [];
 5   each(obj, function(value) {
 6     rand = _.random(index++);
 7     shuffled[index - 1] = shuffled[rand];
 8     shuffled[rand] = value;
 9   });
10   return shuffled;
11 };
_.shuffle([1, 2, 3, 4, 5, 6]);
=> [4, 1, 6, 3, 5, 2]

sample(list, [n])

從 list中產生一個隨機樣本。傳遞一個數字表示從list中返回n個隨機元素。不然將返回一個單一的隨機項。

1 _.sample = function(obj, n, guard) {
2   if (arguments.length < 2 || guard) {
3     return obj[_.random(obj.length - 1)];
4   }
5   return _.shuffle(obj).slice(0, Math.max(0, n));
6 };
_.sample([1, 2, 3, 4, 5, 6]);
=> 4

_.sample([1, 2, 3, 4, 5, 6], 3);
=> [1, 6, 2]

toArray(list)

list(任何能夠迭代的對象)轉換成一個數組,在轉換 arguments 對象時很是有用。

1 _.toArray = function(obj) {
2   if (!obj) return [];
3   if (_.isArray(obj)) return slice.call(obj);
4   if (obj.length === +obj.length) return _.map(obj, _.identity);
5   return _.values(obj);
6 };
(function(){ return _.toArray(arguments).slice(1); })(1, 2, 3, 4);
=> [2, 3, 4]

PS:該方法比較實用

size(list)

返回list的長度。

1 _.size = function(obj) {
2   if (obj == null) return 0;
3   return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
4 };
_.size({one: 1, two: 2, three: 3});
=> 3

該方法無用

階段總結

collection與array我這裏都無論他了,感受意義不大,有須要的朋友本身去看API和源碼吧

http://www.css88.com/doc/underscore/#first

下面就挑一點我感興趣的來講吧

Functions

functions前面提到了幾個函數如bind或者bindAll與zepto proxy相似,又有少量不一樣,咱們這裏不予關注,咱們來看看其餘的

memoize(function, [hashFunction])

Memoizes方法能夠緩存某函數的計算結果。對於耗時較長的計算是頗有幫助的。若是傳遞了 hashFunction 參數,就用 hashFunction 的返回值做爲key存儲函數的計算結果。 hashFunction 默認使用function的第一個參數做爲key

1 _.memoize = function(func, hasher) {
2   var memo = {};
3   hasher || (hasher = _.identity);
4   return function() {
5     var key = hasher.apply(this, arguments);
6     return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
7   };
8 };
var fibonacci = _.memoize(function(n) {
  return n < 2 ? n: fibonacci(n - 1) + fibonacci(n - 2);
});

這個函數有點意思,他在函數外層保留了一個閉包,而後,每次都會用到,這個知識點,咱們這裏來詳細說說

......

 

throttle(function, wait, [options])

建立並返回一個像節流閥同樣的函數,當重複調用函數的時候,最多每隔 wait毫秒調用一次該函數。 對於想控制一些觸發頻率較高的事件有幫助。(愚人碼頭注:詳見:javascript函數的throttle和debounce

默認狀況下,throttle將在你調用的第一時間儘快執行這個function,而且,若是你在wait週期內調用任意次數的函數,都將盡快的被覆蓋。若是你想禁用第一次首先執行的話,傳遞{leading: false},還有若是你想禁用最後一次執行的話,傳遞{trailing: false}

 1 _.throttle = function(func, wait, options) {
 2   var context, args, result;
 3   var timeout = null;
 4   var previous = 0;
 5   options || (options = {});
 6   var later = function() {
 7     previous = options.leading === false ? 0 : new Date;
 8     timeout = null;
 9     result = func.apply(context, args);
10   };
11   return function() {
12     var now = new Date;
13     if (!previous && options.leading === false) previous = now;
14     var remaining = wait - (now - previous);
15     context = this;
16     args = arguments;
17     if (remaining <= 0) {
18       clearTimeout(timeout);
19       timeout = null;
20       previous = now;
21       result = func.apply(context, args);
22     } else if (!timeout && options.trailing !== false) {
23       timeout = setTimeout(later, remaining);
24     }
25     return result;
26   };
27 };
var throttled = _.throttle(updatePosition, 100);
$(window).scroll(throttled);

 

 

 結語

來不起了,有點晚了,待續......

相關文章
相關標籤/搜索