JavaScript设计模式(txt+pdf+epub+mobi电子书下载)


发布时间:2020-06-24 13:29:35

点击下载

作者:(美)Addy Osmani

出版社:人民邮电出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

JavaScript设计模式

JavaScript设计模式试读:

前言

设计模式是解决软件设计中常见问题的可复用方案。探索任何编程语言时,设计模式都是一个令人兴奋和极具吸引力的话题。

原因之一是:设计模式是许多先前开发人员总结出的经验,我们可以借鉴这些经验进行编程,以确保能够以优化的方式组织代码,为我们解决棘手的问题提供参考。

设计模式还是我们用来描述解决方案的常用词汇。当我们想要向其他人表述一种以代码形式构建解决方案的方式时,描述设计模式比描述语法和语义要简单得多。

在本书中,我们将探讨JavaScript编程语言中经典的与现代的设计模式的应用。

目标读者

本书的目标读者是专业开发人员,希望提高对设计模式的认识,并学会如何将设计模式应用到JavaScript编程语言中。

本书对有些概念(闭包、原型继承)只做了一些基本介绍,以便于理解。如果想要进一步了解这些概念,下面列出了一些推荐书目,方便大家阅读。

如果想要学习如何编写出美观、结构化和组织良好的代码,相信这本书就是为你而准备的。

致谢

本书中提到的很多设计模式是根据我的个人经验总结出来的,还有很多设计模式是JavaScript 社区前辈总结出来的经验。本作品是众多开发人员的经验结合产物。与斯托扬·斯蒂凡诺夫为防止致谢名单中出现错漏的明智做法(在《JavaScript模式》中)类似,我列出了致谢名单以及本书参考的文献。

如果参考文献中遗漏了任何著作或链接,我深表歉意。如果您与我联系,我一定会及时将您的著作或链接添加到参考文献中。

其他读物

虽然本书也面向初学者和中级开发人员,但也需要读者对JavaScript有基本的了解。如果想要深入了解该语言,我很乐意向您推荐以下书目。

• 《JavaScript权威指南》,作者:David Flanagan

• 《JavaScript编程精解》,作者:Marijn Haverbeke

• 《JavaScript模式》,作者:Stoyan Stefanov1

• 《编写可维护的 JavaScript》,作者:Nicholas Zakas1

注释: 编者注:《编写可维护的JavaScript》已由人民邮电出版社出版(ISBN9787115310088,定价55元, 2013年3月)。

• 《JavaScript语言精粹》,作者:Douglas Crockford

本书约定

本书使用下列排版约定。

斜体(Italic)

表示专业词汇、链接(URL)、文件名和文件扩展名。

等宽字体(Constant width)

表示广义上的计算机编码,它们包括变量或函数名、数据库、数据类型、环境变量、语句和关键字。

等宽粗体(Constant width bold)

表示应该由用户按照字面引入的命令或其他文本。

等宽斜体(Constant width italic)

表示应该由用户替换或取决于上下文的值。

这个图标表示提示、建议或一般说明。

这个图标表示警告或提醒。

代码示例

这本书是为了帮助你做好工作。一般来说,你可以在程序和文档中使用本书的代码。你无须联系我们获取许可。例如,使用来自本书的几段代码写一个程序是不需要许可的。出售和散布O’ Reilly书中用例的光盘(CD-ROM)是需要许可的。通过引用本书用例和代码来回答问题是不需要许可的。把本书中大量的用例代码并入到你的产品文档中是需要许可的。

我们赞赏但不强求注明信息来源。一条信息来源通常包括标题、作者、出版者和国际标准书号(ISBN)。例如:“Learning JavaScript Design Patterns by Addy Osmani (O’Reilly). Copyright 2012 Addy Osmani, 978-1-449-33181-8”。

如果你感到对示例代码的使用超出了正当引用或这里给出的许可范围,请随时通过permissions@oreilly.com联系我们。®

Safari在线图书

Safari在线图书(Safari Books Online)是一家按需服务的数字图书馆,提供来自领先出版商的技术类和商业类专业参考书目和视频。

专业技术人员、软件开发人员、Web设计师、商业和创意专家将Safari Books Online作为他们研究、解决问题、学习和认证培训的主要资源。

Safari Books Online为组织、政府机构和个人提供一系列的产品组合和定价计划。用户可以在一个来自各个出版社的可完全搜索的数据库中访问成千上万的书籍、培训视频和正式出版前的手稿,这些出版社包括:O’Reilly Media、Prentice Hall Professional、Addison-Wesley Professional、微软出版社、Sams、Que、Peachpit Press、Focal Press、Cisco Press、John Wiley & Sons、Syngress、Morgan Kaufmann、IBM Redbooks、Packt、Adobe Press、FT Press、Apress、Manning、New Riders、McGraw-Hill、Jones & Bartlett、Course Technology等等。欲获得有关Safari Books Online的更多信息,请在线访问我们。

联系我们

关于本书的建议和疑问,可以与下面的出版社联系:

美国:

O’Reilly Media, Inc.

1005 Gravenstein Highway North

Sebastopol, CA 95472

中国:

北京市西城区西直门南大街2号成铭大厦C座807室(100035)

