Underscore.js(JavaScript對象操做方法)

Underscore封裝了經常使用的JavaScript對象操做方法,用於提升開發效率。(Underscore還能夠被使用在Node.js運行環境。)javascript

  在學習Underscore以前,你應該先保存它的API地址,由於你將在之後常常訪問它:css

  http://documentcloud.github.com/underscore/html

  從API中,你已經能夠看出,Underscore沒有任何複雜的結構和流程,它僅僅提供了一系列經常使用的函數。若是你將API中的方法從頭到尾用一遍,你就會對它很是瞭解。java

  儘管如此,但我以爲仍是有必要將一些重要的方法拿出來與你們討論,它們十分重要,卻在API中描述地還不夠清楚。git

5.1 Underscore對象封裝

  Underscore並無在原生的JavaScript對象原型中進行擴展,而是像jQuery同樣,將數據封裝在一個自定義對象中(下文中稱「Underscore對象」)。github

  你能夠經過調用一個Underscore對象的value()方法來獲取原生的JavaScript數據,例如:windows

 

Js代碼    收藏代碼
  1. // 定義一個JavaScript內置對象  
  2. var jsData = {  
  3.     name : 'data'  
  4. }  
  5.   
  6. // 經過_()方法將對象建立爲一個Underscore對象  
  7. // underscoreData對象的原型中包含了Underscore中定義的全部方法,你能夠任意使用  
  8. var underscoreData = _(jsData);  
  9.   
  10. // 經過value方法獲取原生數據, 即jsData  
  11. underscoreData.value();  

 

5.2 優先調用JavaScript 1.6內置方法

  Underscore中有許多方法在JavaScript1.6中已經被歸入規範,所以在Underscore對象內部,會優先調用宿主環境提供的內置方法(若是宿主環境已經實現了這些方法),以此提升函數的執行效率。數組

  而對於不支持JavaScript 1.6的宿主環境,Underscore會經過本身的方式實現,而對開發者來講,這些徹底是透明的。瀏覽器

  這裏所說的宿主環境,多是Node.js運行環境,或客戶端瀏覽器。服務器

5.3 改變命名空間

  Underscore默認使用_(下劃線)來訪問和建立對象,但這個名字可能不符合咱們的命名規範,或容易引發命名衝突。

  咱們能夠經過noConflict()方法來改變Underscore的命名,並恢復_(下劃線)變量以前的值,例如:

 

Js代碼    收藏代碼
  1. <script type="text/javascript">  
  2.     var _ = '自定義變量';  
  3. </script>  
  4. <script type="text/javascript" src="underscore/underscore-min.js"></script>  
  5. <script type="text/javascript">  
  6.     // Underscore對象  
  7.     console.dir(_);  
  8.     // 將Underscore對象重命名爲us, 後面都經過us來訪問和建立Underscore對象  
  9.     var us = _.noConflict();  
  10.     // 輸出"自定義變量"  
  11.     console.dir(_);  
  12. </script>  

 

 

5.4 鏈式操做

  還記得咱們在jQuery中是如何進行連接操做嗎?例如:

 

Js代碼    收藏代碼
  1. $('a')  
  2.     .css('position', 'relative')  
  3.     .attr('href', '#')  
  4.     .show();  

 

 

  Underscore一樣支持鏈式操做,但你須要先調用chain()方法進行聲明:

 

Js代碼    收藏代碼
  1. var arr = [10, 20, 30];  
  2. _(arr)  
  3.     .chain()  
  4.     .map(function(item){ return item++; })  
  5.     .first()  
  6.     .value();  

 

 

  若是調用了chain()方法,Underscore會將所調用的方法封裝在一個閉包內,並將返回值封裝爲一個Underscore對象並返回:

 

Js代碼    收藏代碼
  1. // 這是Underscore中實現鏈式操做的關鍵函數,它將返回值封裝爲一個新的Underscore對象,並再次調用chain()方法,爲方法鏈中的下一個函數提供支持。  
  2. var result = function(obj, chain) {  
  3.     return chain ? _(obj).chain() : obj;  
  4. }  

 

 

5.5 擴展Underscore

  咱們能夠經過mixin()方法輕鬆地向Underscore中擴展自定義方法,例如:

 

Js代碼    收藏代碼
  1. _.mixin({  
  2.     method1: function(object) {  
  3.         // todo  
  4.     },  
  5.     method2: function(arr) {  
  6.         // todo  
  7.     },  
  8.     method3: function(fn) {  
  9.         // todo  
  10.     }  
  11. });  

 

 

 

  這些方法被追加到Underscore的原型對象中,全部建立的Underscore對象均可以使用這些方法,它們享有和其它方法一樣的環境。

5.6 遍歷集合

  each()和map()方法是最經常使用用到的兩個方法,它們用於迭代一個集合(數組或對象),並依次處理集合中的每個元素,例如:

 

