【译】Using Objects to Organize Your Code
耗了一个晚上吐血翻译不过也学到了不少...《使用对象来组织你的代码》,翻译中发现原作者在原文中有部分代码有误或不全,本文已修改和添加~
丽贝卡·墨菲原文链接: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 了解更多
- More on the jQuery data() method
- More praise for the object literal pattern
- The jQuery History plugin
- An interseting application of the object literal pattern for architecting code for multiple page types
附录前文中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的更多相关文章
- 【译】第8节---EF Code First中配置类
原文:http://www.entityframeworktutorial.net/code-first/configure-classes-in-code-first.aspx 前面的章节中我们知道 ...
- 【译】第1节--- EF Code First 介绍
原文:http://www.entityframeworktutorial.net/code-first/entity-framework-code-first.aspx 本教程涵盖了code fir ...
- 【译】第9节---EF Code First中数据注解
原文:http://www.entityframeworktutorial.net/code-first/dataannotation-in-code-first.aspx EF Code-First ...
- 大型 JavaScript 应用架构中的模式
原文:Patterns For Large-Scale JavaScript Application Architecture by @Addy Osmani 今天我们要讨论大型 JavaScript ...
- [转]大型 JavaScript 应用架构中的模式
目录 1.我是谁,以及我为什么写这个主题 2.可以用140个字概述这篇文章吗? 3.究竟什么是“大型”JavaScript应用程序? 4.让我们回顾一下当前的架构 5.想得长远一些 6.头脑风暴 7. ...
- 学习jQuery的免费资源:电子书、视频、教程和博客
jQuery毫无疑问是目前最流行的JavasScript库.排名最前的网站中70%使用了jQuery,并且jQuery也成为了Web开发的标准.如果你想找Web开发方面的工作,了解jQuery会大大的 ...
- (译)iOS Code Signing: 解惑
子龙山人 Learning,Sharing,Improving! (译)iOS Code Signing: 解惑 免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切 ...
- Objects and Data Structures
Date Abstraction Hiding implementation is not just a matter of putting a layer of fucntions between ...
- jQuery学习--Code Organization Concepts
jQuery官方文档: http://learn.jquery.com/code-organization/concepts/ Code Organization Concepts(代码组织概念) ...
随机推荐
- Ubuntu创建新用户并增加管理员权限
1.Ubuntu中的root帐号默认是被禁用了的,所以登陆的时候没有这个账号 打开终端开启root账户 sudo passwd -u root sudo passwd root 设置root密码,输入 ...
- Unity和虚幻的比较
很多人从Unity开始转向虚幻4了,我目前则相反,从研究使用虚幻4,回到了Unity 5上. 前端总结的Unity和Unreal 4的一些优缺点,自己做的对比图.就先放这里了. 其实,作为引擎,各有优 ...
- iOS事件拦截(实现触摸任意位置隐藏指定view)
项目里有一个需求,类似新浪或者腾讯微博的顶部title栏的类别选择器的消失(在选择器展开的时候,触摸屏幕任何地方使其消失). 最开始的想法是当这个选择器(selectorView)展开的时候,在当前屏 ...
- UIBarButtonItem
1.UINavigationController导航控制器如何使用 UINavigationController可以翻译为导航控制器,在IOS里经常用到. 我们看看它的如何使用: 下面的图显示了导航控 ...
- 一个java源文件中是否可以包括多个类(非内部类)?有何限制?
可以有多个类,但只能有一个public的类,并且public的类名必须与文件名一致.
- 【PHP】使用openssl进行Rsa长数据加密(117)解密(128)
PHP使用openssl进行Rsa加密,如果要加密的明文太长则会出错,解决方法:加密的时候117个字符加密一次,然后把所有的密文拼接成一个密文:解密的时候需要128个字符解密一下,然后拼接成数据. 加 ...
- Docker源码分析(六):Docker Daemon网络
1. 前言 Docker作为一个开源的轻量级虚拟化容器引擎技术,已然给云计算领域带来了新的发展模式.Docker借助容器技术彻底释放了轻量级虚拟化技术的威力,让容器的伸缩.应用的运行都变得前所未有的方 ...
- 谈谈后台服务的RPC和路由管理
版权声明:本文由廖念波原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/147 来源:腾云阁 https://www.qclo ...
- 静态资源的gzip
1.项目中,接触到gzip.未压缩的文件和压缩后的文件的比例可能达到:3:1.所以,gzip是网络中文件高速传输的很好方法. 2.一般js.css.html文件都会在后端进行gzip.当浏览器请求这些 ...
- 自动化测试---PageObjects快速入门(一)
PageObject快速入门 介绍: Page Object是selenium的一种设计模式, 是在web自动化测试中将一个页面设备成一个class或基础库来实际的方法这种模式的好处有以下几点:1.让 ...