Underscore一個JavaScript實用庫,提供了一整套函數式編程的實用功能,可是沒有擴展任何JavaScript內置對象。javascript
它彌補了部分jQuery沒有實現的功能,同時又是Backbone.js必不可少的部分。css
Underscore還能夠被使用在Node.js運行環境html
API地址:http://www.css88.com/doc/underscore/java
下載: git clone https://github.com/jashkenas/underscorejquery
Collections是集合,指那些由單個元素組成,可以使用下標操做的數據類型的統稱,好比Array,Object,String等,從underscore的源碼來看,這一類函數只用到最基本的[]運算符和for循環,以及對由此構成的新方法的組合運用。這一類方法有:each、map、reduce、find等。git
相對Collections的鳥槍而言,Arrays有了小鋼炮,可以使用Array內置的方法,好比slice。underscore的Arrays方法不只適用於Array類型,也適用於String和Array-like類型的對象。這一類方法有:union、intersection、difference、indexOf等。github
簡單介紹一下Array-like,顧名思義,就是像Array而不是Array的一種數據類型,它的特色是可以經過數字下標(0、一、2 ...)訪問,有length屬性,可是不能使用Array的內置方法。這類裏面比較常見的是arguments,就是函數的參數列表,Object.getOwnPropertyNames(arguments)的返回值除了參數列表外,還有length和callee兩個屬性。那麼對這種類型若是要想用Array的內置方法怎麼辦呢?能夠經過數組泛化來調用,有兩種方式:編程
1 Array. method (obj, function(){}); 2 Array.prototype. method .call(obj, function(){});
在underscore裏面主要用了call的方式。既然提到了call,就再對call進行一下解釋。數組
在javascript中call和apply經常使用於實現繼承機制,兩者很相似,只有第二個參數略有差別。調用 call() 方法時,調用者是須要執行的函數對象,第一個參數就是要執行函數中的 this變量,後面的參數都會做爲參數傳遞給要執行函數。例如:瀏覽器
var me = { name: "Alex", City: "Beijing", }; function sayHello(comments) {console.log("Hi, " + this.name + comments)}; sayHello.call(me, ", you are great!");
執行後會打印出「Hi, Alex, you are great!」。
再好比
a.func().call(b)
就至關於b繼承了a, 結果就是b調用了a的func()方法。
在Collections部分的源碼中常常要對Array或Array-like類型與Object類型分開來處理,用到了一個技巧
if (obj.length === +obj.length) {}
對於前者而言,返回爲true,而Object沒有length屬性,obj.length返回的是undefined,"+"是將其餘類型轉化爲數字或者NaN,等同於Number(obj.length),+undefined的結果是NaN,所以整個表達式返回false。這裏有一個知識點是null, false, undefined, NaN的關係。
函數自己也有length屬性,表明函數可以接受的參數個數;相對而言,arguments.length表示的是實際傳入的參數個數,兩者數量能夠不一樣。若是實參比形參要少,沒有的參數被賦值爲undefined。
Functions部分的函數有些使用了閉包(closure),用於保存狀態或者做爲緩存,好比once、after和memoize等。閉包是javascript的一大特點,原理是根據鏈式做用域(chain scope)的原則,上級變量對下級可見。若是在對象或者函數內部再定義函數,而內部的函數使用了上級的變量,當將這個函數被做爲返回值時,返回的函數就成爲閉包,而上級的變量由於仍舊被使用所以會一直保存在內存中。
這一類的函數包括:bind、memorize、debounce、throttle等。
Objects部分最複雜的一個函數是內部用的equal,可能也是整個underscore最複雜的一個,這裏最重要的知識點就是javascript的判等,注意引用類型不能直接使用「==」或「===」,須要使用迭代函數轉化成原始類型進行比較。underscore的isEqual函數與javascript的徹底等同(===)或相等(==)不太同樣,更符合人的直覺。根據源碼簡單總結一下規則,有順序,前面的規則沒有匹配才匹配後面的:
「a」 isEqual new String("a")
Objects部分的函數包括:keys、values、has、isEqual以及一對isType判斷類型的函數。
Utility部分根據比較有用的是生成隨機數和生成ID,還包括增長自定義函數的mixin,轉義html的escape,以及一個簡單的html模板函數。
除了使用函數風格的underscore外,還可使用面向對象的方式,在這種方式下,underscore支持鏈式調用。
1 //定義一個內置對象 2 var data={ 3 name:'ccy' 4 } 5 //經過_()方法將對象建立爲一個Underscore對象 6 //underData對象的原型中包含了Underscore重定義的全部方法ta,能夠任意使用 7 var underData=_(data); 8 //經過value()方法得到原生數據,即data 9 underData.value();
Underscore支持鏈式操做,但你須要先調用chain()方法進行聲明:
var arr = [10, 20, 30]; _(arr) .chain() .map(function(item){ return item++; }) .first() .value();
若是調用了chain()方法,Underscore會將所調用的方法封裝在一個閉包內,並將返回值封裝爲一個Underscore對象並返回:
// 這是Underscore中實現鏈式操做的關鍵函數,它將返回值封裝爲一個新的Underscore對象,並再次調用chain()方法,爲方法鏈中的下一個函數提供支持。 var result = function(obj, chain) { return chain ? _(obj).chain() : obj; }
咱們能夠經過mixin()方法輕鬆地向Underscore中擴展自定義方法
這些方法被追加到Underscore的原型對象中,全部建立的Underscore對象均可以使用這些方法,它們享有和其它方法一樣的環境。
_.mixin({ method1: function(object) { // todo }, method2: function(arr) { // todo }, method3: function(fn) { // todo } });
each()和map()方法是最經常使用用到的兩個方法,它們用於迭代一個集合(數組或對象),並依次處理集合中的每個元素,
map()方法與each()方法的做用、參數相同,但它會將每次迭代函數返回的結果記錄到一個新的數組並返回。
var arr = [1, 2, 3]; _(arr).map(function(item, i) { arr[i] = item + 1; }); var obj = { first : 1, second : 2 } _(obj).each(function(value, key) { return obj[key] = value + 1; });
函數節流是指控制一個函數的執行頻率或間隔(就像控制水流的閘門同樣),Underscore提供了debounce()和throttle()兩個方法用於函數節流。
需求:當用戶在文本框輸入搜索條件時,自動查詢匹配的關鍵字並提示給用戶
1 <input type="text" id="search" name="search" /> 2 <script type="text/javascript"> 3 var query = _(function() { 4 // 在這裏進行查詢操做 5 }).debounce(200); 6 7 $('#search').bind('keypress', query); 8 </script>
需求:當用戶拖動瀏覽器滾動條時,調用服務器接口檢查是否有新的內容
<script type="text/javascript"> var query = _(function() { // 在這裏進行查詢操做 }).throttle(500); $(window).bind('scroll', query); </script>
Underscore提供了一個輕量級的模板解析函數,它能夠幫助咱們有效地組織頁面結構和邏輯。
<!-- 用於顯示渲染後的標籤 --> <ul id="element"></ul> <!-- 定義模板,將模板內容放到一個script標籤中 --> <script type="text/template" id="tpl"> <% for(var i = 0; i < list.length; i++) { %> <% var item = list[i] %> <li> <span><%=item.firstName%> <%=item.lastName%></span> <span><%-item.city%></span> </li> <% } %> </script> <script type="text/javascript" src="underscore/underscore-min.js"></script> <script type="text/javascript"> // 獲取渲染元素和模板內容 var element = $('#element'), tpl = $('#tpl').html(); // 建立數據, 這些數據多是你從服務器獲取的 var data = { list: [ {firstName: '<a href="#">Zhang</a>', lastName: 'San', city: 'Shanghai'}, {firstName: 'Li', lastName: 'Si', city: '<a href="#">Beijing</a>'}, {firstName: 'Wang', lastName: 'Wu', city: 'Guangzhou'}, {firstName: 'Zhao', lastName: 'Liu', city: 'Shenzhen'} ] } // 解析模板, 返回解析後的內容 var html = _.template(tpl, data); // 將解析後的內容填充到渲染元素 element.html(html); </script>
在本例中,咱們將模板內容放到一個<script>標籤中,你可能已經注意到標籤的type是text/template而不是text/javascript,由於它沒法做爲JavaScript腳本直接運行。
我也建議你將模板內容放在<script>中,由於若是你將它們寫在一個<div>或其它標籤中,它們可能會被添加到DOM樹中進行解析(即便你隱藏了這個標籤也沒法避免)。
在本例中,咱們將模板內容和須要填充的數據傳遞給template方法,它會按如下順序進行處理:
咱們常常會遇到一種狀況:屢次調用template方法將數據渲染到同一個模板。
假設咱們有一個分頁列表,列表中的每一條數據都經過模板渲染,當用戶進入下一頁,咱們會獲取下一頁的數據並從新渲染,實際上每次渲染的模板都是同一個,但剛纔描述的template全部處理過程總會被執行。
其實Underscore的template方法提供了一種更高效的調用方式,咱們將上面代碼中的最後兩句修改成:
// 解析模板, 返回解析後的內容 var render = _.template(tpl); var html = render(data); // 將解析後的內容填充到渲染元素 element.html(html);
你會發現細微的差異:咱們在調用template方法時只傳遞了模板內容,而沒有傳遞數據,此時template方法會解析模板內容,生成解析後的可執行JavaScript代碼,並返回一個函數,而函數體就是解析後的JavaScript,所以當咱們調用該函數渲染數據時,就省去了模板解析的動做。
你應該將返回的函數存儲起來(就像我將它存儲在render變量中同樣),再經過調用該函數來渲染數據,特別是在同一個模板可能會被屢次渲染的狀況下,這樣作能提升執行效率
_.template模板函數只能解析3種模板標籤:
<% %>:用於包含JavaScript代碼,這些代碼將在渲染數據時被執行。
<%= %>:用於輸出數據,能夠是一個變量、某個對象的屬性、或函數調用(將輸出函數的返回值)。
<%- %>:用於輸出數據,同時會將數據中包含的HTML字符轉換爲實體形式(例如它會將雙引號轉換爲"形式),用於避免XSS攻擊。
當咱們但願將數據中的HTML做爲文本顯示出來時,經常會使用<%- %>標籤。
Underscore還容許你修改這3種標籤的形式,若是咱們想使用{% %}、{%= %}、{%- %}做爲標籤,能夠經過修改templateSettings來實現,就像這樣:
_.templateSettings = {
evaluate : /\{%([\s\S]+?)\%\}/g, interpolate : /\{%=([\s\S]+?)\%\}/g, escape : /\{%-([\s\S]+?)%\}/g }