Js代碼    收藏代碼
  1. var arr = [1, 2, 3];  
  2.   
  3. _(arr).map(function(item, i) {  
  4.     arr[i] = item + 1;  
  5. });  
  6.   
  7. var obj = {  
  8.     first : 1,  
  9.     second : 2  
  10. }  
  11.   
  12. _(obj).each(function(value, key) {  
  13.     return obj[key] = value + 1;  
  14. });  

 

 

 

  map()方法與each()方法的做用、參數相同,但它會將每次迭代函數返回的結果記錄到一個新的數組並返回。

5.7 函數節流

  函數節流是指控制一個函數的執行頻率或間隔(就像控制水流的閘門同樣),Underscore提供了debounce()和throttle()兩個方法用於函數節流。

  爲了更清楚地描述這兩個方法,假設咱們須要實現兩個需求:

 

  需求1:當用戶在文本框輸入搜索條件時,自動查詢匹配的關鍵字並提示給用戶(就像在Tmall輸入搜索關鍵字時那樣)

  首先分析第1個需求,咱們能夠綁定文本框的keypress事件,當輸入框內容發生變化時,查詢匹配關鍵字並展現。假設我想查詢「windows phone」,它包含13個字符,而我輸入完成只花了1秒鐘(好像有點快,就意思意思吧),那麼在這1秒內,調用了13次查詢方法。這是一件很是恐怖的事情,若是Tmall也這樣實現,我擔憂它會不會在光棍節到來以前就掛掉了(固然,它並無這麼脆弱,但這絕對不是最好的方案)

  更好的方法是,咱們但願用戶已經輸入完成,或者正在等待提示(也許他懶得再輸入後面的內容)的時候,再查詢匹配關鍵字。

  最後咱們發現,在咱們指望的這兩種狀況下,用戶會暫時中止輸入,因而咱們決定在用戶暫停輸入200毫秒後再進行查詢(若是用戶在不斷地輸入內容,那麼咱們認爲他可能很明確本身想要的關鍵字,因此等一等再提示他)

  這時,利用Underscore中的debounce()函數,咱們能夠輕鬆實現這個需求:

 

Js代碼    收藏代碼
  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>  

 

 

 

  你能看到,咱們的代碼很是簡潔,節流控制在debounce()方法中已經被實現,咱們只告訴它當query函數在200毫秒內沒有被調用過的話,就執行咱們的查詢操做,而後再將query函數綁定到輸入框的keypress事件。

  query函數是怎麼來的?咱們在調用debounce()方法時,會傳遞一個執行查詢操做的函數和一個時間(毫秒數),debounce()方法會根據咱們傳遞的時間對函數進行節流控制,並返回一個新的函數(即query函數),咱們能夠放心大膽地調用query函數,而debounce()方法會按要求幫咱們作好控制。

 

  需求2:當用戶拖動瀏覽器滾動條時,調用服務器接口檢查是否有新的內容

  再來分析第2個需求,咱們能夠將查詢方法綁定到window.onscroll事件,但這顯然不是一個好的作法,由於用戶拖動一次滾動條可能會觸發幾十次甚至上百次onscroll事件。

  咱們是否可使用上面的debounce()方法來進行節流控制?當用戶拖動滾動條完畢後,再查詢新的內容?但這與需求不符,用戶但願在拖動的過程當中也能看到新內容的變化。

  所以咱們決定這樣作:用戶在拖動時,每兩次查詢的間隔很多於500毫秒,若是用戶拖動了1秒鐘,這可能會觸發200次onscroll事件,但咱們最多隻進行2次查詢。

  利用Underscore中的throttle()方法,咱們也能夠輕鬆實現這個需求:

 

Js代碼    收藏代碼
  1. <script type="text/javascript">  
  2.     var query = _(function() {  
  3.         // 在這裏進行查詢操做  
  4.     }).throttle(500);  
  5.   
  6.     $(window).bind('scroll', query);  
  7. </script>  

 

 

 

  代碼仍然十分簡潔,由於在throttle()方法內部,已經爲咱們實現的全部控制。

 

  你可能已經發現,debounce()和throttle()兩個方法很是類似(包括調用方式和返回值),做用卻又有不一樣。

  它們都是用於函數節流,控制函數不被頻繁地調用,節省客戶端及服務器資源。

 

  • debounce()方法關注函數執行的間隔,即函數兩次的調用時間不能小於指定時間。
  • throttle()方法更關注函數的執行頻率,即在指定頻率內函數只會被調用一次。

 

5.8 模板解析

  Underscore提供了一個輕量級的模板解析函數,它能夠幫助咱們有效地組織頁面結構和邏輯。

  我將經過一個例子來介紹它:

 

