Underscore 是一個JavaScript實用庫,提供了相似Prototype.js (或 Ruby)的一些功能,可是沒有擴展任何JavaScript內置對象。javascript
它彌補了部分jQuery沒有實現的功能,同時又是Backbone.js必不可少的部分。css
Underscore提供了80多個函數,包括經常使用的: map, select, invoke — 固然還有更多專業的輔助函數,如:函數綁定, JavaScript模板功能, 強類型相等測試, 等等.html
在新的瀏覽器中, 有許多函數若是瀏覽器自己直接支持,將會採用原生的,如 forEach, map, reduce, filter,every, some 和 indexOf。java
咱們使用Underscore通常配合backbone,用得最多的仍是裏面的模板引擎,咱們今天就來一窺underscore的神祕面紗jquery
PS:老規矩,咱們仍是看着API 一個個來看源碼,若是確實有用的就寫demo,我估計只會多模板引擎一塊深刻研究git
本文參考了愚人碼頭翻譯的中文apigithub
模板引擎是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>
當時想法比較簡單,並且核心代碼也比較少,提供一個模板加一個二維數據便可
① 第一步首先是解析模板,將模板變爲字符串數組
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><script></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中undefined屬性。而且返回這個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>=><script>
他這裏又分了幾步走,第一步是將匹配字符串以前的字符串給保存起來,這裏對應的「<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的分析結束,我也爲本身兩年前的問題畫下圓滿的句號......
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相似又多了一個參數,這裏不細究
碼頭哥的解釋是,經過變換函數(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一樣實現了相關功能,其實就是用於處理數組的,根據處理結果返回新的數組,這個不予深究,想作精簡也能夠換下
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不一樣),第三個爲數組對象自己
② 若是傳入參數過少(可能不含有回調函數或者傳入失誤,這裏作容錯處理,不予關注)
③ 調用回調函數返回處理的數值,由此邏輯結束
這裏邏輯亂主要是傳入的參數過多,並且參數有所重複,因此我開始有點迷糊
遍歷數組,返回第一個回調檢測爲真的值
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有點相似的感受
遍歷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]
簡單來講就是過濾數組
遍歷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作篩選,自己意義不大
返回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]
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
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
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:搞了這麼久,這個東西比較靠譜,較實用
在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也許是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"]
返回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};
該方法源碼內部原理就是求數組最大值......
返回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
返回一個排序後的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]
把一個集合分組爲多個集合,經過 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"]}
給定一個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} }
排序一個列表組成一個組,而且返回各組中的對象的數量的計數。相似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}
返回一個隨機亂序的 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]
從 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]
把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:該方法比較實用
返回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前面提到了幾個函數如bind或者bindAll與zepto proxy相似,又有少量不一樣,咱們這裏不予關注,咱們來看看其餘的
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); });
這個函數有點意思,他在函數外層保留了一個閉包,而後,每次都會用到,這個知識點,咱們這裏來詳細說說
......
建立並返回一個像節流閥同樣的函數,當重複調用函數的時候,最多每隔 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);
來不起了,有點晚了,待續......