耗了一個晚上吐血翻譯不過也學到了很多...《使用對象來組織你的代碼》,翻譯中發現原做者在原文中有部分代碼有誤或不全,本文已修改和添加~javascript
麗貝卡·墨菲原文連接:http://rmurphey.com/blog/2009/10/15/using-objects-to-organize-your-code php
當你不僅是使用jQuery的簡單片斷而是開始開發更復雜的用戶交互,你的代碼會變得笨重和難以調試,這篇文章經過使用對象字面量的形式向你展現如何在行爲特徵的角度思考這些交互。css
在過去幾年,JavaScript庫讓初級開發者有能力爲他們的站點製做炫酷的交互,就像jQuery,有着很是簡單的語法得以讓零編程經驗的人裝飾他們的網頁,一個插件或自定義的幾十行代碼運行出的效果就能給人留下深入印象。html
可是等等,現今的需求早已改變了,如今你的代碼可能須要根據ID的不一樣而被重用,這樣的話用jQuery(或其餘庫)的編寫的代碼片斷看似用處不大了,它們只是代碼片斷不是嗎?當你不使用插件而實現 show() 或 hide() 的功能應該怎麼設計你的代碼呢?java
Introducing the Object Literal Pattern 對象字面量的介紹jquery
對象字面量提供了一個包括行爲的方式去組織代碼,這也意味着避免污染全局命名空間,這是對於一個較大項目的很好作法,它迫使你去思考你的代碼在一開始就應該作什麼以及哪些部分須要放置在合適的位置。對象字面量是封裝相關行爲的方式,以下所示:git
var myObjectLiteral = { myBehavior1 : function() { /* do something */ }, myBehavior2 : function() { /* do something else*/ } };
假設你使用jQuery完成一個點擊list項顯示和隱藏的功能:github
$(document).ready(function() { $('#myFeature li') .append('<div/>') .each(function(){ $(this).find('div') .load('foo.php?item=' + $(this).attr('id')); }) .click(function() { $(this).find('div').show(); $(this).siblings().find('div').hide(); }); });
就是這麼簡單,可是當你想在這個例子中改變一些需求,例如加載內容的URL的方式,以及加載內容的URL,或者是顯示和隱藏的行爲等等,對象字面量清晰地劃分了這些功能特徵,看起來以下:編程
var myFeature = { config : { wrapper : '#myFeature', container : 'div', urlBase : 'foo.php?item=' }, init : function(config){ $.extend(myFeature.config, config); $(myFeature.config.wrapper).find('li'). each(function(){ myFeature.getContent($(this)); }). click(function(){ myFeature.showContent($(this)); }); }, buildUrl : function($li){ return myFeature.config.urlBase + $li.attr('id'); }, getContent : function($li){ $li.append('<' + myFeature.config.container + '/>'); var url = myFeature.buildUrl($li); $li.find(myFeature.config.container).load(url); }, showContent : function($li){ $li.find(myFeature.config.container).show(); myFeature.hideContent($li.siblings()); }, hideContent : function($elements){ $elements.find(myFeature.config.container).hide(); } }; $(document).ready(function() { myFeature.init(); });
最初的例子是很簡單的,用對象字面量形式卻讓代碼變得更長,說實話,對象字面量形式通常是不會節省你的代碼量的。使用對象字面量咱們將代碼的邏輯部分分割開來,所以很容易找到咱們想要改變的部分,咱們已經取得咱們的功能擴展,提供了覆寫默認配置的功能。而且作了文檔上的限制,很容易一眼看出該部分作什麼功能。拋開這個例子的簡單結構,隨着需求的增加咱們的代碼結構將變得越來越清晰。後端
An in-depth example 一個更深層次的示例
咱們的任務是建立每一個部分含有多項內容的UI元素,點擊一個區塊將顯示區塊中項目的列表,點擊項目列表中的項目,項目內容將顯示在內容區域。每當區塊被顯示時,第一個項目列表應該也被顯示。第一部分應該在頁面加載時被顯示。
做者想表達的效果圖應該是這樣的:
Step 1: HTML結構
編寫良好語義化的HTML是編寫好的JavaScript的先決條件,因此咱們思考一下HTML應該長什麼樣子呢,HTML應該是:
考慮到這些策略,咱們開始編寫html吧:
<h1>This is My Nifty Feature</h1> <div id="myFeature"> <ul class="sections"> <li> <h2><a href="/section/1">Section 1</a></h2> <ul> <li> <h3><a href="/section/1/content/1">Section 1 Title 1</a></h3> <p>The excerpt content for Content Item 1</p> </li> <li> <h3><a href="/section/1/content/2">Section 1 Title 2</a></h3> <p>The expert content for Content Item 2</p> </li> <li> <h3><a href="/section/1/content/3">Section 1 Title 3</a></h3> <p>The expert content for Content Item 3</p> </li> </ul> </li> <li> <h2><a href="/section/2">Section 2</a></h2> <ul> <li> <h3><a href="/section/2/content/1">Section 2 Title 1</a></h3> <p>The excerpt content for Content Item 1</p> </li> <li> <h3><a href="/section/2/content/2">Section 2 Title 2</a></h3> <p>The expert content for Content Item 2</p> </li> <li> <h3><a href="/section/2/content/3">Section 2 Title 3</a></h3> <p>The expert content for Content Item 3</p> </li> </ul> </li> <li> <h2><a href="/section/3">Section 3</a></h2> <ul> <li> <h3><a href="/section/3/content/1">Section 3 Title 1</a></h3> <p>The excerpt content for Content Item 1</p> </li> <li> <h3><a href="/section/3/content/2">Section 3 Title 2</a></h3> <p>The expert content for Content Item 2</p> </li> <li> <h3><a href="/section/3/content/3">Section 3 Title 3</a></h3> <p>The expert content for Content Item 3</p> </li> </ul> </li> </ul> </div>
注意此時沒有任何標記顯示一級導航或二級(項目)導航,經過加入Jquery讓它們工做;不支持JavaScript的用戶將會獲得很好的語義HTML(若是HTML表達語義不清,應該是時候替換舊的語義和實現漸進加強了)。
Step 2: Scaffolding the Object Object的腳手架
建立對象第一步是爲對象建立"存根",能夠把"存根"想象成佔位符;它們是咱們要構建的功能大綱,咱們的對象將有以下方法:
首先配置屬性,經過 myFeature.config 將各個屬性設置到一塊兒而不是在代碼中各個部分定義。咱們將在 myFeature.init() 中提供默認屬性覆寫的功能。
var myFeature = { 'config' : {}, 'init' : function() {}, 'buildSectionNav' : function() {}, 'buildItemNav' : function() {}, 'showSection' : function() {}, 'showContentItem' : function() {} };
Step 3: The Code 代碼
一旦咱們創建起了骨架,是時候開始編寫後面的代碼了。首先編寫 myFeature.config 對象和 myFeature.init() 方法:
'config' : { //default container is #myFeature 'container' : $('#myFeature') }, 'init' : function(config){ //provide for custom configuration via init() if(config && typeof(config) == 'object' ){ $.extend(myFeature.config, config); } //create and/or cache some DOM elements //we'll want to use throughout the code myFeature.$container = myFeature.config.container; myFeature.$sections = myFeature.$container. // only select immediate children! find('ul.sections > li');
myFeature.$items = myFeature.$sections.
find('ul > li'); myFeature.$section_nav = $('<p/>') .attr('id', 'section_nav') .prependTo(myFeature.$container); myFeature.$item_nav = $('<p/>') .attr('id', 'item_nav') .insertAfter(myFeature.section_nav); myFeature.$content = $('<p/>') .attr('id', 'content') .insertAfter(myFeature.$item_nav); //build the section-level nav and //"click" the first item myFeature.buildSectionNav(myFeature.$sections); myFeature.$section_nav.find('li:first').click(); //hide the plain HTML from sight myFeature.$container.find('ul.sections').hide(); //make a note that the initialization //is complete; we don't strictly need this //for this iteration, but it can come in handy myFeature.initialized = true; }
接下來編寫 myFeature.buildSectionNav() 方法:
'buildSectionNav' : function($sections){ //iterate over the provided list of sections $sections.each(function(){ //get the section var $section = $(this); //create a list item for the section navigation $('<li/>') //use the text of the first h2 //in the section as the text for //the section navigation .text($section.find('h2:first').text()) //add the list item to the section navigation .appendTo(myFeature.$section_nav) //use data() to store a reference //to the original section on the //newly-created list item .data('section', $section) //bind the click behavior //to the newly created list item //so it will show the section .click(myFeature.showSection); }); }
接下來編寫 myFeature.buildItemNav() 方法:
'buildItemNav' : function($items){ //iterate over the provided list of items $items.each(function(){ //get the item var $item = $(this); //create a list item element for the //item navigation $('<li/>') //use the text of the first h3 //in the item as the text for the //item navigation .text($item.find('h3:first').text()) //add the list item to item navigation .appendTo(myFeature.$item_nav) //use data to store a reference //to the original item on the //newly created list item .data('item', $item) //bind the click behavior to the //newly created list item so it will //show the content item .click(myFeature.showContentItem); }) }
最後,咱們將編寫 showSection() 和 showContentItem() 方法:
'showSection' : function(){ // capture the list item that was clicked on var $li = $(this); //clear out the left nav and content area myFeature.$item_nav.empty(); myFeature.$content.empty(); //get the jQuery section object from original HTML, //which we stored using data() during buildSectionNav var $section = $li.data('section'); //mark the clicked list item as current //and remove the current marker from its siblings $li.addClass('current') .siblings().removeClass('current'); //find all of items related to the section var $items = $section.find('ul li'); //build the item nav for the section myFeature.buildItemsNav($items); //"click" on the first list item in the section's item nav myFeature.$item_nav.find('li:first').click(); }, 'showContentItem' : function(){ var $li = $(this); //mark the clicked list item as current //and remove the current marker form its siblidngs $li.addClass('current') .siblings().removeClass('current'); //get the jQuery item object from the original HTML, //which we stored using data during buildContentNav var $item = $li.data('item'); myFeature.$content.html($item.html()); }
全部準備完後,咱們開始調用 myFeature.init() 方法:
$(document).ready(myFeature.init())
Step 4: Changing Requirements
沒有項目是不提需求的,隨時變動是特色不是嗎?對象字面量的方式使開發快速而且至關容易實現變動需求。若是咱們須要獲取內容片斷是從AJAX得來的而不是HTML?假設這裏添加了先後端交互的功能,嘗試一下:
var myFeature = { 'config' : { 'container' : $('#myFeature'), // configurable function for getting // a URL for loading item content 'getItemURL' : function($item){ return $item.find('a:first').attr('href'); } }, 'init' : function (config) { // stays the same }, 'buildSectionNav' : function($sections){ // stays the same }, 'buildItemNav' : function($items) { // stays the same }, 'showSection' : function(){ //stays the same }, 'showContentItem' : function(){ var $li = $(this); $li.addClass('current'). $siblings().removeClass('current'); var $item = $li.data('item'); var url = myFeature.config.getItemURL($item); // myFeature.$content.html($item.html()) myFeature.$content.load(url); } }
想要更加靈活嗎?有許多你能配置的(覆寫)若是你真的想使代碼功能變得靈活。例如,你能夠經過配置 myFeature.config 自定義地爲每一個item找到對應的文本:
var myFeature = { 'configure' : { ' container' : $('#myFeature'), //specify the default selector // for finding the text to use // for each item in the item nav 'itemNavSelector' : 'h3', //specify a default callback //for "processing" the jQuery object //returned by the itemNavText selector 'itemNavProcessor' : function($selection){ return 'Preview of ' + $selection.eq(0).text(); } }, 'init' : function(config){ // stays the same }, 'buildSectionNav' : function($sections){ // stays the same }, 'buildItemNav' : function($items){ $.items.each(function(){ var $item = $(this); //use the selector and processor //from the config //to get the text for each item nav var myText = myFeature.config.itemNavProcessor( $item.find(myFeature.config.itemNavSelector) ); $('<li/>') //use the new variable //as the text for the nav item .text(myText) .appendTo(myFeature.$item_nav) .data('item', $item) .click(myFeature.showContentItem); }); }, 'showSection' : function(){ // stays the same }, 'showContentItem' : function (){ // stays the same } };
只要你添加配置對象參數,調用 myFeature.init() 時就能夠覆寫config對象:
$(document).ready(function(){ myFeature.init({ 'itemNavSelector' : 'h2' }); });
OK!有了以上了解和學習,讀者們能夠嘗試實現jQuery history 插件~
Conclusion 總結
若是你按照代碼例子一步步理解過來後,你應該對對象字面量有了基本瞭解,它會對你開發複雜功能和交互提供一個有用的方式,提供給你能夠在本代碼上繼續擴展功能,我鼓勵你在JavaScript中嘗試使用對象字面量模式去代替短短几行的代碼——由於這會迫使你去思考元素的表現和行爲去構成一個複雜的功能或交互。一旦你掌握了它,它爲擴展和重用你的代碼提供了堅實的基礎。
Learn More 瞭解更多
附錄前文中An in-depth example 完整代碼:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>An in-depth example 一個更深層次的示例</title> <style type="text/css"> .current{ background: #f47460; } </style> <script type="text/javascript" src="http://cdn.bootcss.com/jquery/3.0.0/jquery.min.js"></script> </head> <body> <h1>This is My Nifty Feature</h1> <div id="myFeature"> <ul class="sections"> <li> <h2><a href="/section/1">Section 1</a></h2> <ul> <li> <h3><a href="/section/1/content/1">Section 1 Title 1</a></h3> <p>The excerpt content for Content Item 1</p> </li> <li> <h3><a href="/section/1/content/2">Section 1 Title 2</a></h3> <p>The expert content for Content Item 2</p> </li> <li> <h3><a href="/section/1/content/3">Section 1 Title 3</a></h3> <p>The expert content for Content Item 3</p> </li> </ul> </li> <li> <h2><a href="/section/2">Section 2</a></h2> <ul> <li> <h3><a href="/section/2/content/1">Section 2 Title 1</a></h3> <p>The excerpt content for Content Item 1</p> </li> <li> <h3><a href="/section/2/content/2">Section 2 Title 2</a></h3> <p>The expert content for Content Item 2</p> </li> <li> <h3><a href="/section/2/content/3">Section 2 Title 3</a></h3> <p>The expert content for Content Item 3</p> </li> </ul> </li> <li> <h2><a href="/section/3">Section 3</a></h2> <ul> <li> <h3><a href="/section/3/content/1">Section 3 Title 1</a></h3> <p>The excerpt content for Content Item 1</p> </li> <li> <h3><a href="/section/3/content/2">Section 3 Title 2</a></h3> <p>The expert content for Content Item 2</p> </li> <li> <h3><a href="/section/3/content/3">Section 3 Title 3</a></h3> <p>The expert content for Content Item 3</p> </li> </ul> </li> </ul> </div> <script type="text/javascript"> var myFeature = { 'config': { 'container' : $('#myFeature') }, 'init': function(config){ if(config && typeof config == 'object'){ $.extend(myFeature.config, config); } //緩存變量 myFeature.$container = myFeature.config.container; myFeature.$sections = myFeature.$container. find('ul.sections > li'); myFeature.$items = myFeature.$sections. find('ul > li'); myFeature.$section_nav = $('<p/>') .attr('id', 'section_nav') .prependTo(myFeature.$container); myFeature.$item_nav = $('<p/>') .attr('id', 'item_nav') .insertAfter(myFeature.$section_nav); myFeature.$content = $('<p/>') .attr('id', 'content') .insertAfter(myFeature.$item_nav); //初始化新增的這三層DOM結構 myFeature.buildSectionNav(myFeature.$sections); myFeature.$section_nav.find('li:first').click(); //隱藏原有的HTML結構 myFeature.$container.find('ul.sections').hide(); }, 'buildSectionNav' : function($sections){ //綁定事件 $sections.each(function(){ var $section = $(this); $('<li>').text($section.find('h2:first').text()) .appendTo(myFeature.$section_nav) .data('section', $section) .click(myFeature.showSection) }); }, 'buildItemNav' : function($items){ //綁定事件 $items.each(function(){ var $item = $(this); $('<li>').text($item.find('h3:first').text()) .appendTo(myFeature.$item_nav) .data('item', $item) .click(myFeature.showContentItem); }); }, 'showSection' : function(){ //事件處理程序 var $li = $(this); myFeature.$item_nav.empty(); myFeature.$content.empty(); var $section = $li.data('section'); $li.addClass('current') .siblings().removeClass('current'); var $items = $section.find('ul li'); myFeature.buildItemNav($items); myFeature.$item_nav.find('li:first').click(); }, 'showContentItem' : function(){ //事件處理程序 var $li = $(this); $li.addClass('current') .siblings().removeClass('current'); var $item = $li.data('item'); myFeature.$content.html($item.html()); } } $(document).ready(function(){myFeature.init()}); </script> </body> </html>