Js代碼    收藏代碼
  1. <!-- 用於顯示渲染後的標籤 -->  
  2. <ul id="element"></ul>  
  3.   
  4. <!-- 定義模板,將模板內容放到一個script標籤中 -->  
  5. <script type="text/template" id="tpl">  
  6.     <% for(var i = 0; i < list.length; i++) { %>  
  7.         <% var item = list[i] %>  
  8.         <li>  
  9.             <span><%=item.firstName%> <%=item.lastName%></span>  
  10.             <span><%-item.city%></span>  
  11.         </li>  
  12.     <% } %>  
  13. </script>  
  14. <script type="text/javascript" src="underscore/underscore-min.js"></script>  
  15. <script type="text/javascript">  
  16.     // 獲取渲染元素和模板內容  
  17.     var element = $('#element'),  
  18.         tpl = $('#tpl').html();  
  19.       
  20.     // 建立數據, 這些數據多是你從服務器獲取的  
  21.     var data = {  
  22.         list: [  
  23.             {firstName: '<a href="#">Zhang</a>', lastName: 'San', city: 'Shanghai'},  
  24.             {firstName: 'Li', lastName: 'Si', city: '<a href="#">Beijing</a>'},  
  25.             {firstName: 'Wang', lastName: 'Wu', city: 'Guangzhou'},  
  26.             {firstName: 'Zhao', lastName: 'Liu', city: 'Shenzhen'}  
  27.         ]  
  28.     }  
  29.       
  30.     // 解析模板, 返回解析後的內容  
  31.     var html = _.template(tpl, data);  
  32.     // 將解析後的內容填充到渲染元素  
  33.     element.html(html);  
  34. </script>  

 

 

  在本例中,咱們將模板內容放到一個<script>標籤中,你可能已經注意到標籤的type是text/template而不是text/javascript,由於它沒法做爲JavaScript腳本直接運行。

  我也建議你將模板內容放在<script>中,由於若是你將它們寫在一個<div>或其它標籤中,它們可能會被添加到DOM樹中進行解析(即便你隱藏了這個標籤也沒法避免)。

 

  _.template模板函數只能解析3種模板標籤(這比Smarty、JSTL要簡單得多):

  <%  %>:用於包含JavaScript代碼,這些代碼將在渲染數據時被執行。

  <%= %>:用於輸出數據,能夠是一個變量、某個對象的屬性、或函數調用(將輸出函數的返回值)。

  <%- %>:用於輸出數據,同時會將數據中包含的HTML字符轉換爲實體形式(例如它會將雙引號轉換爲&quot;形式),用於避免XSS攻擊。

  當咱們但願將數據中的HTML做爲文本顯示出來時,經常會使用<%- %>標籤。

  Underscore還容許你修改這3種標籤的形式,若是咱們想使用{% %}、{%= %}、{%- %}做爲標籤,能夠經過修改templateSettings來實現,就像這樣:

 

Js代碼    收藏代碼
  1. _.templateSettings = {  
  2.     evaluate : /\{%([\s\S]+?)\%\}/g,  
  3.     interpolate : /\{%=([\s\S]+?)\%\}/g,  
  4.     escape : /\{%-([\s\S]+?)%\}/g  
  5. }  

 

 

  在本例中,咱們將模板內容和須要填充的數據傳遞給template方法,它會按如下順序進行處理:

 

 

  • 將模板內容解析爲可執行的JavaScript(解析模板標籤)
  • 經過with語句將解析後的JavaScript做用域修改成咱們傳遞的數據對象,這使咱們可以直接在模板中經過變量形式訪問數據對象的屬性
  • 執行解析後的JavaScript(將數據填充到模板)
  • 返回執行後的結果

 

  咱們常常會遇到一種狀況:屢次調用template方法將數據渲染到同一個模板。

  假設咱們有一個分頁列表,列表中的每一條數據都經過模板渲染,當用戶進入下一頁,咱們會獲取下一頁的數據並從新渲染,實際上每次渲染的模板都是同一個,但剛纔描述的template全部處理過程總會被執行。

  其實Underscore的template方法提供了一種更高效的調用方式,咱們將上面代碼中的最後兩句修改成:

 

Js代碼    收藏代碼
  1. // 解析模板, 返回解析後的內容  
  2. var render = _.template(tpl);  
  3. var html = render(data);  
  4. // 將解析後的內容填充到渲染元素  
  5. element.html(html);  

 

 

  你會發現細微的差異:咱們在調用template方法時只傳遞了模板內容,而沒有傳遞數據,此時template方法會解析模板內容,生成解析後的可執行JavaScript代碼,並返回一個函數,而函數體就是解析後的JavaScript,所以當咱們調用該函數渲染數據時,就省去了模板解析的動做。

 

  你應該將返回的函數存儲起來(就像我將它存儲在render變量中同樣),再經過調用該函數來渲染數據,特別是在同一個模板可能會被屢次渲染的狀況下,這樣作能提升執行效率(具體提高多少,應該根據你的模板長度和複雜度而定,但不管如何,這都是一個良好的習慣)。

相關文章
相關標籤/搜索