代码组织的第一步是将应用程序分离成不同的代码块;有时,哪怕只做到这一点也是很有意义的。
对象原语可能是最简单的关联代码封装方式了。它不会为属性或方法提供任何隐私保护,但它对于从你的代码中消除匿名函数,集中配置选项,简化重用和重构都很有帮助。
Example 10.1. 对象原语
var myFeature = { myProperty : 'hello', myMethod : function() { console.log(myFeature.myProperty); }, init : function(settings) { myFeature.settings = settings; }, readSettings : function() { console.log(myFeature.settings); } }; myFeature.myProperty; // 'hello' myFeature.myMethod(); // 记录 'hello' myFeature.init({ foo : 'bar' }); myFeature.readSettings(); // 记录 { foo : 'bar' }
上面的对象原语简单地将一个对象分配给了一个变量。该对象具有一个属性和几个方法。所有的属性和方法都是公有的,因此你应用程序的任何部分都可以访问对象的属性、调用对象的方法。尽管有一个init方法,但在对象实例化之前调用它却没有任何必要。
我们怎样将这种模式应用到jQuery代码中?让我们通过编写传统jQuery风格的代码来说明吧:
//点击一个列表项加载一些内容 //引用列表项的ID并隐藏兄弟列表项的内容 $(document).ready(function() { $('#myFeature li') .append('<div/>') .click(function() { var $this = $(this); var $div = $this.find('div'); $div.load('foo.php?item=' + $this.attr('id'), function() { $div.show(); $this.siblings() .find('div').hide(); } ); }); });
如果它出现在我们的应用程序范围内,抛弃它可能会更好。换句话说,如果它是一个大型应用程序的一部分,我们最好将这个功能从孤立的功能中分离出来。我们可能想把代码中的URL移到一个配置区域。最后,我们可能想摆脱链式代码的束缚让以后修改功能块更简单一点。
Example 10.2. 将对象原语用到jQuery特性中
var myFeature = { init : function(settings) { myFeature.config = { $items : $('#myFeature li'), $container : $('<div class="container"></div>'), urlBase : '/foo.php?item=' }; // 允许重载默认配置 $.extend(myFeature.config, settings); myFeature.setup(); }, setup : function() { myFeature.config.$items .each(myFeature.createContainer) .click(myFeature.showItem); }, createContainer : function() { var $i = $(this), $c = myFeature.config.$container.clone() .appendTo($i); $i.data('container', $c); }, buildUrl : function() { return myFeature.config.urlBase + myFeature.$currentItem.attr('id'); }, showItem : function() { var myFeature.$currentItem = $(this); myFeature.getContent(myFeature.showContent); }, getContent : function(callback) { var url = myFeature.buildUrl(); myFeature.$currentItem .data('container').load(url, callback); }, showContent : function() { myFeature.$currentItem .data('container').show(); myFeature.hideContent(); }, hideContent : function() { myFeature.$currentItem.siblings() .each(function() { $(this).data('container').hide(); }); } }; $(document).ready(myFeature.init);
你会关心的第一件事就是这种方式比原来的代码明显要长得多——此外,如果它在我们应用程序的范围内,对象原语的使用则有点泛滥了。假设它不在我们程序的范围内,我们便发现了这样几个事实: The first thing you'll notice is that this approach is obviously far longer than the original — again, if this were the extent of our application, using an object literal would likely be overkill. Assuming it's not the extent of our application, though, we've gained several things:
我们已经将功能特性分解成了短小的方法。如果以后我们需要改变显示内容,在哪里更改将一目了然。
我们已经消除了匿名函数的使用。
我们已经将配置选项移出了代码本身,并将这些配置选项放到了核心位置。
我们已经摆脱了链式代码的束缚,使代码易于重构,重新合成,重新组织。
作为不一般的特性,对象原语是对在$(document).ready()块中在编写冗长代码的明显改进,它迫使我们考虑应用程序的功能块。无论如何,和在$(document).ready()块中简单定义大量函数相比,它们也好不到哪里去。
组件模式突破了对象原语的一些局限性,如有必要可以对外暴露出公有API,为变量和函数提供隐私保护。
Example 10.3. 组件模式
var feature =(function() { //私有变量和函数 var privateThing = 'secret', publicThing = 'not secret', changePrivateThing = function() { privateThing = 'super secret'; }, sayPrivateThing = function() { console.log(privateThing); changePrivateThing(); }; //公有API return { publicThing : publicThing, sayPrivateThing : sayPrivateThing } })(); feature.publicThing; // 'not secret' feature.sayPrivateThing(); //记录'secret'并且改变privateThing的值
在上面的例子里,我们自动执行了一个返回对象的匿名函数。在该函数内部,我们定义了一些变量,因为这些变量是定义在函数内部的,我们无法在函数之外去访问它们,除非我们把它放到返回的对象中。这意味着函数外部的代码无法访问privateThing
变量或changePrivateThing
函数。不管怎样,sayPrivateThing
却可以访问privateThing
和changePrivateThing
,因为它们和sayPrivateThing
处于相同的作用域。
这种模式之所以十分强大,是因为当需要暴露由返回对象的属性和方法构成的受限API时,你可以隐藏这些变量名,形成私有的变量和函数。
下面是上一个例子的修正版本,它向我们展示了仅仅暴露该组件一个公有方法showItemByIndex()
时,通过组件模式创建相同的特性。
Example 10.4. 通过jQuery使用组件模式
$(document).ready(function() { var feature = (function() { var $items = $('#myFeature li'), $container = $('<div class="container"></div>'), $currentItem, urlBase = '/foo.php?item=', createContainer = function() { var $i = $(this), $c = $container.clone().appendTo($i); $i.data('container', $c); }, buildUrl = function() { return urlBase + $currentItem.attr('id'); }, showItem = function() { var $currentItem = $(this); getContent(showContent); }, showItemByIndex = function(idx) { $.proxy(showItem, $items.get(idx)); }, getContent = function(callback) { $currentItem.data('container').load(buildUrl(), callback); }, showContent = function() { $currentItem.data('container').show(); hideContent(); }, hideContent = function() { $currentItem.siblings() .each(function() { $(this).data('container').hide(); }); }; $items .each(createContainer) .click(showItem); return { showItemByIndex : showItemByIndex }; })(); feature.showItemByIndex(0); });
Copyright Rebecca Murphey, released under the Creative Commons Attribution-Share Alike 3.0 United States license.