代码组织的第一步是将应用程序分离成不同的代码块;有时,哪怕只做到这一点也是很有意义的。
对象原语可能是最简单的关联代码封装方式了。它不会为属性或方法提供任何隐私保护,但它对于从你的代码中消除匿名函数,集中配置选项,简化重用和重构都很有帮助。
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.