奥莱利技术咨询(北京)有限公司

我们将关于本书的勘误表,例子以及其他信息列在本书的网页上,网页地址是:

http://www.oreilly.com/catalog/9781449302146

如果要评论本书或者咨询关于本书的技术问题,请发邮件到:

bookquestions@oreilly.com

想了解关于O’Reilly图书、课程、会议和新闻的更多信息,请访问以下网站:

http://www.oreilly.com.cn

http://www.oreilly.com

致谢

我要感谢热情的技术审稿人帮助我检查和改进本书,这些审稿人还包括整个社区里的同行。他们的知识和热情对这一工作做出了不可思议的贡献。这些技术审稿人的官方tweet和博客也经常为我提供一些想法和灵感,我竭诚向大家推荐他们的tweet和博客。

• Nicholas Zakas (http://nczonline.net, @slicknet(http://twitter.com/slicknet))

• Andrée Hansson (http://andreehansson.se, @peolanha(http://twitter.com/peolanha))

• Luke Smith (http://lucassmith.name, @ls_n(http://twitter.com/ls_n))

• Eric Ferraiuolo (http://ericf.me/, @ericf (http://ericf.me/))

• Peter Michaux (http://michaux.ca, @petermichaux)

• Alex Sexton (http://alexsexton.com, @slexaxton(http://twitter.com/slexaxton))

我还要感谢丽贝卡·墨菲(http://rebeccamurphey.com, @rmurphey (http://twitter.com/rmurphey))给我写作的灵感,更重要的是,本书在GitHub和O’Reilly出版公司都可以获取。

最后,我要感谢我的妻子埃莉,她非常支持我的出书工作。第1章介绍

编写易于维护的代码,其中一个最重要方面是能够找到代码中重复出现的主题并优化它们。这也是设计模式有价值的地方。

在本书第1章,我们将探讨可应用于任何编程语言的设计模式发展史和重要性。如果大家已经看过或熟悉这段发展史,可以直接跳到第2章继续阅读。

设计模式来源于土木工程师克里斯托弗·亚历山大(http://en.wikipedia.org/wiki/Christopher_Alexander)的早期作品。他经常发表一些作品,内容是总结他在解决设计问题方面的经验,以及这些知识与城市和建筑模式之间有何关联。有一天,亚历山大突然发现,重复使用这些模式可以让某些设计构造取得我们期望的最佳效果。亚历山大与萨拉-石川佳纯和穆雷·西尔弗斯坦合作创造了一种建筑模式语言,帮助设计师提高自己的设计能力,以解决任何规模的设计和建筑挑战。这就是亚历山大在1977年发表的一篇题为《建筑模式语言》的文章,其后又制作成一本完整的精装书出版(http://www.amazon.co.uk/Pattern-Language-Buildings-Construction-Environmental/dp/0195019199/ref=sr_1_1?s=books&ie=UTF8&qid=1329440685&sr=1-1)。

大约在30年前,软件工程师们开始把亚历山大编写的建筑设计原则纳入首个有关设计模式的文档中,成为初级开发人员改进编程技巧的指南。需要指出的是,设计模式背后的概念实际上自编程行业诞生以来就已经存在了,虽然是以一种不太正式的形式存在。

关于软件工程设计模式的最早和最具代表性的作品是1995年出版的《设计模式:可复用面向对象软件的基础》一书。该书的作者是Erich Gamma(http:// en. wikipedia. org/wiki/Erich_Gamma)、Richard Helm(http://www.amazon. co. uk/Pattern-Language- Buildings-Construction- Environmental/ dp/ 0195019199/ ref= sr_1_ 1?s= books&ie= UTF8&qid=1329440685&sr=1-1)、Ralph Johnson(http://en.wikipedia.org/wiki/ Ralph_ Johnson)和 John Vlissides(http:// en.wikipedia.org/ wiki/ John-Vlissides),他们以“四人组”著称(或简称为GoF)。

GoF发表的著作大大推动了设计模式概念在编程领域中的进一步发展,因为它描述了很多开发技术和误区,并列举了23个面向对象设计中最常用的经典设计模式。我们将在第7章进一步详细介绍这些设计模式。

我们将在本书中了解一些流行的JavaScript设计模式,并探讨为何某些设计模式比其他模式更适用于我们编写的程序。要记住,设计模式不仅适用于原生JavaScript(即标准JavaScript代码),也适用于jQuery和Dojo等抽象库。在开始之前,我们先来了解一下软件设计中“模式”的确切定义。第2章什么是模式

模式是一种可复用的解决方案,可用于解决软件设计中遇到的常见问题,如在我们编写的JavaScript应用程序的实例中。另一种模式的方式是将解决问题的方法制作成模板,并且这些模板可应用于多种不同的情况。

那么,为什么了解和熟悉模式是很重要的?设计模式有三大好处。

模式是已经验证的解决方案。

它们为解决软件开发中遇到的问题提供可靠的方法,也就是使用已经验证的解决方案,这些解决方案体现了开发人员的经验及见解,他们为定义和改进这些方法提供了帮助,从而形成现在的模式。

模式很容易被复用。

模式通常是指一种立即可用的解决方案,可以对其进行修改以满足个人需求。该特性使得这些模式的功能非常强大。

模式富有表达力。

看到模式时,通常就表示有一个设置好的结构和表达解决方案的词汇,以帮助我们非常轻松地表达出所实现的大型解决方案。

模式不是一种确切的解决方案。重要的是,我们要知道模式的作用仅仅是为我们提供一个解决问题的方案。模式无法解决所有的设计问题,也无法取代优秀软件设计师的工作,但模式确实能够支持这些工作。接下来我们将了解一下模式的其他一些优点。

• 复用模式有助于防止在应用程序开发过程中小问题引发大问题。这意味着当我们在已经验证的模式基础上编写代码时,可以在代码结构上少花点时间,从而有更多的时间专注于整体解决方案的质量。这是因为模式支持我们用更结构化和更有组织性的方式编写代码,从而避免以后因代码的整洁性问题而重构代码。

• 模式可以提供通用的解决方案,并且其记录方式不需要与某个特定问题挂钩。这种通用的方法意味着不管现在开发的是哪种应用程序(在许多情况下是指编程语言),设计模式都可用于改进代码的结构。

• 某些模式确实能够通过避免代码复用来减少代码的总体资源占用量。通过鼓励开发人员更密切地关注解决方案中可以即刻减少代码复用的部分,例如,减少类似处理过程的函数数量,用一个广义函数取而代之,那么就可以减小代码库的总大小。这也就是所谓的使代码更加简洁。

• 模式添加到开发人员的词汇中,会使沟通更快速。

• 经常使用的模式可以逐步改进,因为其他开发人员使用这些模式后总结出的共同经验又贡献给了设计模式社区。在某些情况下,这会创造出全新的设计模式,而在其他情况下,会对有关如何更好地使用特定模式的指导做出改进。这样可以确保基于模式的解决方案比临时解决方案更加强大。我们每天都在使用模式

为了了解使用模式的好处,我们来研究一个非常简单的元素选择问题,我们通常是用jQuery库来解决这种问题。

试想,如果我们有一个脚本,想为页面上每个具有“foo”类(class属性)的DOM元素增加一个计数器,查询列表最简单有效的方法是什么?对,有几种不同的方法可以解决这个问题。

• 在页面上选择所有元素并存储。接着过滤该集合并使用正则表达式(或另一种方式)来存储那些具有“foo”类的元素。

• 使用浏览器原生的querySelectorAll()等功能来选择所有具有“foo”类的元素。

• 同样地,使用原生特性getElementsByClassName()等功能来重新获得所需的集合。

那么,哪种方法最快?最快的实际上是第 3种方法,它比其他方法(http:// jsperf. com/getelementsbyclassname-vs-queryselectorall/5)快8至10倍。但在实际应用中, IE9以下的版本不支持第3种方法,因此必须使用第1种方法,而其他方法都行不通。

但使用jQuery的开发人员不必担心这个问题,因为通过使用Facade(外观)模式,它已经被抽象出来了。正如我们将在后面详细介绍的,该模式为若干更复杂的底层代码体提供了一套简单的抽象接口(例如$el.css()和$el.animate())。正如我们所看到的,这意味着我们可以在“实现级”细节上花费更少的时间。

根据现有浏览器的支持范围,jQuery 会在幕后选择最佳的元素选择方式,我们只需要使用抽象层即可。

我们可能都很熟悉jQuery的$(“selector”)。用它选择页面上的HTML元素比需要手动处理的getElementById()、getElementsByClassName()、getElementByTagName等容易得多。

虽然我们知道 querySelectorAll()可以解决这个问题,但让我们来比较一下使用jQuery的Facade(外观)模式接口与自己选择最佳路径这两种方式所花费的精力。根本就不用比!使用设计模式的抽象可以体现真实价值。

我们将在本书的后面章节探讨上述问题并了解更多设计模式。第3章模式状态测试、Proto模式及三法则

请记住,并不是每一种算法、最佳实践或解决方案都代表一种完整的模式。这里可能会有一些遗漏的关键内容,在认真审查之前,设计模式社区通常不会认定它是一种真正的模式。即使展现在我们面前的某种内容似乎符合模式的标准,但也要经过一段时间的审查和多人的测试才能被视为真正的设计模式。

再一次回顾亚历山大所做的工作,他称设计模式应该是一种流程,也是一种“事物”。该定义比较模糊,他接着补充说,设计模式是一种能够创建“事物”的流程。这就是为什么模式通常专注于为视觉可识别结构寻址,即我们应该能够直观地描绘(或绘制)该结构,而且该结构是模式在实践中产生的。

在学习设计模式时,我们不时会碰到“原始模式(proto-pattern)”这个词:一种尚未通过“模式”测试的设计模式。原始模式是值得与社区分享的特殊解决方案,来自社区编程人员的心血,但因为产生的时间短,还没有机会对它们进行严格的审查。

另外,分享设计模式的个人可能没有时间或兴趣审查“模式”流程,可能只是发布原始模式的简单描述。这种类型的模式的简要描述或片段被称为patlet。

完整记录合格模式的相关工作是相当艰巨的。回顾设计模式方面的一些最早期的作品,如果模式可以执行以下操作,就被认为是“优秀”的模式。

• 解决特殊问题。模式不应该只是获取原则或策略,它们需要获取解决方案。这是作为一种优秀模式不可或缺的要素。

• 没有显而易见的解决方案。我们通常会发现,解决问题的技术基本来自众所周知的基本原则。最好的设计模式通常会间接提供解决问题的方案——这被认为是解决与设计相关的最具有挑战性问题的必要方法。

• 描述业经验证的概念。设计模式需要证明它们的作用与描述相一致,并且如果没有证明这一点,就不能认真考虑该设计。如果一种模式实际上是推测性的,那么只有那些勇敢的人才可能尝试使用它。

• 描述一种关系。在某些情况下,看起来可能是一种模式描述了一种类型的模块。尽管一种实现看起来也是这样,但对模式的正式描述必须能够更深入地解释它与代码关系的系统结构和机制。

我们会理所当然地认为一种不符合设计准则的proto模式是不值得学习的,然而,这与事实相差甚远。很多proto 模式其实是非常有用的。这里并不是说所有的proto模式都值得研究,但也有不少被遗忘的有用的原始模式能够在以后的项目中为我们提供帮助。牢记上述内容,发挥最佳判断力,你的选择过程就会很顺利。

成为有效模式的其中一个附加要求是,它们展示一些反复出现的现象。这通常可以限定在至少三个关键领域中,被称为三法则。使用该规则重现,则必须证明如下:

适合性

如何才算是成功的模式?

实用性

为什么模式被视为是成功的?

适用性

一项设计是因为它有着广泛的适用性才能称得上是模式吗?如果是这样,需要解释其原因。第4章设计模式的结构

大家可能很想知道编写模式的作者是如何完成一种新模式的结构概述、实现和目的的。传统上,一种模式最初是以一种规则的形式呈现的,该规则建立下面这样的关系。

• 上下文

• 上下文里产生的元件系统

• 解决元件在上下文中自身问题的配置

基于这一点,现在让我们来看看对设计模式组件元素的总结。一种设计模式应该包括:

模式名称

描述

上下文大纲

模式在上下文中有效,以满足用户的需求。

问题陈述

问题的陈述已解决,因此我们可以理解模式的意图。

解决方案

以可理解的步骤和看法解决用户问题的描述。

设计

模式设计的描述,特别是与它交互的用户行为。

实现

有关如何实现模式的指导

插图

模式中类的可视表示(如图表)

示例

最小形式的模式实现

辅助条件

需要哪些其他模式来支持所描述模式的使用?

关系

这种模式像哪些模式?它是否极力模仿其他模式呢?

已知的用法

是广泛使用的模式?如果是这样,在哪里使用?如何使用?

讨论

团队或者作者对该模式所带来巨大好处的想法。

在创建或维护解决方案时,设计模式是将企业或团队中的所有开发人员拉到同一战线上来的一种非常有效的方法。如果你或你的公司曾考虑研究自己的设计模式,请记住,尽管在规划和设计阶段可能要花费大量的初始成本,但考虑到从投资中获得的价值,这样做也是相当值得的。但不要在研究现有新模式之前总是做深入研究,因为你可能会发现,直接使用或在现有已经验证的设计模式的基础上构建新模式比重新开发一种模式要更加有利。第5章编写设计模式

编写优秀的设计模式是一项具有挑战性的任务。模式不仅需要为最终用户提供大量的参考资料,还需要能够证明自己为何是必要的。

如果已经阅读了前面章节中的“什么是模式”,我们可能会认为当我们在各种地方看到它们的时候,这个定义本身应该能够帮助我们辨别模式。其实这是不完全正确的,对于我们正在查看的一段代码是否遵循一种固定模式或者只是偶然性地像一种模式,这点我们并不是很清楚。

当看到一个我们认为它可能正在使用某种模式的代码体时,我们应考虑写下这段代码里我们认为遵循现有特定模式的某些方面。

在模式分析的很多情况下,我们会发现我们只是在查看遵循好的原则和设计实践的代码,而这些原则和设计实践可能偶然会与模式的规则发生重叠。请记住:没有交互和明确规则的解决方案就不是模式。

如果你对编写自己的设计模式有兴趣,我推荐你向已经经历过这个过程并做得很好的人学习。要花时间从大量不同的设计模式描述中汲取信息,并吸收对自己有意义的知识。

发掘结构和语义——可以通过审查感兴趣模式的交互和上下文来实现,因此我们可以确定有助于在有用配置中将这些模式组织在一起的原则。

一旦熟悉了模式方面的大量信息,我们就可以使用现有的格式来开始编写模式,并且看看自己能否想出新点子来改善它,或将自己的想法融入到里面。

近年来有个人就是这样做的,他是 Christian Heilmann,他采用了现有的模块(Module)模式,并对它做了一些有用的基本修改,以创建揭示模块(Revealing Module)模式(这是本书稍后将讨论的模式之一)。

如果你对创建新设计模式有兴趣,以下是我建议的几个要点。

模式的实用性有多少?

确保模式描述的是能够解决重复出现的问题的业经验证的解决方案,而不是未经验证的推测性解决方案。

牢记最佳实践。

作出的设计决策应该基于通过对最佳实践的理解而获得的原则。

设计模式对于用户来说应该是透明的。

设计模式对于任何类型的用户体验都应是完全透明的。它们主要是为使用它们的开发人员提供服务,而不应强制改变用户体验的行为,不使用模式,这些事情将不会发生。

要记住独创性在模式设计中不是重点。

编写模式时,我们不需要是已有解决方案的最初发现者,也不必担心我们的设计有一小部分与其他模式有重叠。如果我们的方法很强大,有广泛的适用性,那么它就有可能被认定为是一个有效的模式。

模式需要一批有说服力的示例。

好的模式描述需要伴随着一系列同样强有力的示例,以演示所编写模式的成功应用。为了展示模式的广泛应用,举出能够展示良好设计原则的示例是比较理想的。

编写模式是要在创建通用、具体及有用的设计方面找到一种细致的平衡。要努力确保所编写的模式能够覆盖最广泛的应用领域。希望本篇编写模式的简要介绍能够为大家提供一些见解,以帮助大家进一步学习本书后面的章节。第6章反模式

如果我们认为一种模式代表一种最佳实践,那么一种反模式就代表我们已经学到的教训。反模式这个术语是1995年由安德鲁·凯尼格在当年的11月C++报告中创造的,是受“四人组”所著《设计模式》一书的启发。在凯尼格的报告中,他提出了反模式的两个概念。反模式是:

• 描述一种针对某个特定问题的不良解决方案,该方案会导致糟糕的情况发生;

• 描述如何摆脱前述的糟糕情况以及如何创造好的解决方案。

关于这个话题,亚历山大写到了有关实现在优秀设计结构和优秀上下文之间取得良好平衡所面临的困难:

这些要点是关于设计的过程;以及创造展示新物理顺序、组织和形式的物理对象的过程,与功能相对应。……每一个设计问题都是以在两个实体之间实现平衡为开始的,即:问题的形式和它的上下文。形式是解决问题的方案;上下文定义该问题。

虽然了解设计模式很重要,但理解反模式也同样很重要。让我们来探究其原因。创建应用程序时,一个项目的生命周期就会以此为起点;一旦完成了初始版本,就需要进行维护。最终方案的质量好坏取决于技能水平和团队投入的时间。这里的好坏是在上下文中考虑的,如果一个“完美的”设计应用于错误的上下文中,那么它就可能是一种反模式。

应用程序在进入生产环境并即将进入维护模式时会面临更大的挑战。之前没有研究过该应用程序的开发人员,在这样的系统上工作,可能会将不良设计意外地引入到项目中。如果说将不良实践创建为反模式,则能让开发人员有办法提前识别这些反模式,这样就可以避免常见错误的发生,它与下面这个方法相类似:设计模式为我们提供了识别有用途通用技术的方法。

总的来说,反模式是一种值得记录的不良设计。JavaScript中的反模式示例如下所示。

• 在全局上下文中定义大量的变量污染全局命名空间。

• 向setTimeout或setInterval传递字符串,而不是函数,这会触发eval()的内部使用。

• 修改Object类的原型(这是一个特别不良的反模式)。

• 以内联形式使用JavaScript,它是不可改变的。

• 在使用 document.createElement 等原生 DOM方法更合适的情况下使用document.write。多年以来 document.write 一直都是在被严重滥用,并有相当多的缺点,包括:如果在页面加载完成后执行 document.write,它实际上会重写我们所在的页面,而document.createElement则不会。访问此网站(http:// jsfiddle. net/addyosmani/6T9vX/)可以看到它运行的实例。document.write 也无法与XHTML相适,这是选择像document.createElement这样更为DOM友好的方法比较有利的另一个原因。

了解反模式是成功的关键。一旦能够辨别这种反模式,我们将能够重构代码来防止它们的出现,这样我们解决方案的总体质量就能够立即得到改善。第7章设计模式类别

来自知名设计书籍的一个词汇——领域驱动术语,是这样描述的:

设计模式命名、抽象和标识是通用设计结构的主要方面,这些设计结构能被用于构造可复用的面向对象设计。设计模式确定所包含的类和实例、它们的角色、协作方式以及职责分配。

每一种设计模式都重点关注一个特定的面向对象设计问题或设计要点,描述何时使用它,在另一些约束条件下是否还能使用,以及使用的效果和利弊。由于我们最终要实现设计,设计模式还提供了示例……代码来阐明其实现。

虽然设计模式描述的是面向对象设计,但它们都基于实际的解决方案,这些方案的实现语言是主流面向对象的编程语言。

可以将设计模式划分成很多不同的类别。在这一节中,我们将回顾其中三种类别,并在详细探索具体类别之前,简要举出一部分这些类别的设计模式示例。

1.创建型设计模式

创建型设计模式专注于处理对象创建机制,以适合给定情况的方式来创建对象。创建对象的基本方法可能导致项目复杂性增加,而这些模式旨在通过控制创建过程来解决这种问题。

属于这个类别的模式包括:Constructor(构造器)、Factory(工厂)、Abstract(抽象)、Prototype(原型)、Singleton(单例)和Builder(生成器)。

2.结构型设计模式

结构型模式与对象组合有关,通常可以用于找出在不同对象之间建立关系的简单方法。这种模式有助于确保在系统某一部分发生变化时,系统的整个结构不需要同时改变。同时对于不适合因某一特定目的而改变的系统部分,这种模式也能够帮助它们完成重组。

属于这个类别的模式包括:Decorator(装饰者)、Facade(外观)、Flyweight(享元)、Adapter(适配器)和Proxy(代理)。

3.行为设计模式

行为模式专注于改善或简化系统中不同对象之间的通信。

行为模式包括:Iterator(迭代器)、Mediator(中介者)、Observer(观察者)和Visitor (访问者)。第8章设计模式分类

根据我早期学习设计模式的经历,我个人发现表 8-1 中的内容能够非常有效地提醒我们这些模式能够提供什么。它涵盖“四人组”提到的23种设计模式。原始表格是由艾丽丝·尼尔森在2004年总结,我对一些必要的地方进行了修改,以使它与我们在本节中的讨论内容相符。

我推荐大家将该表格作为参考,但是要记住,这里还有很多未提及的其他模式,本书稍后将讨论这些模式。有关类(Class)的要点

要记住,该表格中将会有“类”的概念。JavaScript 是一种无类语言,但可以使用函数来模拟类。

最常用的实现方法是定义一个JavaScript函数,然后使用new关键字创建新对象。使用this来定义对象的新属性和方法,如下所示:

//一个Car "class"

function Car(model) {

 this.model = model;

 this.color = "silver";

 this.year = "2012";

 this.getInfo = function () {

   return this.model + " " + this.year;

 };

}

然后可以像这样使用我们上面定义的car构造函数来实例化该对象:

var myCar = new Car("ford");

myCar.year = "2010";

console.log(myCar.getInfo());

欲了解更多使用JavaScript来定义“类”的方法,请查看斯托扬·斯蒂凡诺夫发布的帖子(http://www.phpied.com/3-ways-to-define-a-javascript-class/)。

让我们继续来查看此表。表8-1续表第9章JavaScript设计模式

在本章中,我们将探索一些经典与现代设计模式的JavaScript实现。

开发人员通常想知道他们是否应该在工作中使用一种“理想”的模式或模式集。这个问题没有明确的唯一答案,我们研究的每个脚本和 Web应用程序可能都有它自己的个性化需求,我们需要思考模式的哪些方面能够为实现提供实际价值。

例如,一些项目可能会受益于观察者模式提供的解耦好处(这可以减少应用程序的某些部分对彼此的依赖度),而有些项目可能只是因为太小,而根本无需考虑解耦。

也就是说,一旦我们牢牢掌握了设计模式和与它们最为相配的具体问题,那么将它们集成到我们的应用程序架构中就会变得更加容易。

在本节中将要探索的模式包括:

• Constructor(构造器)模式;

• Module(模块)模式;

• Revealing Module(揭示模块)模式;

• Singleton(单例)模式;

• Observer(观察者)模式;

• Mediator(中介者)模式;

• Prototype(原型)模式;

• Command(命令)模式;

• Facade(外观)模式;

• Factory(工厂)模式;

• Mixin(混入)模式;

• Decorator(装饰者)模式;

• Flyweight(享元)模式。9.1 Constructor(构造器)模式

在经典面向对象编程语言中,Constructor是一种在内存已分配给该对象的情况下,用于初始化新创建对象的特殊方法。在JavaScript中,几乎所有东西都是对象,我们通常最感兴趣的是object构造器。

Object构造器用于创建特定类型的对象——准备好对象以备使用,同时接收构造器可以使用的参数,以在第一次创建对象时,设置成员属性和方法的值(见图9-1)。图9-1 Constructor(构造器)模式9.1.1 对象创建

在JavaScript中,创建新对象的两种常用方法如下所示:

//下面每种方式都将创建一个新的空对象

var newObject = {};

// object构造器的简洁记法

var newObject = new Object();

在 Object 构造器为特定的值创建对象封装,或者没有传递值时,它将创建一个空对象并返回它。

有四种方法可以将键值赋值给一个对象:

// ECMAScript 3兼容方式

// 1.“点”语法

// 设置属性

newObject.someKey = "Hello World";

// 获取属性

var key = newObject.someKey;

// 2.中括号语法

// 设置属性

newObject["someKey"] = "Hello World";

// 获取属性

var key = newObject["someKey"];

//只适用ECMAScript 5的方式

// 更多信息查看:http://kangax.github.com/es5-compat-table/

// 3. Object.defineProperty

 // 设置属性

Object.defineProperty(newObject, "someKey", {

  value: "for more control of the property's behavior",

  writable: true,

  enumerable: true,

  configurable: true

});

// 如果上面的看着麻烦,可以使用下面的简便方式

var defineProp = function (obj, key, value) {

 config.value = value;

 Object.defineProperty(obj, key, config);

};

// 使用上述方式,先创建一个空的person对象

var person = Object.create(null);

// 然后设置各个属性

defineProp(person, "car", "Delorean");

defineProp(person, "dateOfBirth", "1981");

defineProp(person, "hasBeard", false);

//4.Object.defineProperties

// 设置属性

Object.defineProperties(newObject, {

 "someKey": {

   value: "Hello World",

   writable: true

 },

  "anotherKey": {

   value: "Foo bar",

   writable: false

 }

});

// 可以用1和2中获取属性的方式获取3和4方式中的属性

正如我们将在本书稍后看到的,这些方法甚至可以用于继承,如下所示:

// 用法

// 创建赛车司机driver对象,继承于person对象

var driver = Object.create(person);

// 为driver设置一些属性

defineProp(driver, "topSpeed", "100mph");

// 获取继承的属性

console.log(driver.dateOfBirth);

// 获取我们设置的100mph的属性

console.log(driver.topSpeed);9.1.2 基本Constructor(构造器)

正如我们在前面所看到的,JavaScript 不支持类的概念,但它确实支持与对象一起用的特殊 constructor(构造器)函数。通过在构造器前面加 new关键字,告诉JavaScript像使用构造器一样实例化一个新对象,并且对象成员由该函数定义。

在构造器内,关键字this引用新创建的对象。回顾对象创建,基本的构造器看起来可能是这样的:

function Car(model, year, miles) {

 this.model = model;

 this.year = year;

 this.miles = miles;

 this.toString = function () {

  return this.model + " has done " + this.miles + " miles";

 };

}

// 用法

// 可以创建car的新实例

var civic = new Car("Honda Civic", 2009, 20000);

var mondeo = new Car("Ford Mondeo", 2010, 5000);

// 打开浏览器控制台,查看这些对象上调用的toString()方法的输出

console.log(civic.toString());

console.log(mondeo.toString());

上面是一个简单的构造器模式版本,但它确实有一些问题。其中一个问题是,它使继承变得困难,另一个问题是,toString()这样的函数是为每个使用Car构造器创建的新对象而分别重新定义的。这不是最理想的,因为这种函数应该在所有的Car类型实例之间共享。

值得庆幸的是,因为有很多ES3和ES5兼容替代方法能够用于创建对象,所以很容易解决这个限制问题。9.1.3 带原型的Constructor(构造器)

JavaScript中有一个名为prototype的属性。调用JavaScript构造器创建一个对象后,新对象就会具有构造器原型的所有属性。通过这种方式,可以创建多个Car对象,并访问相同的原型。因此我们可以扩展原始示例,如下所示:

function Car(model, year, miles) {

 this.model = model;

 this.year = year;

 this.miles = miles;

}

// 注意这里我们使用Object.prototype.newMethod而不是Object.prototype是为了避免重新定义prototype对象

Car.prototype.toString = function () {

 return this.model + " has done " + this.miles + " miles";

};

 //用法:

  var civic = new Car("Honda Civic", 2009, 20000);

  var mondeo = new Car("Ford Mondeo", 2010, 5000);

 console.log(civic.toString());

 console.log(mondeo.toString());

现在toString()的单一实例就能够在所有Car对象之间共享。9.2 Module(模块)模式

模块是任何强大应用程序架构中不可或缺的一部分,它通常能够帮助我们清晰地分离和组织项目中的代码单元。

在JavaScript中,有几种用于实现模块的方法,包括:

• 对象字面量表示法

• Module模式

• AMD模块

• CommonJS模块

• ECMAScript Harmony模块

我们稍后将在本书第 11 章探索后三种方法。Module 模式在某种程度上是基于对象字面量,因此首先重新认识对象字面量是有意义的。9.2.1 对象字面量

在对象字面量表示法中,一个对象被描述为一组包含在大括号({})中、以逗号分隔的name/value 对。对象内的名称可以是字符串或标识符,后面跟着一个冒号。对象中最后的一个name/value对的后面不用加逗号,如果加逗号将会导致出错。

var myObjectLiteral = {

 variableKey: variableValue,

 functionKey: function () {

   // ...

 }

};

对象字面量不需要使用 new 运算符进行实例化,但不能用在一个语句的开头,因为开始的可能被解读为一个块的开始。在对象的外部,新成员可以使用如下赋值语句添加到对象字面量上,如:myModule.property = "someValue";

下面我们可以看到一个更完整的示例:使用对象字面量表示法定义的模块:

var myModule = {

myProperty: "someValue",

// 对象字面量可以包含属性和方法

// 例如,可以声明模块的配置对象

myConfig: {

 useCaching: true,

 language: "en"

},

// 基本方法

myMethod: function () {

 console.log("Where in the world is Paul Irish today?");

},

// 根据当前配置输出信息

myMethod2: function () {

 console.log("Caching is:" + (this.myConfig.useCaching) ? "enabled" : "disabled");

},

// 重写当前的配置

myMethod3: function (newConfig) {

 if (typeof newConfig === "object") {

   this.myConfig = newConfig;

  console.log(this.myConfig.language);

 }

}

};

//输出:Where in the world is Paul Irish today?

myModule.myMethod();

//输出:enabled

myModule.myMethod2();

//输出:fr

myModule.myMethod3({

 language: "fr",

 useCaching: false

});

使用对象字面量有助于封装和组织代码,如果想进一步了解有关对象字面量的信息,丽贝卡·墨菲曾对这一主题进行了深入解析,可阅读其文章进行了解(地址:http://rmurphey.com/blog/2009/10/15/using-objects-to-organize-your-code/)。

也就是说,如果我们选择了这种技术,我们可能同样也对Module模式感兴趣。它仍然使用对象字面量,但只是作为一个作用域函数的返回值。9.2.2 Module(模块)模式

Module模式最初被定义为一种在传统软件工程中为类提供私有和公有封装的方法。

在JavaScript中,Module模式用于进一步模拟类的概念,通过这种方式,能够使一个单独的对象拥有公有/私有方法和变量,从而屏蔽来自全局作用域的特殊部分。产生的结果是:函数名与在页面上其他脚本定义的函数冲突的可能性降低(见图9-2)。图9-2 Module模式

9.2.2.1 私有

Module模式使用闭包封装“私有”状态和组织。它提供了一种包装混合公有/私有方法和变量的方式,防止其泄露至全局作用域,并与别的开发人员的接口发生冲突。通过该模式,只需返回一个公有API,而其他的一切则都维持在私有闭包里。

这为我们提供了一个屏蔽处理底层事件逻辑的整洁解决方案,同时只暴露一个接口供应用程序的其他部分使用。该模式除了返回一个对象而不是一个函数之外,非常类似于一个立即调用的函数表达式1。1

注释: IIFE(http: //benalman. com/news/2010/11/immediately-invoked- function- expression/)。请参阅第200页的“命名空间模式”了解更多信息。

应该指出的是,在 JavaScript 中没有真正意义上的“私有”,因为不像有些传统语言,JavaScript没有访问修饰符。从技术上来说,我们不能称变量为公有或是私有,因此我们需使用函数作用域来模拟这个概念。在Module模式内,由于闭包的存在,声明的变量和方法只在该模式内部可用。但在返回对象上定义的变量和方法,则对外部使用者都是可用的。

9.2.2.2 历史

从历史的角度来看,Module模式最初是在2003年由多人共同开发出来的,其中包括理查德•康佛德(http:// groups.google. com/group/ comp.lang. javascript/ msg/9f58bd11bd67d937)。后来由道格拉斯·克劳克福德在其讲座中推广开来。除此之外,如果你曾体验过雅虎的 YUI 库,它的一些特性看起来可能相当熟悉,原因是在创建它们的组件时,Module模式对YUI有很大的影响。

9.2.2.3 示例

让我们通过创建一个自包含的模块来看一下Module模式的实现。

var testModule = (function () {

 var counter = 0;

 return {

  incrementCounter: function () {

   return ++counter;

  },

  resetCounter: function () {

   console.log("counter value prior to reset: " + counter);

   counter = 0;

   }

 };

})();

//用法:

//增加计数器

testModule.incrementCounter();

// 检查计数器值并重置

//输出:1

testModule.resetCounter();

在这里,代码的其他部分无法直接读取incrementCounter()或resetCounter()。counter变量实际上是完全与全局作用域隔离的,因此它表现得就像是一个私有变量,它的存在被局限于模块的闭包内,因此唯一能够访问其作用域的代码就是这两个函数。上述方法进行了有效的命名空间设置,所以在测试代码中,所有的调用都需要加上前缀(如:“testModule”)。

使用Module模式时,可能会觉得它可以用来定义一个简单的模板来入门使用。下面是一个包含命名空间、公有和私有变量的Module模式:

var myNamespace = (function () {

// 私有计数器变量

var myPrivateVar = 0;

// 记录所有参数的私有函数

var myPrivateMethod = function (foo) {

 console.log(foo);

 };

return {

 // 公有变量

  myPublicVar: "foo",

 // 调用私有变量和方法的公有函数

 myPublicFunction: function (bar) {

  // 增加私有计数器值

  myPrivateVar++;

  // 传入bar调用私有方法

  myPrivateMethod(bar);

 }

};

})();

来看另一个示例,我们可以看到一个使用这种模式实现的购物车。模块本身是完全自包含在一个被称为basketModule的全局变量中。模块中的basket数组是私有的,因此应用程序的其他部分无法直接读取它。它只与模块的闭包一起存在,所以能够访问它的方法都是那些能够访问其作用域的方法(即addItem()、getItem()等)。

var basketModule = (function () {

 // 私有

 var basket = [];

 function doSomethingPrivate() {

 //...

}

function doSomethingElsePrivate() {

 //...

}

// 返回一个暴露出的公有对象

return {

 // 添加item到购物车

 addItem: function (values) {

  basket.push(values);

 },

 // 获取购物车里的item数

 getItemCount: function () {

   return basket.length;

 },

 // 私有函数的公有形式别名

 doSomething: doSomethingPrivate,

 // 获取购物车里所有item的价格总值

 getTotal: function () {

  var itemCount = this.getItemCount(),

   total = 0;

  while (itemCount--) {

   total += basket[itemCount].price;

  }

  return total;

 }

};

})();

在该模块中,可能已经注意到返回了一个object。它会被自动赋值给basketModule,以便我们可以与它交互,如下所示:

// basketModule返回了一个拥有公用API的对象

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载