耗了一个晚上吐血翻译不过也学到了不少...《使用对象来组织你的代码》,翻译中发现原作者在原文中有部分代码有误或不全,本文已修改和添加~

丽贝卡·墨菲原文链接:http://rmurphey.com/blog/2009/10/15/using-objects-to-organize-your-code

当你不只是使用jQuery的简单片段而是开始开发更复杂的用户交互,你的代码会变得笨重和难以调试,这篇文章通过使用对象字面量的形式向你展示如何在行为特征的角度思考这些交互。

在过去几年,JavaScript库让初级开发者有能力为他们的站点制作炫酷的交互,就像jQuery,有着非常简单的语法得以让零编程经验的人装饰他们的网页,一个插件或自定义的几十行代码运行出的效果就能给人留下深刻印象。

但是等等,现今的需求早已改变了,现在你的代码可能需要根据ID的不同而被重用,这样的话用jQuery(或其他库)的编写的代码片段看似用处不大了,它们只是代码片段不是吗?当你不使用插件而实现 show() 或 hide() 的功能应该怎么设计你的代码呢?

Introducing the Object Literal Pattern 对象字面量的介绍

对象字面量提供了一个包括行为的方式去组织代码,这也意味着避免污染全局命名空间,这是对于一个较大项目的很好做法,它迫使你去思考你的代码在一开始就应该做什么以及哪些部分需要放置在合适的位置。对象字面量是封装相关行为的方式,如下所示:

var myObjectLiteral = {
myBehavior1 : function() {
/* do something */
}, myBehavior2 : function() {
/* do something else*/
}
};

假设你使用jQuery完成一个点击list项显示和隐藏的功能:

$(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应该是:

  • 当JavaScript不可用时HTML仍然有意义并且很好的工作
  • 提供可预测的DOM结构方便附加在JavaScript上
  • 避免不必要的IDs和classes(你可能会感到惊讶)

考虑到这些策略,我们开始编写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.init() 将会运行在 $(document).ready() 中。通过语义化的HTML标签,让我们很快进入到JavaScript的用户界面。
  • myFeature.buildSectionNav()  将被 myFeature.init() 调用。这将需要一个jQuery对象包含所有section语义的HTML标签来构建一级导航,每项一级导航而且会被绑定点击事件,点击它们将会显示相应的部分(二级导航)
  • myFeature.buildItemNav() 将被 myFeature.showSection() 调用,这需要jQuery对象包含所有和section相关的具有语义HTML的item项,用它们来建立二级导航。它们(二级导航)也将会被绑定点击事件所以点击后将显示相关内容。
  • myFeature.showSection() 当用户点击一级导航的项时将被调用。通过一级导航的点击项判断哪部分语义内容将被显示。
  • myFeature.showContentItem()  当用户点击二级导航时将被调用。通过二级导航的点击判断那部分语义内容将被显示。

首先配置属性,通过 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>

【译】Using Objects to Organize Your Code的更多相关文章

  1. 【译】第8节---EF Code First中配置类

    原文:http://www.entityframeworktutorial.net/code-first/configure-classes-in-code-first.aspx 前面的章节中我们知道 ...

  2. 【译】第1节--- EF Code First 介绍

    原文:http://www.entityframeworktutorial.net/code-first/entity-framework-code-first.aspx 本教程涵盖了code fir ...

  3. 【译】第9节---EF Code First中数据注解

    原文:http://www.entityframeworktutorial.net/code-first/dataannotation-in-code-first.aspx EF Code-First ...

  4. 大型 JavaScript 应用架构中的模式

    原文:Patterns For Large-Scale JavaScript Application Architecture by @Addy Osmani 今天我们要讨论大型 JavaScript ...

  5. [转]大型 JavaScript 应用架构中的模式

    目录 1.我是谁,以及我为什么写这个主题 2.可以用140个字概述这篇文章吗? 3.究竟什么是“大型”JavaScript应用程序? 4.让我们回顾一下当前的架构 5.想得长远一些 6.头脑风暴 7. ...

  6. 学习jQuery的免费资源:电子书、视频、教程和博客

    jQuery毫无疑问是目前最流行的JavasScript库.排名最前的网站中70%使用了jQuery,并且jQuery也成为了Web开发的标准.如果你想找Web开发方面的工作,了解jQuery会大大的 ...

  7. (译)iOS Code Signing: 解惑

    子龙山人 Learning,Sharing,Improving! (译)iOS Code Signing: 解惑 免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切 ...

  8. Objects and Data Structures

    Date Abstraction Hiding implementation is not just a matter of putting a layer of fucntions between ...

  9. jQuery学习--Code Organization Concepts

    jQuery官方文档:  http://learn.jquery.com/code-organization/concepts/ Code Organization Concepts(代码组织概念) ...

随机推荐

  1. Invalidate()函数

    Invalidate( ) :使整个窗口客户区无效, 并进行更新显示的函数 介绍 void Invalidate( BOOL bErase = TRUE ); 参数: bErase 决定了是否要在WM ...

  2. Environment.GetEnvironmentVariable

    参考: https://jingyan.baidu.com/article/b24f6c82cba6dc86bfe5da9f.html https://msdn.microsoft.com/zh-cn ...

  3. 原来javascript 自带 encodeURI 和 decodeURI文 方法了

    今天百度一下才知道js 自带 encodeURI 和 decodeURI 方法了,之前还找了其他代码来处理(笑哭了.jpg <script type="text/javascript& ...

  4. 【Laravel】Mac下玩转Laravel

    1 apache 首先Mac系统是自带了Apache,只需要执行 sudo apachectl start 就可以打开Apache服务,然后访问 http://localhost 就可以访问到,it' ...

  5. 由JS函数返回值引发的一场”血案"

    ---恢复内容开始--- 啊...  本来昨天晚上想写来着,结果陪老婆看电视剧就忘了... 呢滴神啊,原谅我吧. 背景:昨天在项目中做一个小功能的时候,出现了个小问题,而且一开始找了半天也没找到原因. ...

  6. 高中生的IT之路-1.3那一幕

    上一篇讲到,当时我认为自己的命运就是小时候上学,长大后外出打工,所以高中毕业后就来到了天津,到爸爸的店铺打工. 我爸的店铺就在天津大学校园里,幸运的是,我人生的转折点也就在此. 刚到店里那段时间,每天 ...

  7. 使用HttpClient以文件流的方式上传文件(非multipartFormData方式)

    @Test public void testAdd() throws IOException { HttpPost post = new HttpPost("http://localhost ...

  8. mysql客户端不能插入中文字符

    问题:输入中文报错:Incorrect string value 步骤: 1.查看MySQL编码设置 show variables like '%character%'; 2.重新设置编码(注意:ut ...

  9. 新增form表单,post提交.2

  10. 基于JDK1.8的LinkedList源码学习笔记

    LinkedList作为一种常用的List,是除了ArrayList之外最有用的List.其同样实现了List接口,但是除此之外它同样实现了Deque接口,而Deque是一个双端队列接口,其继承自Qu ...