前端开发学习总结

最后更新于:2022-04-01 22:08:47

[TOC] ###2016-05学习笔记 ####1.eavl()函数可计算某个字符串,并执行其中的的 JavaScript 代码 当返回参数需要当成JS代码来执行的时候可以使用该函数。 ####2.纯数字在div层里无法自动换行 解决方案:在div层样式加入 ``` word-wrap:break-word; word-break:break-all; ``` ####3.arguments属性 ``` $.preloadSound = function () { //arguments批量获取所有函数传递内容成数组对象 for (var i = 0; i < arguments.length; i++) { $('
';

学习JavaScript设计模式

最后更新于:2022-04-01 22:08:44

学习JavaScript设计模式是下发布知识共享署名-非商业性使用-禁止演绎3.0 unported许可证。它可用于购买通过O'Reilly的媒体,但仍然可为免费在线和物理(或电子书)购买为希望支持该项目的读者。 学习JavaScript的设计模式书的封面 这个有帮助吗? 我们希望你写评论。 前言 设计模式是经常发生的软件设计问题的可重用的解决方案。他们是既令人兴奋又一个引人入胜的话题,在任何编程语言探索。 这其中的一个原因是,他们帮助我们建立在我们面前来了众多开发商的经验相结合,确保我们构建我们的代码以优化的方式,满足我们正在试图解决问题的需要。 设计模式也为我们提供了一个共同的词汇来形容的解决方案。这可以比描述的语法和语义,当我们试图传达构建在代码形式对他人的溶液的方式显著简单。 在这本书中,我们将探讨同时应用古典与现代的设计模式,以JavaScript编程语言。 目标听众 这本书是针对希望提高自己的设计模式的知识,以及他们如何可以应用到JavaScript编程语言的专业开发人员。 一些涉及的概念(闭包,原型继承)将承担基本的先验知识和理解的程度。如果你发现自己需要进一步了解这些主题,提供了方便建议标题的列表。 如果您想了解如何编写漂亮,结构和组织代码,我相信这是你的书。 致谢 我将永远为有才华的技术评论家谁帮助审查和提高这本书,包括来自社会大众表示感谢。他们带来了该项目的知识和热情是简直太神奇了。官方的技术评审微博和博客也是双方想法和灵感常规来源,我衷心建议检查出来。 尼古拉斯Zakas(http://nczonline.net,@slicknet) 汉森的Andrée(http://andreehansson.se,@peolanha) 卢克·史密斯(http://lucassmith.name,@ls_n) 埃里克Ferraiuolo(http://ericf.me/,@ericf) 彼得·米肖(http://michaux.ca,@petermichaux) 亚历克斯·塞克斯顿(http://alexsexton.com,@slexaxton) 我还要感谢丽贝卡·墨菲(http://rmurphey.com,@rmurphey)提供灵感来写这本书,更重要的是,继续使它既可以在GitHub上,并通过奥赖利。 最后,我要感谢我出色的妻子埃莉,为所有她的支持,而我是这个刊物放在一起。 积分 虽然有些本书介绍的模式根据个人经验的顺利实施,其中许多人先前已经通过JavaScript社区标识。这个工作是这样生产的一些开发商的综合经验。类似斯托扬斯特凡的逻辑的办法来防止叙事中断与学分(在JavaScript的模式),我所列举的学分,并建议阅读覆盖在参考部分的任何内容。 如果有任何文章或链接已在引用列表中遗漏了,请接受我诚挚的歉意。如果你与我联系,我一定会对其进行更新,包括你在名单上。 读 虽然这本书是针对初学者和中级开发人员,则假定JavaScript的基本面有一个基本了解。如果您想了解更多关于语言,我很乐意为您推荐以下商品: JavaScript的:权威指南由大卫·弗拉纳根 雄辩的JavaScript由Marijn Haverbeke JavaScript的模式由斯托扬斯特凡 编写可维护的JavaScript由Nicholas Zakas JavaScript的:好零件由Douglas Crockford的 目录 介绍 什么是模式? “模式”-ity测试,始祖模式与三国规则 设计模式的结构 写作设计模式 反模式 分类设计模式 汇总表设计模式分类的 JavaScript的设计模式 构造模式 模块模式 揭示模块模式 Singleton模式 观察者模式 调解模式 原型模式 命令模式 门面模式 工厂模式 混入模式 装饰图案 享元模式 JavaScript的MV *模式 MVC模式 MVP模式 MVVM模式 现代模块化的JavaScript设计模式 AMD CommonJS的 ES和谐 设计模式的jQuery 组合模式 适配器模式 门面模式 观察者模式 迭代器模式 延迟初始化模式 代理模式 Builder模式 jQuery插件设计模式 JavaScript的命名空间模式 结论 参考 简介 一个编写维护的代码的最重要的方面是能够注意到在该代码的重复出现的主题,并对其进行优化。这是一个领域的设计模式的知识可以证明非常宝贵的。 在这本书的第一部分,我们将探讨这真的可以适用于任何编程语言设计模式的历史和重要性。如果你已经卖了或熟悉这段历史,随意跳到章“ 什么是模式? ” 继续读书。 设计模式可以追溯到名为建筑师的早期作品克里斯托弗·亚历山大。他会经常写出版了他在解决设计问题的经验,以及它们如何与建筑和城镇。有一天,它发生在亚历山大的再次使用时间和时间,一定的设计结构导致所需的最佳的效果。 在与Sara石川和穆雷西尔弗斯坦合作,亚历山大生产的模式语言,这将有助于赋予希望设计和建造在任何规模的任何人。这是在题为“模式语言”一文,后来发布了作为一个完整的精装发布回在1977年的书。 大约30年前,软件工程师的开始将亚历山大写了关于进入设计模式,这是新手开发商希望提高自己的编程技巧指导的第一个文件的原则。需要注意的是背后的设计模式的概念其实一直围绕在编程行业自成立以来,尽管在一个不太正式的形式是非常重要的。 一个软件工程的设计模式出版了第一本,可以说是最具代表性的作品正式的一本书于1995年被称为设计模式:元素可复用面向对象软件的。这是写的埃里希·伽马,理查德头盔,拉尔夫·约翰逊和约翰Vlissides -这被称为四人帮(或GoF的简称)一组。 GoF的的出版被认为是相当的工具来进一步推设计模式的概念,在我们的领域,因为它描述了一些开发技术和陷阱,以及提供23核心的面向对象的设计模式,当今世界各地的频繁使用。我们将在部分“设计模式的分类”更详细地覆盖这些图案。 在这本书中,我们将看看一些流行的JavaScript的设计模式,并探讨为什么某些模式可能更适合你的项目比其他人。请记住,模式可以应用于不只是香草的JavaScript(即标准JavaScript代码),而且还抽象库,如jQuery的或道场为好。在我们开始之前,让我们来看看在软件设计中的“模式”的确切定义。 什么是模式? 一种模式是可以适用于经常发生在软件设计问题的可重复使用的解决方案 - 在我们的案例 - 在编写JavaScript的Web应用程序。看着模式的另一种方式是为我们如何解决问题的模板 - 那些可以在很多不同的情况下使用。 那么,为什么是重要的理解模式和熟悉他们?设计模式有三个主要优势: 模式是行之有效的解决方案:他们使用反映的经验和见解,帮助定义它们带到模式开发成熟的技术提供软件开发固态方法来解决问题。 模式可以方便地重复使用:一个模式通常反映了一个开箱即用的解决方案,可以调整,以适应自己的需求。这一特性使得它们相当强劲。 模式可以表现:当我们看一个模式通常有一组结构和词汇所提出的解决方案,可以帮助快递相当大的解决方案,非常典雅。 模式是不是一个确切的解决方案。大家记住一个模式的作用仅仅是为我们提供一个解决方案,方案是非常重要的。模式不能解决所有的设计问题,也不会取代优秀的软件设计师,然而,他们不支持他们。接下来,我们来看看其他一些优势模式所提供的。 重用模式有助于防止可能会导致在应用程序开发过程中的重大问题,小问题。这意味着,当代码是建立在成熟的模式,我们能负担得起花费更少的时间担心我们的代码和更多的时间专注于结构我们的整体解决方案的质量。这是因为模式可以鼓励我们以更结构化和有组织的方式避免了需要重构其在未来清洁目的的代码。 图案可提供广义解这是在一个方式记载,并不要求它们被连接到一个特定的问题。这广义方法意味着不管应用程序(以及在许多情况下,程序设计语言),我们用,设计模式工作可应用于提高我们的代码的结构。 某些模式可以通过避免重复实际上减少了代码的整体文件大小的足迹。通过鼓励开发人员在他们的领域的解决方案更密切地关注其中即时减少重复可以制成,例如减少的功能进行支持类似的进程数单个广义的功能,我们的代码库的整体尺寸可以减小。这也被称为使代码更干。 模式添加到开发人员的词汇,这使得通信速度更快。 频繁被通过利用使用这些模式的集体的经验的其他开发人员使用可以随时间改进型态有助于回到设计图案的社区。在一些情况下,这导致创建的全新的设计模式,而在其他情况可以导致提供关于具体怎么图案可以用最好的改进的准则。这可以确保基于模式的解决方案,继续变得更加健壮比特设解决方案可能。 我们已经日常使用模式 要了解有用的模式怎么可以,让我们回顾了jQuery库解决了我们一个非常简单的元素选择问题。 试想一下,我们在那里有一个页面上发现类“foo”的每个DOM元素,我们希望递增计数器的脚本。什么是查询该集合元素的最有效方法是什么?嗯,有几个不同的方法解决此问题可以解决: 选择中的所有页面的元素,然后存储到它们的引用。接下来,该过滤器收集和使用正则表达式(或其他方式)只存储那些与类“富”。 使用现代的原生浏览器的功能,如querySelectorAll()选择所有与类“foo”的元素。 使用本机的功能,如getElementsByClassName()以同样找回所需的集合。 那么,这些选项中是最快的?它实际上是由8-10倍的一个因素选项3. 替代品。然而,在现实世界的应用程序,将3不会在Internet Explorer 9以下版本的工作,因此使用方法1.其中两个2和3不支持的必要。 使用jQuery的开发人员不必担心这个问题但是,由于它使用的幸运抽象出来给我们正面的图案。正如我们将在后面详细审查,这种模式提供了一套简单的抽象接口(如$el.css(),$el.animate())的代码几个比较复杂的底层机构。正如我们所看到的,这意味着不必关心执行层面的细节较少的时间。 在幕后,图书馆只需选择采用,取决于什么我们当前的浏览器支持的元素,我们只是消耗抽象层选择最优化的方法。 我们很可能是全部也熟悉jQuery的$("selector")。这是显著更易于使用的页面上选择的HTML元素与具有手动选择getElementById(),getElementsByClassName(),getElementByTagName()等。 虽然我们知道,querySelectorAll()试图解决这个问题,比较了使用jQuery的门面接口与选择最优化的路径选择自己的努力。有没有比赛!使用模式的抽象可以提供真实世界的价值。 我们将在这个多的设计模式后来在书中寻找。 “模式”-ity测试,始祖模式与三国规则 请记住,不是每一个算法,最佳实践或解决方案代表了什么可能被视为一个完整的图案。这里可能有几个关键的成分,缺少和花纹社会普遍警惕一些自称为一个,除非它已经严重审核。因此即使被呈现给我们哪些显示满足标准的模式,它不应该被视为一个,直到它已被他人接受审查和检验的适当的周期。 回首工作由Alexander一次,他声称模式都应该是一个过程,一个“东西”。这个定义,因为他遵循说,这是应建立“一事一议”的过程是有意钝。这是有原因的图案一般集中于寻址视觉识别结构即,我们应当能够目视描绘(或绘制),表示放置图案付诸实践的结果中的结构的图像。 在学习设计模式,它不是不规则碰到术语“原型模式”。这是什么?好,这尚未已知要通过“模式”-ity测试图案通常被称为原图案。原模式可能导致已建立一个特定的解决方案,是值得与社区分享的人的工作,但可能尚未有已审核大量的机会,因为它非常年轻的年龄。 另外,共享模式的个人(S)可能没有时间或通过“模式”-ity过程持续的兴趣,并可能释放他们的原型模式的一个简短描述来代替。简要描述或这种类型的图案的片段被称为patlets。 参与记录完全合格的模式工作可以说是相当艰巨的。在一些设计模式的领域最早的工作回眸,一个模式可以被认为是“好”,如果它执行以下操作: 解决一个具体问题:模式不应该只是捕捉原则或策略。他们需要捕捉解决方案。这是一个良好的图案最重要的成分之一。 这个问题的解决方案不能明显:我们可以发现,解决问题的技术通常试图从公知的第一原理得出。最好的设计模式通常的问题提供解决方案,间接地-这被认为是一个必要的方式来设计相关的最具挑战性的问题。 所描述的概念必须已经证明:设计模式需要证明,他们所描述的,没有这方面的证据设计不能被认真考虑的功能。如果一个模式在本质上是高度投机性,只有勇敢可以尝试使用它。 它必须说明的关系:在某些情况下,可能出现的一个模式描述一种类型的模块。尽管实现可能会出现这样的格局的正式说明必须描述更深的系统结构和机制,解释其代码的关系。 我们会误以为这不符合准则原型模式是不值得从然而,学习,这是与事实不符。许多原模式实际上是相当不错的。我不是说所有的原模式是值得看的,但也有在野外不少有用的,可以帮助我们与未来的项目。使用最好的判断与考虑到上述列表中,您会在您的选择过程罚款。 一对才有效模式的附加 ​​要求是,它们显示一些经常性的现象。这往往是东西,可以至少在三个关键领域,被称为合格三个规则。要使用这个规则表明复发,必须证明: 目的健身 -如何图案认为是成功的? 实用性 -为什么格局认为是成功的? 适用性 -是设计不愧是一个模式,因为它具有更广泛的适用性?如果是这样,这需要进行说明。在审查或定义一个模式,它保持上述的一点是重要的。 设计模式的结构 你可能会好奇如何的模式作者可能接近新格局的概括结构,实施和目的。图案最初呈现的形式规则的规定之间的关系: 一个背景 的系统力量的产生在这方面和 一个配置,使这些力量来解决自己的上下文 考虑到这一点,现在让我们来看看一个设计模式构成要素的总结。设计模式应该有: 图案名称和描述 语境的轮廓 -在这种模式有效地应对用户需求的上下文。 问题陈述 -问题的声明得到解决,所以我们可以理解模式的意图。 解决方案 -如何在用户的问题在步骤和看法可以理解的名单正在解决的描述。 设计 -在特定的图案的设计和说明,用户在与它相互作用的行为 实施 -指导,以模式将如何实施 插图 -类模式中的可视化表示形式(如示意图) 例子 -该模式中一个最小的形式实现 联合必要条件 -可能需要什么其他的模式被描述为支持使用模式? 关系 -这是否类似的模式是什么模式?它紧密地模仿任何其他方面? 已知的使用 -在正在使用的模式野性?如果是这样,在哪里以及如何? 讨论 -团队或作者的想法上的图案的令人激动的好处 设计模式是一个相当强大的方法来创建或维护解决方案时,让所有的开发人员在一个组织或团队在同一页上。如果考虑到你自己的图案时,请记住,虽然他们可能在规划一个沉重的初始成本和写了阶段,从投资的返回值可以是相当值得的。总是在新的模式工作然而,你可能会发现它更有利于对现有成熟的模式重新相比起上面使用或创建之前摸底调研。 写作设计模式 这本书虽然是针对这些新的设计模式,一个设计模式是怎么写的一个基本的了解可以提供一些有用的好处。首先,我们可以为为什么需要一个模式背后的原因有更深刻的赞赏。我们还可以学习如何判断审查其用于我们自己的需要,当一个模式(或原图案)是达到标准。 编写好的模式是一项艰巨的任务。模式不仅需要(理想)提供面向最终用户的参考材料的数量可观,但他们还需要能够保卫他们为什么是必要的。 看了上一节什么的模式是,我们可能会认为,这本身就足以帮助我们确定我们在野外看到的模式。这其实并不完全正确。如果一段代码,我们正在寻找以下是一组的模式,或只是偶然碰巧出现像它它并不总是很清楚。 当我们正在寻找的代码体,我们认为可能会使用一个模式,我们应该考虑写下一些代码方面,我们相信一个特定的现有格局下跌倒或设置模式。 在模式分析的很多情况下,我们可以发现,我们只是在看后面可能发生的与意外模式的规则重叠好的原则和设计实践的代码。记住-在没有交流,也没有定义的规则出现的解决方案是不是模式。 如果有意冒险下来写你自己的设计模式,我建议从其他人谁已经通过的过程中学习的路径,并把事情做好。花时间吸收来自许多不同的设计模式描述的信息,并采取什么实际意义。 探索结构和语义 - 这可以通过检查交互和你有兴趣,所以你可以找出协助在有用的配置组织这些模式共同原则的模式方面进行。 一旦我们暴露自己,丰富的图案上的文献信息,我们不妨使用开始编写我们的模式存在的格式,并看看我们是否能够改善它,或者在那里整合我们的想法脑力激荡的新思路。 开发人员认为这样做是近年来的一个例子是基督徒海尔曼,谁把现有的模块模式并对其作出了一些根本有用的变化创造了显露的模块模式(这就是后来在这本书涵盖的模式之一)。 以下是提示如果有意创建一个新的设计模式,我建议: 如何实际是模式?:确保模式描述了行之有效的解决方案,以重复出现的问题,而不是它没有资格只是投机性的解决方案。 保持最佳做法:设计决策,我们做应该基于我们从最佳实践的理解推导而来的原则。 我们的设计模式应该是透明的,用户:设计模式应该是完全透明的任何类型的用户体验。它们主要有利用他们所服务的开发者,并且不应迫使在于不会没有使用的图案的招致用户体验改变行为。 请记住,原创是不是在设计模式键入:当编写一个模式,我们并不需要成为解决方案的最初发现者被记录也不必担心我们的设计与其他模式的次要部分重叠。如果方法是强大到足以具有广阔的应用非常有用,它被认为是一个有效的模式的机会。 模式需要一个强大的一套例子:一个好的模式描述需要跟着一个同样强大的一套例子,表明我们的模式的成功应用。要显示的广泛使用,表现出良好的设计原则的例子是理想的。 模式写作是创造一个设计是通用的,具体的和高于一切的,有用的一个谨慎的平衡。尽量保证如果写入的模式,你支付应用最广泛的领域,你应该罚款。我希望这个简短的介绍写作模式给了你一些见解,这将有助于你的学习过程,这本书的下一个部分。 反模式 如果我们认为这是一个模式代表一种最佳实践,反模式表示已吸取了教训。术语反模式是在1995年的十一月C ++报告当年创造的安德鲁·柯尼希,由GoF的书启发设计模式。在柯尼希的报告中,也有被提出反模式的两个概念。反模式: 描述一个坏的解决方案,这就造成了恶劣的情况下发生的特殊问题 描述如何走出所述情形以及如何从那里到一个很好的解决方案 关于这个话题,亚历山大写到取得了良好的设计结构和良好的环境之间的良好平衡的困难: “这说明是有关设计过程; 。发明它显示一个新的物理顺序,组织形式的物理的东西,响应功能的过程......每一个设计问题,开始努力在两个实体之间达到健身:有问题的形式和内容。的形式是解决问题的办法; 上下文定义了该问题。“ 虽然需要注意的设计模式是相当重要的,它可以是要了解的反模式同样重要。让我们限定这背后的原因。当创建一个应用程序,一个项目的生命周期与建设始于然而,一旦你已经得到了最初的版本完成,它需要维护。最终的解决方案的质量要么是好还是坏,取决于技巧和时间球队纷纷投入到它的水平。在这里,好和坏的方面考虑-如果应用在错误的情况下一个“完美”的设计可以用一个反模式出线。 更大的挑战出现的应用程序已达到生产和准备进入维护模式后。开发人员这样谁没有对应用程序的工作之前可能会推出一个系统上工作不好设计成事故的项目。如果说坏的做法为反模式创建的,它允许开发者事先认识到这些,使他们能够避免可能发生的常见错误的方法-这是平行于设计模式为我们提供了一种方法来识别常见的方式技术,都是有用的。 总之,反模式是一个不好的设计,是值得记录的。在JavaScript中的反模式的例子如下: 通过在全球范围内定义大量的变量污染全局命名空间 传递字符串,而不是功能要么的setTimeout或setInterval的,因为这会触发使用eval()内部。 修改Object类原型(这是一个特别坏的反模式) 在内嵌的形式使用JavaScript,因为这是不灵活 使用文件撰写的,其中原生DOM等替代了document.createElement是比较合适的。文件撰写已被严重滥用多年来有不少缺点,包括,如果之后的页面已被加载它实际上可以覆盖我们的页面,同时使用document.createElement没有它的执行。我们可以看到在这里为这个动作一个活生生的例子。它也没有使用XHTML这是另一个原因选择了多个DOM友好的方法,如使用document.createElement是有利的工作。 反模式知识是成功的关键。一旦我们能够认识到这种反模式,我们能够重构我们的代码来否定他们,使我们的解决方案的整体素质提高了瞬间。 分类设计模式 从著名的设计书的词汇,领域驱动条款,正确地指出: “设计模式名称,摘要,并确定一个共同的设计结构使其成为创建​​可重用的面向对象的设计非常有用的关键方面。设计模式确定了参与类和它们的实例,他们的角色和协作和责任的分配。 每一个设计图案集中在一个特定的面向对象的设计问题或问题。它描述了当它适用,无论它是否可以在考虑其它设计约束施加,其后果及其使用的权衡。因为我们必须最终实现我们的设计中,设计模式还提供了样本...代码来说明一个实现。 虽然设计模式描述的面向对象的设计中,它们是基于已在主流的面向对象编程语言中实现的实际解决办法......“ 设计模式可以细分成若干不同的类别。在本节中,我们将回顾这三个类别,并简要提到属于这些类别更详细地探索具体的之前模式的几个例子。 造物设计模式 造物设计模式着重处理在哪里适合我们的工作情况的方式创建对象的对象创建机制。要创建对象,否则可能导致额外的复杂性,项目的基本方法,而这些模式的目标是解决这个问题控制创建过程。 一些属于这一类的模式是:构造函数,工厂,摘要,原型,辛格尔顿和生成器。 结构设计模式 结构模式所关注的对象组成,通常找出简单的方法来实现不同对象之间的关系。它们有助于确保当一个系统的变化之一的一部分,该系统的整个结构不需要做。他们还协助重铸也不适合特定用途为那些做了系统的组成部分。 这属于此类模式包括:装饰,幕墙,飞锤,适配器和代理。 行为设计模式 行为模式注重提高或精简的系统中不同对象之间的通信。 有些行为模式包括:迭代器,中保,观察员和游客。 设计模式分类 在我学习设计模式的早期经验,我个人以下表中找到一个什么样的一些模式所提供的一个非常有用的提醒 - 它涵盖GoF的中提到的23个设计模式。原始表于2004年总结了伊利斯尼尔森回来,我已经修改了它在必要时以适应书中的这一部分我们的讨论。 我建议使用此表作为参考,但千万记住,有一些是这里没有提到的其他模式,但将在本书后面讨论。 在类的简要说明 请记住,会出现在该表的模式引用“类”的概念。JavaScript是一类少的语言,但类可以使用函数来模拟。 为实现这一点的最常用的方法是通过定义一个JavaScript功能,我们然后使用创建一个对象new的关键字。this可用于帮助定义新的属性和方法为对象如下: ~~~ // A car "class" function Car( model ) { this.model = model; this.color = "silver"; this.year = "2012"; this.getInfo = function () { return this.model + " " + this.year; }; } ~~~ 然后,我们可以用我们上面这样定义的汽车构造实例化对象: ~~~ var myCar = new Car("ford"); myCar.year = "2010"; console.log( myCar.getInfo() ); ~~~ 欲了解更多的方法来定义“类”使用JavaScript,请斯托扬斯特凡的有用的岗位上他们。 现在让我们继续审查表。 造物 基于创建对象的概念。 类 工厂方法 这使得根据接口的数据或事件的多个派生类的一个实例。 对象 抽象工厂 创建类的几个家庭的实例,但没有详细说明具体的类。 生成器 从它的表示中隔离对象的构造,总是创建相同类型的对象。 原型 用于复制或克隆完全初始化实例。 独生子 一类以只与全球接入点的一个实例。 结构 基于建立的对象的块的概念。 类 适配器 不同级别的比赛,因此接口类都可以尽管不兼容的接口一起工作。 对象 适配器 不同级别的比赛,因此接口类都可以尽管不兼容的接口一起工作。 桥梁 从它的实现中分离对象的接口,所以可以独立地变化。 综合 简单和复合物的结构,这使得总的目标比其部分刚总和。 装饰 动态交替处理添加到对象。 正面 一个单独的类,隐藏了一个完整的子系统的复杂性。 飞锤 用于在别处包含的信息的高效共享细粒度实例。 代理 代表真正的对象的占位符对象。 行为的 基于对象的游戏和一起工作的方式。 类 翻译员 一种方法,包括在应用程序中相匹配的预期语言的语法的语言元素。 模板 方法 创建一个算法的壳的方法,那么推迟到子类的具体步骤。 对象 链 责任 传递对象链之间的请求,以找到能够处理请求的对象的方式。 命令 封装命令请求为对象,以使,记录和/或请求的队列,并提供错误处理未处理请求。 迭代器 顺序访问一个集合中的元素不知道集合的内部运作。 中间人 定义类之间简化的通信,以防止一组类从明确提到彼此。 纪念 拍摄对象的内部状态,这样才能在稍后恢复它。 观察 通知更改了许多类以保证类之间一致性的一种方式。 状态 当改变对象的行为及其状态的变化。 战略 封装类分开的实现里面选择的算法。 游客 添加一个新的操作类,而不改变类。 JavaScript的设计模式 在本节中,我们将探讨一些既古典与现代的设计模式的JavaScript实现。 开发人员通常不知道是否有理想的,他们应该用他们的工作流模式的模式或集。没有一个真正的单一答案; 我们的工作对每个脚本和Web应用程序很可能有自己的个性化需求,我们需要思考的问题,我们觉得一个模式可以提供真正的价值的实现。 例如,一些项目可能由Observer模式带来的效益脱钩受益(这减少了应用程序的相关部分如何彼此),而其他的可能只是太小,去耦引起人们的关注的。 这就是说,当我们有设计模式和他们是最适合的具体问题,牢牢把握,它变得更容易将其集成到我们的应用程序架构。 我们将在本节中探索的模式是: 构造模式 模块模式 揭示模块模式 Singleton模式 观察者模式 调解模式 原型模式 命令模式 门面模式 工厂模式 混入模式 装饰图案 享元模式 构造函数模式 在传统的面向对象的编程语言,构造函数是用来初始化一旦内存被分配给它一个新创建的对象的特殊方法。在JavaScript中,因为几乎一切都是对象,我们最常感兴趣的对象构造函数。 对象构造用于创建特定类型的对象 - 无论是准备使用的对象,并接受其构造可以使用第一次创建对象时设置成员属性和方法的值的参数。 对象的创建 在JavaScript来创建新对象的三种常用的方法如下: ? 1 2 3 4 五 6 7 8 9 // Each of the following options will create a new empty object: var newObject = {}; // or var newObject = Object.create( Object.prototype ); // or var newObject = new Object(); 凡在最后一个例子“对象”的构造函数创建一个特定值的对象包装,或​​没有传递值,它会创建一个空的对象,并将其返回。 有然后在其中键和值然后可以被分配给一个目的四种方式: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 // ECMAScript 3 compatible approaches // 1. Dot syntax // Set properties newObject.someKey = "Hello World"; // Get properties var value = newObject.someKey; // 2. Square bracket syntax // Set properties newObject["someKey"] = "Hello World"; // Get properties var value = newObject["someKey"]; // ECMAScript 5 only compatible approaches // For more information see: http://kangax.github.com/es5-compat-table/ // 3. Object.defineProperty // Set properties Object.defineProperty( newObject, "someKey", { value: "for more control of the property's behavior", writable: true, enumerable: true, configurable: true }); // If the above feels a little difficult to read, a short-hand could // be written as follows: var defineProp = function ( obj, key, value ){ var config = { value: value, writable: true, enumerable: true, configurable: true }; Object.defineProperty( obj, key, config ); }; // To use, we then create a new empty "person" object var person = Object.create( Object.prototype ); // Populate the object with properties defineProp( person, "car", "Delorean" ); defineProp( person, "dateOfBirth", "1981" ); defineProp( person, "hasBeard", false ); console.log(person); // Outputs: Object {car: "Delorean", dateOfBirth: "1981", hasBeard: false} // 4. Object.defineProperties // Set properties Object.defineProperties( newObject, { "someKey": { value: "Hello World", writable: true }, "anotherKey": { value: "Foo bar", writable: false } }); // Getting properties for 3. and 4. can be done using any of the // options in 1. and 2. 正如我们将在本书稍后看到,这些方法甚至可以用于继承,如下所示: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 // Usage: // Create a race car driver that inherits from the person object var driver = Object.create( person ); // Set some properties for the driver defineProp(driver, "topSpeed", "100mph"); // Get an inherited property (1981) console.log( driver.dateOfBirth ); // Get the property we set (100mph) console.log( driver.topSpeed ); 基本构造 正如我们前面看到的,JavaScript不支持类的概念,但它不支持使用对象特殊的构造函数。通过简单的前缀与关键字“新”的构造函数的调用,我们可以告诉我们的JavaScript愿功能表现得像一个构造函数和实例与函数定义成员的新对象。 内部构造函数中,关键字此引用的正在创建的新对象。重访对象的创建,基本构造可能如下所示: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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"; }; } // Usage: // We can create new instances of the car var civic = new Car( "Honda Civic", 2009, 20000 ); var mondeo = new Car( "Ford Mondeo", 2010, 5000 ); // and then open our browser console to view the // output of the toString() method being called on // these objects console.log( civic.toString() ); console.log( mondeo.toString() ); 以上是构造模式的一个简单的版本,但它确实从一些问题的困扰。之一是,它使继承困难,另一个是如函数toString()被重新定义为每个使用汽车构造创建的新对象。这不是非常理想的功能应理想的所有车辆类型的实例之间共享。 值得庆幸的是,因为有一些既ES3和ES5兼容的替代构造的对象,是微不足道的解决此限制。 构造方法原型 功能,如在JavaScript中几乎所有的对象,包含一个“原型”的对象。当我们调用JavaScript构造函数来创建一个对象,构造函数的原型的所有属性,然后提供给新的对象。以这种方式,多个车载对象可以创建了访问同一原型。因此,我们可以扩展原始举例如下: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function Car( model, year, miles ) { this.model = model; this.year = year; this.miles = miles; } // Note here that we are using Object.prototype.newMethod rather than // Object.prototype so as to avoid redefining the prototype object Car.prototype.toString = function () { return this.model + " has done " + this.miles + " miles"; }; // Usage: 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的单个实例()现在将所有对车对象之间共享。 模块模式 模块 模块是任何强大的应用架构中不可或缺的一块,通常在保持代码的单位为一个项目都清楚分开的和有组织的帮助。 在JavaScript中,有实现模块的几个选项。这些包括: 模块模式 对象的文字符号 AMD模块 CommonJS的模块 ECMAScript的和谐模块 我们将在后面探讨后三种这些选项在本书中的部分现代模块化JavaScript的设计模式。 模块模式是基于在对象的文字部分,因此它是有道理的,首先刷新我们对他们的了解。 对象文本 在对象的文字符号,一个对象被描述为一组(包含在大括号逗号分隔的名称/值对{})。物体内部名称可能是字符串或标识符后跟一个冒号。不应该有在对象的最终名称/值对后使用,因为这可能会导致错误的逗号。 ? 1 2 3 4 五 6 7 8 var myObjectLiteral = { variableKey: variableValue, functionKey: function () { // ... } }; 对象文字不需要实例使用new操作者,但不应该在开始发言被用作开口{可以被解释为一个块的开始。一个对象外,新成员可以使用分配如下被加入到其myModule.property = "someValue"; 下面我们可以看到使用对象的文字符号定义的模块的更完整的例子: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 var myModule = { myProperty: "someValue", // object literals can contain properties and methods. // e.g we can define a further object for module configuration: myConfig: { useCaching: true, language: "en" }, // a very basic method saySomething: function () { console.log( "Where in the world is Paul Irish today?" ); }, // output a value based on the current configuration reportMyConfig: function () { console.log( "Caching is: " + ( this.myConfig.useCaching ? "enabled" : "disabled") ); }, // override the current configuration updateMyConfig: function( newConfig ) { if ( typeof newConfig === "object" ) { this.myConfig = newConfig; console.log( this.myConfig.language ); } } }; // Outputs: Where in the world is Paul Irish today? myModule.saySomething(); // Outputs: Caching is: enabled myModule.reportMyConfig(); // Outputs: fr myModule.updateMyConfig({ language: "fr", useCaching: false }); // Outputs: Caching is: disabled myModule.reportMyConfig(); 使用对象文本可以协助封装和组织代码和丽贝卡·墨菲以前曾写过关于此主题的深度应该要读入对象文本进一步。 这就是说,如果我们选择了这种技术,我们可以同样在模块图案感兴趣。它仍然使用对象文本,但只有从一个范围函数的返回值。 模块模式 模块模式最初被定义为一种方法来提供传统软件工程类私人和公共封装。 在JavaScript中,模块模式被用于进一步模拟的类的概念在这样一种方式,我们能够包括单个对象内部公共/私有方法和变量,从而屏蔽特定部件从全局范围。这是什么导致在我们的函数名在页面上的其他脚本定义等功能相冲突的可能性降低。 隐私 模块模式使用闭包封装“隐私”,国家和组织。它提供了包装的公共和私有方法和变量的组合,保护片泄漏到全球范围时,不慎与其他开发界面碰撞的方式。有了这个模式,只返回一个公共的API,保持封闭的私人内的一切。 这为我们提供了屏蔽逻辑做繁重的任务,而只露出我们希望我们的应用程序中使用的其他部分的接口干净的解决方案。图案是相当类似的紧调用功能表达(IIFE -见命名空间型态更多关于此的部分),除了一个对象被返回,而不是一个函数。 应该指出的是,不是一个真正的真实明确的“隐私”里的JavaScript意义,因为不像一些传统的语言,它没有访问修饰符。变量不能在技术上被声明为公众人士,亦私,所以我们使用的功能范围来模拟这一概念。在模块模式,声明的变量或方法仅是模块本身由于闭包内可用。然而,变量或返回对象中定义的方法提供给大家。 历史 从历史的角度来看,模块模式最初是由许多人包括发达国家理查德康福德于2003年,后来被道格拉斯在克罗克福德他的讲座推广。琐事的另一件是,如果你曾经与雅虎的YUI库出场,它的某些功能可能会出现相当的熟悉和这样做的原因是,他们创建组件时模块模式是YUI强大的影响力。 例子 让我们首先创建一个模块,它是自包含在看模块模式的实现。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 var testModule = (function () { var counter = 0; return { incrementCounter: function () { return counter++; }, resetCounter: function () { console.log( "counter value prior to reset: " + counter ); counter = 0; } }; })(); // Usage: // Increment our counter testModule.incrementCounter(); // Check the counter value and reset // Outputs: counter value prior to reset: 1 testModule.resetCounter(); 这里,码的其他部分无法直接阅读我们的值incrementCounter()或resetCounter()。它的存在,这样能够访问其范围的唯一的代码是我们两个功能仅限于模块的关闭内-计数器变量实际上是从我们的全球范围,以便它的行为就像一个私有变量将完全屏蔽。我们的方法是有效的命名空间所以我们代码的测试部分,我们需要与模块的名称(如“testModule”),前缀任何电话。 当与模块模式工作时,我们可能会发现它有用的定义,我们使用入门与它一个简单的模板。这里有一个覆盖命名空间,公共和私有变量: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 var myNamespace = (function () { var myPrivateVar, myPrivateMethod; // A private counter variable myPrivateVar = 0; // A private function which logs any arguments myPrivateMethod = function( foo ) { console.log( foo ); }; return { // A public variable myPublicVar: "foo", // A public function utilizing privates myPublicFunction: function( bar ) { // Increment our private counter myPrivateVar++; // Call our private method using bar myPrivateMethod( bar ); } }; })(); 看着另外一个例子,下面我们可以看到使用这种模式实现了一个购物篮。该模块本身完全是自包含在一个叫做全局变量basketModule。该basket模块中的阵列将保密,我们的应用程序,所以其他部分都无法直接读取它。它仅与模块的闭合存在,并且因此能够访问它的唯一的方法是那些能够访问它的范围(即addItem(),getItemCount()等)。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 43 44 var basketModule = (function () { // privates var basket = []; function doSomethingPrivate() { //... } function doSomethingElsePrivate() { //... } // Return an object exposed to the public return { // Add items to our basket addItem: function( values ) { basket.push(values); }, // Get the count of items in the basket getItemCount: function () { return basket.length; }, // Public alias to a private function doSomething: doSomethingPrivate, // Get the total value of items in the basket getTotal: function () { var q = this.getItemCount(), p = 0; while (q--) { p += basket[q].price; } return p; } }; })(); 在模块内,你可能已经注意到,我们返回object。这被自动分配给basketModule这样我们可以按照如下与其交互: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 // basketModule returns an object with a public API we can use basketModule.addItem({ item: "bread", price: 0.5 }); basketModule.addItem({ item: "butter", price: 0.3 }); // Outputs: 2 console.log( basketModule.getItemCount() ); // Outputs: 0.8 console.log( basketModule.getTotal() ); // However, the following will not work: // Outputs: undefined // This is because the basket itself is not exposed as a part of our // public API console.log( basketModule.basket ); // This also won't work as it only exists within the scope of our // basketModule closure, but not in the returned public object console.log( basket ); 以上的方法是有效的命名空间内basketModule。 请注意如何在上述筐模块中的作用域功能是​​围绕我们的所有功能,我们然后调用并立即存储的返回值包裹。这有许多优点,包括: 自由有私人活动和只能由我们的模块消耗私有成员。因为它们没有暴露在页面(只有我们出口的API)的其余部分,他们认为真正私有的。 鉴于函数通常声明,并命名,它可以更容易显示调用堆栈在调试器中,当我们正在试图发现什么功能(S)引发了异常。 作为TJ克劳德在过去指出的那样,它也使我们能够返回根据环境不同的功能。在过去,我已经看到了开发人员使用此,以便提供特定于IE浏览器的模块中的代码路径执行UA测试,但我们可以很容易地选择功能检测,这些天来实现类似的目标。 模块模式变化 进口混入 该模式的这种变化说明了如何全局(如jQuery的,下划线)可以作为参数传递给我们的模块的匿名函数传递。这有效地使我们能够导入他们和当地的别名他们如人所愿。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // Global module var myModule = (function ( jQ, _ ) { function privateMethod1(){ jQ(".container").html("test"); } function privateMethod2(){ console.log( _.min([10, 5, 100, 2, 1000]) ); } return{ publicMethod: function(){ privateMethod1(); } }; // Pull in jQuery and Underscore })( jQuery, _ ); myModule.publicMethod(); 出口 接下来的这个变化使我们无需耗费它们声明全局,并可能同样支持在最后一个例子看出全球进口的概念。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // Global module var myModule = (function () { // Module object var module = {}, privateVariable = "Hello World"; function privateMethod() { // ... } module.publicProperty = "Foobar"; module.publicMethod = function () { console.log( privateVariable ); }; return module; })(); 工具包和框架特定模块的模式实现 道场 Dojo提供与被调用对象的工作的一个便捷方法dojo.setObject()。这需要作为第一个参数一个圆点分隔字符串,如myObj.parent.child它指的是一个名为“子”对象“父”内里“MyObj中”定义的属性。使用setObject()允许我们设置儿童的价值,传递的路径的其余部分创建任何中间的对象,如果他们不存在。 例如,如果我们想声明basket.core作为的一个目的store名称空间,这是可以实现用传统的方式如下: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 var store = window.store || {}; if ( !store["basket"] ) { store.basket = {}; } if ( !store.basket["core"] ) { store.basket.core = {}; } store.basket.core = { // ...rest of our logic }; 或者用道场1.7(AMD兼容的版本)及以上如下: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 require(["dojo/_base/customStore"], function( store ){ // using dojo.setObject() store.setObject( "basket.core", (function() { var basket = []; function privateMethod() { console.log(basket); } return { publicMethod: function(){ privateMethod(); } }; })()); }); 有关更多信息dojo.setObject(),请参阅官方文档。 ExtJS的 对于使用煎茶的ExtJS的那些,一个例子演示如何正确使用模块模式与框架可以在下面找到。 在这里,我们看到的如何定义,然后可以用含有一个私人和公共API模块填充命名空间的一例。随着一些语义差异外,这是相当接近模块模式是如何在香草JavaScript实现的: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 // create namespace Ext.namespace("myNameSpace"); // create application myNameSpace.app = function () { // do NOT access DOM from here; elements don't exist yet // private variables var btn1, privVar1 = 11; // private functions var btn1Handler = function ( button, event ) { console.log( "privVar1=" + privVar1 ); console.log( "this.btn1Text=" + this.btn1Text ); }; // public space return { // public properties, e.g. strings to translate btn1Text: "Button 1", // public methods init: function () { if ( Ext.Ext2 ) { btn1 = new Ext.Button({ renderTo: "btn1-ct", text: this.btn1Text, handler: btn1Handler }); } else { btn1 = new Ext.Button( "btn1-ct", { text: this.btn1Text, handler: btn1Handler }); } } }; }(); YUI 同样,我们也可以用YUI3构建应用程序时实现的模块的模式。下面的例子主要是基于由埃里克·米拉利亚原YUI模块模式实现,但同样,不能从香草JavaScript版本大不相同: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 Y.namespace( "store.basket" ) ; Y.store.basket = (function () { var myPrivateVar, myPrivateMethod; // private variables: myPrivateVar = "I can be accessed only within Y.store.basket."; // private method: myPrivateMethod = function () { Y.log( "I can be accessed only from within YAHOO.store.basket" ); } return { myPublicProperty: "I'm a public property.", myPublicMethod: function () { Y.log( "I'm a public method." ); // Within basket, I can access "private" vars and methods: Y.log( myPrivateVar ); Y.log( myPrivateMethod() ); // The native scope of myPublicMethod is store so we can // access public members using "this": Y.log( this.myPublicProperty ); } }; })(); jQuery的 有多种方式,其中jQuery代码特异性到插件可以模块图案内包裹。本樱桃先前建议,其中一个功能封装在那里是一个数字模块之间的共同性的事件周围使用模块定义的实现。 在下面的例子中,library函数定义它宣布一个新的图书馆,并自动绑定了init函数来document.ready创建新库(即模块)时。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function library( module ) { $( function() { if ( module.init ) { module.init(); } }); return module; } var myLibrary = library(function () { return { init: function () { // module implementation } }; }()); 优点 我们已经看到,为什么构造模式可能是有用的,但为什么是模块模式一个好的选择吗?对于初学者来说,它是开发者不是真正的封装的想法一个面向对象的背景的,从JavaScript的角度来看,至少变得更干净。 其次,它支持私有数据 - 因此,在模块的模式,我们的代码公共部位都能够触摸私处,但外界无法接触到类的私有部分(不笑的哦,感谢大卫Engfer!为笑谈)。 缺点 模块模式的缺点是,当我们访问不同的公共和私营部门的成员,当我们希望改变的知名度,我们实际上不得不更改使用了成员的每个地方。 我们也不能访问在被添加到对象在以后的方法私有成员。这就是说,在许多情况下,模块格局依然相当有用的,正确使用时,当然要改善我们的应用程序结构的潜力。 其他缺点包括:无法创建私有成员和额外的复杂性错误时,需要热修复补丁程序自动单元测试。这是根本不可能修补士兵。相反,人们必须重写它与车互动士兵所有公共方法。开发商不能轻易扩展要么私处,所以它是值得记住的士兵并不像他们最初可能会显示为灵活。 有关模块的格局进一步阅读,请参阅本Cherry的优秀深入的文章就可以了。 透露出模块模式 现在,我们多了几分熟悉的模块模式,让我们来看看一个稍微改进版 - 基督教海尔曼的显露的模块模式。 透出模块模式来作为左右海尔曼感到沮丧的事实,他不得不重复的主要对象的名字的时候,我们想从另一个或访问公共变量调用一个公共方法。他还讨厌模块模式的要求,具有切换到对象的文字符号,因为他希望公开的事情。 他的努力的结果是一个更新的模式,我们会简单地定义了我们所有的函数和变量在私人范围和返回的指针匿名对象,我们希望揭示公共私营功能。 如何使用的显露的模块模式的例子可以发现如下: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 var myRevealingModule = (function () { var privateVar = "Ben Cherry", publicVar = "Hey there!"; function privateFunction() { console.log( "Name:" + privateVar ); } function publicSetName( strName ) { privateVar = strName; } function publicGetName() { privateFunction(); } // Reveal public pointers to // private functions and properties return { setName: publicSetName, greeting: publicVar, getName: publicGetName }; })(); myRevealingModule.setName( "Paul Kinlan" ); 图案也可用于揭示私有函数和属性,更具体的命名方案,如果我们希望: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 var myRevealingModule = (function () { var privateCounter = 0; function privateFunction() { privateCounter++; } function publicFunction() { publicIncrement(); } function publicIncrement() { privateFunction(); } function publicGetCount(){ return privateCounter; } // Reveal public pointers to // private functions and properties return { start: publicFunction, increment: publicIncrement, count: publicGetCount }; })(); myRevealingModule.start(); 优点 该模式允许我们的脚本的语法更加一致。这也使得它在其中我们的函数和变量可以公开访问的模块,它简化了可读性的端部更清晰。 缺点 这种模式的一个缺点是,如果一个私有函数是指一个公共功能,即公共功能不能如果贴剂是必要的覆盖。这是因为私有函数将继续参考私有实现和图案并不适用于公共成员,只有功能。 公共对象成员其中提到私有变量也受到上述无补丁规则的注释。 由于这个结果,与显露的模块模式创建模块可以比那些与原来的模​​块图案创建更脆弱,所以护理应使用期间服用。 Singleton模式 因此公知的单例模式,因为它限制一个类的实例化到单个对象。经典的单例模式可以通过创建,如果不存在,创建类的新实例的方法的类来实现。在已有的一个实例的情况下,它只是简单地返回到该对象的引用。 单身从静态不同的类(或对象),因为我们可以延迟它们的初始化,通常由于需要一些信息期间初始化时间,可能无法使用。它们不提供对代码的方式,是不知道先前提及他们很容易地检索它们。这是因为它既不对象或这是由一个单身返回“下课”,这是一个结构。想想如何合拢变量实际上不是倒闭-功能范围,提供闭包是关闭。 在JavaScript中,单身作为一种共享资源空间从全局命名空间隔离的实现代码,以提供功能的访问的单点。 我们可以实现一个Singleton如下: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 var mySingleton = (function () { // Instance stores a reference to the Singleton var instance; function init() { // Singleton // Private methods and variables function privateMethod(){ console.log( "I am private" ); } var privateVariable = "Im also private"; var privateRandomNumber = Math.random(); return { // Public methods and variables publicMethod: function () { console.log( "The public can see me!" ); }, publicProperty: "I am also public", getRandomNumber: function() { return privateRandomNumber; } }; }; return { // Get the Singleton instance if one exists // or create one if it doesn't getInstance: function () { if ( !instance ) { instance = init(); } return instance; } }; })(); var myBadSingleton = (function () { // Instance stores a reference to the Singleton var instance; function init() { // Singleton var privateRandomNumber = Math.random(); return { getRandomNumber: function() { return privateRandomNumber; } }; }; return { // Always create a new Singleton instance getInstance: function () { instance = init(); return instance; } }; })(); // Usage: var singleA = mySingleton.getInstance(); var singleB = mySingleton.getInstance(); console.log( singleA.getRandomNumber() === singleB.getRandomNumber() ); // true var badSingleA = myBadSingleton.getInstance(); var badSingleB = myBadSingleton.getInstance(); console.log( badSingleA.getRandomNumber() !== badSingleB.getRandomNumber() ); // true // Note: as we are working with random numbers, there is a // mathematical possibility both numbers will be the same, // however unlikely. The above example should otherwise still // be valid. 是什么让辛格尔顿是实例(通常通过全球接入MySingleton.getInstance()),因为我们不(在静态语言至少)调用new MySingleton()直接。但是,这是有可能在JavaScript中。 在GoF的书,适用性 Singleton模式描述如下: 必须有一个类中的恰好一个实例,并且它必须是客户端可以访问从一个公知的接入点。 当鞋底实例应该是由子类扩展和客户端应能够使用扩展实例,而无需修改其代码。 这些点的第二指的是,我们可能需要的代码,如一个案例: ? 1 2 3 4 五 6 7 8 9 10 mySingleton.getInstance = function(){ if ( this._instance == null ) { if ( isFoo() ) { this._instance = new FooSingleton(); } else { this._instance = new BasicSingleton(); } } return this._instance; }; 在这里,getInstance变得有点像一个工厂方法,我们并不需要更新我们的代码访问它的每一点。FooSingleton上面会的一个子类BasicSingleton,并实现相同的接口。 为什么推迟执行考虑了辛格尔顿重要? 在C ++中它的作用是从动态初始化秩序的不可预测性隔离,控制权返回给程序员。 需要注意的一个类(对象)的静态实例和辛格尔顿之间的区别是很重要的:而一个单身可以作为一个静态实例来实现,也可以懒洋洋地构建,而不需要资源,也没有内存中,直到这实际上是必要的。 如果我们有一个可以直接初始化的静态对象,我们必须确保代码总是以相同的顺序执行(例如,在情况objCar需要objWheel其初始化期间),当有大量的源文件的本不能扩展。 无论单身和静态对象是有用的,但他们不应该被过度使用 - 在此我们不应该过度使用其他模式一样。 在实践中,当需要一个对象跨越系统以协调其它Singleton模式是有用的。这里是一个例子,在这种情况下所使用的图案: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 var SingletonTester = (function () { // options: an object containing configuration options for the singleton // e.g var options = { name: "test", pointX: 5}; function Singleton( options ) { // set options to the options supplied // or an empty object if none are provided options = options || {}; // set some properties for our singleton this.name = "SingletonTester"; this.pointX = options.pointX || 6; this.pointY = options.pointY || 10; } // our instance holder var instance; // an emulation of static variables and methods var _static = { name: "SingletonTester", // Method for getting an instance. It returns // a singleton instance of a singleton object getInstance: function( options ) { if( instance === undefined ) { instance = new Singleton( options ); } return instance; } }; return _static; })(); var singletonTest = SingletonTester.getInstance({ pointX: 5 }); // Log the output of pointX just to verify it is correct // Outputs: 5 console.log( singletonTest.pointX ); 虽然辛格尔顿具有有效的用途,往往当我们发现自己需要它在JavaScript中它是我们可能需要重新评估我们的设计标志。 他们常常是,在一个系统中任一紧耦合模块或逻辑被越过代码库的多个部分过于扩散的指示。单身人士可以更难以测试,由于问题,从隐藏的依赖,在掘根的依赖性等创建多个实例,困难难度。 米勒·梅德罗斯以前曾建议这对辛格尔顿和它的各种问题的极好的文章,详细阅读和评论,以这个文章,讨论单身如何提高紧密耦合。我很高兴第二这些建议为两件提高对这种模式很多重要的穴位,同时也是可圈可点。 观察者模式 观察员是一种设计模式,其中一个对象(称为一级学科)维持取决于它(观察者)对象的列表,自动通知他们的任何变化的状态。 当主体需要通知有关一些有趣的事情发生观察员,它广播到观察者(可能包括有关通知的特定主题的数据)的通知。 当我们不再为特定的观察者希望被通知通过它们与登记对象的变化,个体可以从观察名单中删除。 它通常指回是语言无关随着时间的推移得到他们的用途和优点的更广泛意义上的设计模式出版的定义是有用的。在GoF的书中提供的观察者模式的定义,设计模式:可复用面向对象软件的基础是: “一个或多个观察员对某一主题感兴趣的状态,并通过将自己注册自己的学科的兴趣。当东西在我们的主题改变观察者可能感兴趣的,则发送通知消息,它​​调用更新方法在每个观察者。当观察者在主体的状态不再感兴趣,他们可以简单地跳出来。“ 现在,我们可以扩大我们学到的东西来实现与下列组件观察者模式: 主题:保持观察者的列表,方便添加或删除观察者 观察:提供了一个更新界面,需要通知的国家的国民的对象变化 ConcreteSubject:广播通知状态变化的观测,存储ConcreteObservers的状态 ConcreteObserver:存储对ConcreteSubject参考,实现对观察员,以确保状态更新界面与主题的一致 首先,让我们依赖观察员的对象可能有列表模型: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 function ObserverList(){ this.observerList = []; } ObserverList.prototype.add = function( obj ){ return this.observerList.push( obj ); }; ObserverList.prototype.count = function(){ return this.observerList.length; }; ObserverList.prototype.get = function( index ){ if( index > -1 && index < this.observerList.length ){ return this.observerList[ index ]; } }; ObserverList.prototype.indexOf = function( obj, startIndex ){ var i = startIndex; while( i < this.observerList.length ){ if( this.observerList[i] === obj ){ return i; } i++; } return -1; }; ObserverList.prototype.removeAt = function( index ){ this.observerList.splice( index, 1 ); }; 接下来,让我们模型中的主题,并添加,删除或通知观察员观察名单上的能力。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 function Subject(){ this.observers = new ObserverList(); } Subject.prototype.addObserver = function( observer ){ this.observers.add( observer ); }; Subject.prototype.removeObserver = function( observer ){ this.observers.removeAt( this.observers.indexOf( observer, 0 ) ); }; Subject.prototype.notify = function( context ){ var observerCount = this.observers.count(); for(var i=0; i < observerCount; i++){ this.observers.get(i).update( context ); } }; 然后,我们定义了一个框架来创建新的观察员。在update这里的功能将在后面自定义行为覆盖。 ? 1 2 3 4 五 6 // The Observer function Observer(){ this.update = function(){ // ... }; } 在使用上述观察部件示例应用程序,我们现在定义: 一种把新观察到的复选框的页面按钮 控制复选框,将作为一个主体,他们通知应检查其他复选框 被添加了一个新的复选框容器 然后,我们定义ConcreteSubject和ConcreteObserver处理为增加新的观察员页面和实施更新接口。请参阅以下行内注释关于这些部件在我们的例子的情况下做的。 HTML: ? 1 2 3
示例脚本: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 // Extend an object with an extension function extend( obj, extension ){ for ( var key in extension ){ obj[key] = extension[key]; } } // References to our DOM elements var controlCheckbox = document.getElementById( "mainCheckbox" ), addBtn = document.getElementById( "addNewObserver" ), container = document.getElementById( "observersContainer" ); // Concrete Subject // Extend the controlling checkbox with the Subject class extend( controlCheckbox, new Subject() ); // Clicking the checkbox will trigger notifications to its observers controlCheckbox.onclick = function(){ controlCheckbox.notify( controlCheckbox.checked ); }; addBtn.onclick = addNewObserver; // Concrete Observer function addNewObserver(){ // Create a new checkbox to be added var check = document.createElement( "input" ); check.type = "checkbox"; // Extend the checkbox with the Observer class extend( check, new Observer() ); // Override with custom update behaviour check.update = function( value ){ this.checked = value; }; // Add the new observer to our list of observers // for our main subject controlCheckbox.addObserver( check ); // Append the item to the container container.appendChild( check ); } 在这个例子中,我们研究了如何实现和利用观察者模式,涵盖主题,观察者,ConcreteSubject和ConcreteObserver的概念。 观察者和发布/订阅模式之间的差异 尽管Observer模式是需要注意的是有用的,往往在JavaScript中的世界,我们会发现它通常使用被称为发布/订阅模式的变化来实现。虽然很相似,有这些模式值得关注的差异。 观察者模式要求观察者(或对象)希望接收的话题通知必须订阅这个兴趣触发事件(主体)的对象。 然而,发布/订阅模式使用它希望接收通知(用户)的对象和触发事件(发布者)对象之间坐着一个话题/事件信道。该事件系统可以让代码来定义它可以传递包含由用户根据需要自定义的值参数应用特定的事件。这里的想法是,以避免用户和发行人之间的依赖关系。 这不同于观察者模式,因为它允许实施适当的事件处理程序进行注册并获得主题的通知由出版商播放任何用户。 这里是一个可能如何使用发布/如果设置有功能的实现供电订阅一个例子publish(),subscribe()和unsubscribe()幕后: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 // A very simple new mail handler // A count of the number of messages received var mailCounter = 0; // Initialize subscribers that will listen out for a topic // with the name "inbox/newMessage". // Render a preview of new messages var subscriber1 = subscribe( "inbox/newMessage", function( topic, data ) { // Log the topic for debugging purposes console.log( "A new message was received: ", topic ); // Use the data that was passed from our subject // to display a message preview to the user $( ".messageSender" ).html( data.sender ); $( ".messagePreview" ).html( data.body ); }); // Here's another subscriber using the same data to perform // a different task. // Update the counter displaying the number of new // messages received via the publisher var subscriber2 = subscribe( "inbox/newMessage", function( topic, data ) { $('.newMessageCounter').html( ++mailCounter ); }); publish( "inbox/newMessage", [{ sender: "hello@google.com", body: "Hey there! How are you doing today?" }]); // We could then at a later point unsubscribe our subscribers // from receiving any new topic notifications as follows: // unsubscribe( subscriber1 ); // unsubscribe( subscriber2 ); 这里的总体思路是促进松耦合。而不是单个对象上调用其他对象的方法,直接,它们代替订阅特定任务或另一个对象的活动,并当它发生时得到通知。 优点 观察者和发布/订阅模式鼓励我们要认真思考我们的应用程序的不同部分之间的关​​系。他们还帮助我们识别含有可以改为用套主体和观察员取代直接关系的图层。这有效地可以用来分解应用到更小,更松散耦合块以提高代码管理和潜力进行再利用。 背后使用Observer模式进一步动机是我们需要保持相关对象之间的一致性未做紧耦合类。例如,当一个对象需要能够不进行关于这些对象的假设来通知其他对象。 动态关系可以用两种模式,当观察者和主体之间存在。这提供了灵活性,可能不那么容易实现,当我们的应用程序的不同部分被紧密耦合很大。 虽然它可能不总是对每一问题的最佳解决方案,这些模式仍然是最好的工具之一用于设计去耦系统和应被视为在任何JavaScript显影剂的效用带的一个重要工具。 缺点 因此,一些与这些模式的问题实际上是从他们的主要收益干。在发布/订阅,通过来自用户的去耦出版商,它有时难​​以获得保证我们的应用程序的特定部分被充当我们可以预期。 例如,发布商可能使一个假设,即一个或多个用户正在收听他们。说,我们正在使用这样的假设对于一些应用程序记录或输出错误。如果用户在执行日志崩溃(或者因为某些原因无法正常使用),出版商不会看到这一种方式由于系统的解耦性质。 的图案的另一拉回是订户都相当无知彼此的存在,并且盲到开关发布的成本。由于订户和发行商之间的动态关系,更新相关性可能难以追踪。 发布/订阅实现 发布/订阅的配合相当不错,在JavaScript中的生态系统,主要是因为为核心,实现ECMAScript的是事件驱动的。作为使用DOM事件为脚本的主要交互的API这是在浏览器环境中尤其如此。 这就是说,无论是ECMAScript中也不DOM提供核心对象或方法的实现代码中创建自定义事件系统(带或许DOM3自定义事件,这势必给DOM,因此一般不是有用的除外)。 幸运的是,流行的JavaScript库,如道场,jQuery的(自定义事件)和YUI已经有可以帮助轻松实现发布/订阅系统,用很少的努力工具。下面我们可以看到这样一些例子: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 // Publish // jQuery: $(obj).trigger("channel", [arg1, arg2, arg3]); $( el ).trigger( "/login", [{username:"test", userData:"test"}] ); // Dojo: dojo.publish("channel", [arg1, arg2, arg3] ); dojo.publish( "/login", [{username:"test", userData:"test"}] ); // YUI: el.publish("channel", [arg1, arg2, arg3]); el.publish( "/login", {username:"test", userData:"test"} ); // Subscribe // jQuery: $(obj).on( "channel", [data], fn ); $( el ).on( "/login", function( event ){...} ); // Dojo: dojo.subscribe( "channel", fn); var handle = dojo.subscribe( "/login", function(data){..} ); // YUI: el.on("channel", handler); el.on( "/login", function( data ){...} ); // Unsubscribe // jQuery: $(obj).off( "channel" ); $( el ).off( "/login" ); // Dojo: dojo.unsubscribe( handle ); dojo.unsubscribe( handle ); // YUI: el.detach("channel"); el.detach( "/login" ); 对于希望使用香草的JavaScript(或其他库)发布/订阅模式的AmplifyJS包括清洁,图书馆无关的实现,它可以与任何库或工具包中。Radio.js(http://radio.uxder.com/),PubSubJS(https://github.com/mroderick/PubSubJS)或彼得·希金斯纯JS PubSub的(https://github.com/phiggins42/bloody- jQuery的,插件/ BLOB / 55e41df9bf08f42378bb08b93efcb28555b61aeb / pubsub.js)也值得一试类似的替代品。 jQuery的开发者尤其是有不少其他的选项,可以选择使用许多发达的实施范围从彼得·希金斯的jQuery插件奔Alman的(优化)的Pub / Sub jQuery的GitHub上依据之一。链接只是其中的几个可以在下面找到。 本Alman的发布/订阅要点https://gist.github.com/661855(推荐) 里克沃尔德伦的jQuery的核心风格采取上述https://gist.github.com/705311 彼得·希金斯“插件http://github.com/phiggins42/bloody-jquery-plugins/blob/master/pubsub.js。 在AmplifyJS AppendTo的发布/订阅http://amplifyjs.com 本Truyman的要点https://gist.github.com/826794 因此,我们能够得到多少的JavaScript观察者模式的实现可能工作香草的欣赏,让我们通过发布/订阅了一项名为下我在GitHub上发布的极简版散步pubsubz。这表明订阅的核心概念,发布以及退订的概念。 我选择了在此基础上的代码我们的例子中,因为它紧密地粘在方法签名和实施办法我都期望在经典的观察者模式的一个JavaScript版本,看看。 发布/订阅的实现 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 var pubsub = {}; (function(myObject) { // Storage for topics that can be broadcast // or listened to var topics = {}; // An topic identifier var subUid = -1; // Publish or broadcast events of interest // with a specific topic name and arguments // such as the data to pass along myObject.publish = function( topic, args ) { if ( !topics[topic] ) { return false; } var subscribers = topics[topic], len = subscribers ? subscribers.length : 0; while (len--) { subscribers[len].func( topic, args ); } return this; }; // Subscribe to events of interest // with a specific topic name and a // callback function, to be executed // when the topic/event is observed myObject.subscribe = function( topic, func ) { if (!topics[topic]) { topics[topic] = []; } var token = ( ++subUid ).toString(); topics[topic].push({ token: token, func: func }); return token; }; // Unsubscribe from a specific // topic, based on a tokenized reference // to the subscription myObject.unsubscribe = function( token ) { for ( var m in topics ) { if ( topics[m] ) { for ( var i = 0, j = topics[m].length; i < j; i++ ) { if ( topics[m][i].token === token ) { topics[m].splice( i, 1 ); return token; } } } } return this; }; }( pubsub )); 例如:根据我们的实施 现在我们可以使用的实施发布和订阅感兴趣的事件如下: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 // Another simple message handler // A simple message logger that logs any topics and data received through our // subscriber var messageLogger = function ( topics, data ) { console.log( "Logging: " + topics + ": " + data ); }; // Subscribers listen for topics they have subscribed to and // invoke a callback function (e.g messageLogger) once a new // notification is broadcast on that topic var subscription = pubsub.subscribe( "inbox/newMessage", messageLogger ); // Publishers are in charge of publishing topics or notifications of // interest to the application. e.g: pubsub.publish( "inbox/newMessage", "hello world!" ); // or pubsub.publish( "inbox/newMessage", ["test", "a", "b", "c"] ); // or pubsub.publish( "inbox/newMessage", { sender: "hello@google.com", body: "Hey again!" }); // We can also unsubscribe if we no longer wish for our subscribers // to be notified pubsub.unsubscribe( subscription ); // Once unsubscribed, this for example won't result in our // messageLogger being executed as the subscriber is // no longer listening pubsub.publish( "inbox/newMessage", "Hello! are you still there?" ); 例如:用户界面通知 接下来,让我们想象,我们有负责显示实时股票信息的Web应用程序。 应用程序可能具有用于显示股票统计信息和用于显示更新的最后一个点的计数器的网格。当数据模型更改,应用程序将需要更新网格和计数器。在这种情况下,我们的主题(其将出版主题/通知)是数据模型和我们的订户是网格和计数器。 当我们的用户收到该模式本身已经改变的通知,就可以相应地更新自己。 在我们的实现,我们的用户会听取专题“newDataAvailable”,以找出是否新的股票信息是可用的。如果一个新的通知发布到这个话题,就会引发gridUpdate一个新行添加到包含这些信息我们的网格。它也将更新上次更新计数器日志加入最后一次数据 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 // Return the current local time to be used in our UI later getCurrentTime = function (){ var date = new Date(), m = date.getMonth() + 1, d = date.getDate(), y = date.getFullYear(), t = date.toLocaleTimeString().toLowerCase(); return (m + "/" + d + "/" + y + " " + t); }; // Add a new row of data to our fictional grid component function addGridRow( data ) { // ui.grid.addRow( data ); console.log( "updated grid component with:" + data ); } // Update our fictional grid to show the time it was last // updated function updateCounter( data ) { // ui.grid.updateLastChanged( getCurrentTime() ); console.log( "data last updated at: " + getCurrentTime() + " with " + data); } // Update the grid using the data passed to our subscribers gridUpdate = function( topic, data ){ if ( data !== undefined ) { addGridRow( data ); updateCounter( data ); } }; // Create a subscription to the newDataAvailable topic var subscriber = pubsub.subscribe( "newDataAvailable", gridUpdate ); // The following represents updates to our data layer. This could be // powered by ajax requests which broadcast that new data is available // to the rest of the application. // Publish changes to the gridUpdated topic representing new entries pubsub.publish( "newDataAvailable", { summary: "Apple made $5 billion", identifier: "APPL", stockPrice: 570.91 }); pubsub.publish( "newDataAvailable", { summary: "Microsoft made $20 million", identifier: "MSFT", stockPrice: 30.85 }); 例如:采用解耦本Alman的酒吧应用程序/ sub实现 在下面的电影评级的例子中,我们将使用本Alman的jQuery的执行发布/订阅证明我们如何可以断开用户界面的。请注意如何提交评分只有发布了新的用户和评级数据可用事实的效果。 它留给用户对这些主题然后委托与该数据会发生什么。在我们的例子中,我们正在推动新的数据到现有数组,然后使用下划线库的渲染他们.template()的方法为模板。 HTML /模板 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

Recent users

Recent movies rated

JavaScript的 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 ;(function( $ ) { // Pre-compile templates and "cache" them using closure var userTemplate = _.template($( "#userTemplate" ).html()), ratingsTemplate = _.template($( "#ratingsTemplate" ).html()); // Subscribe to the new user topic, which adds a user // to a list of users who have submitted reviews $.subscribe( "/new/user", function( e, data ){ if( data ){ $('#users').append( userTemplate( data )); } }); // Subscribe to the new rating topic. This is composed of a title and // rating. New ratings are appended to a running list of added user // ratings. $.subscribe( "/new/rating", function( e, data ){ if( data ){ $( "#ratings" ).append( ratingsTemplate( data ) ); } }); // Handler for adding a new user $("#add").on("click", function( e ) { e.preventDefault(); var strUser = $("#twitter_handle").val(), strMovie = $("#movie_seen").val(), strRating = $("#movie_rating").val(); // Inform the application a new user is available $.publish( "/new/user", { name: strUser } ); // Inform the app a new rating is available $.publish( "/new/rating", { title: strMovie, rating: strRating} ); }); })( jQuery ); 例如:解耦基于Ajax的应用程序的jQuery 在我们的最后一个例子,我们将采取在如何早期使用的Pub / Sub在发展过程中可以在以后为我们节省一些潜在的痛苦的去耦重构我们的代码实际的样子。 经常在Ajax的应用程序重,一旦我们收到我们要实现的不仅仅是一个唯一的作用更多的是请求的响应。人们可以简单地增加他们的所有请求后的逻辑变成了​​成功回调,但也有缺点,这种方法。 高度耦合应用有时增加重用功能所需的努力由于增加间功能/码依赖性。这意味着,尽管保持我们在回调硬编码请求后的逻辑,如果我们只是试图抓住设置一次的结果可能是罚款,它时,我们要作进一步的Ajax调用同一个数据源并不适当(和不同的最终行为)无需重写代码多次的部分。而不是通过每个调用数据源同一层回去以后他们推广,我们可以使用的pub / sub从一开始就和节省时间。 利用观察员,我们还可以对不同的事件到任何粒度级别,我们感到满意轻易单独的应用程序范围内的通知 - 一些东西,也可以用其它方式来优雅少做。 请注意如何我们下面的示例中,当用户表示他们想使搜索查询一个主题的通知是由当请求返回与实际数据可用于消费的另一个而成。它留给用户能够再决定如何使用这些事件的知识(或返回的数据)。这样做的好处是,如果我们想,我们可以有利用不同的方式返回,但至于阿贾克斯层而言,它并不关心数据10个不同的用户。它的唯一责任是请求并返回数据,然后把它传递给谁想来使用它。这样的事务分离可以使我们的代码少许清洁剂的整体设计。 HTML /模板: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
    JavaScript的: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 ;(function( $ ) { // Pre-compile template and "cache" it using closure var resultTemplate = _.template($( "#resultTemplate" ).html()); // Subscribe to the new search tags topic $.subscribe( "/search/tags", function( e, tags ) { $( "#lastQuery" ) .html("

    Searched for:" + tags + "

    "); }); // Subscribe to the new results topic $.subscribe( "/search/resultSet", function( e, results ){ $( "#searchResults" ).empty().append(resultTemplate( results )); }); // Submit a search query and publish tags on the /search/tags topic $( "#flickrSearch" ).submit( function( e ) { e.preventDefault(); var tags = $(this).find( "#query").val(); if ( !tags ){ return; } $.publish( "/search/tags", [ $.trim(tags) ]); }); // Subscribe to new tags being published and perform // a search query using them. Once data has returned // publish this data for the rest of the application // to consume $.subscribe("/search/tags", function( e, tags ) { $.getJSON( "http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?", { tags: tags, tagmode: "any", format: "json" }, function( data ){ if( !data.items.length ) { return; } $.publish( "/search/resultSet", { items: data.items } ); }); }); })( jQuery ); 观察者模式是在应用程序设计解耦许多不同的情况下非常有用,如果你还没有使用它了,我建议拿起今天提到的预先写好的实现方式之一,只是给它一个尝试。这是比较容易的设计模式上手之一,但也是最有实力的一个。 调解模式 在Observer模式的部分,我们介绍了通过一个单一的对象窜多个事件源的方式。这也被称为发布/订阅或事件汇总。是很常见的,当面对这个问题开发商认为调解员,所以让我们来看看它们的区别。 这本字典是指调解员作为中立的一方,在谈判和解决冲突助攻。在我们的世界中,介体是一种行为的设计图案,使我们能够露出,通过该系统的不同部分可能进行通信的统一接口。 如果它出现一个系统具有部件之间有太多的直接关系,这可能是时间为具有部件通过代替通信控制的中心点。调解员促进通过确保,而不是称呼对方明确的组件,它们的交互是通过这个中心的处理松耦合。这可以帮助我们分离系统和提高组件重用的可能性。 一个真实世界的比喻可能是一个典型的机场交通控制系统。塔(中保)处理什么飞机可以起飞和降落,因为所有通信(通知被监听出或广播)从平面做控制塔,而不是从平面到平面。集中式控制器的关键是这个系统的成功,这就是真正的软件设计一个调解员发挥的作用。 另一个比喻是DOM事件冒泡和事件代表团。如果系统中的所有订阅对文档进行,而非单独的节点,该文件有效地充当调停人。代替结合到单个节点的事件,有较高水平的对象给出通知有关的交互事件的用户的责任。 当涉及到中介和事件聚合模式,也有一些时候它可能看起来像模式是可以互换的,由于实施相似之处。然而,这些模式的语义和意图是非常不同的。 而且即使实现都使用一些相同的核心结构,我相信他们之间有一个明显的区别。我还认为它们不应该被互换或在通信混淆的,因为差异。 一个简单的中介 调解员是协调多个对象之间的交互(逻辑和行为)的对象。它使上何时调用哪些对象的基础上,其它目的和输入的动作(或不采取)的决定。 您可以使用一个单一的代码行写的调解人: ? 1 var mediator = {}; 是的,当然这只是一个对象在JavaScript中的文字。再次,我们在这里谈论的语义。调停的目的是控制对象之间的工作流程,我们真的不需要什么比对象文本来做到这一点了。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var orgChart = { addNewEmployee: function(){ // getEmployeeDetail provides a view that users interact with var employeeDetail = this.getEmployeeDetail(); // when the employee detail is complete, the mediator (the 'orgchart' object) // decides what should happen next employeeDetail.on("complete", function(employee){ // set up additional objects that have additional events, which are used // by the mediator to do additional things var managerSelector = this.selectManager(employee); managerSelector.on("save", function(employee){ employee.save(); }); }); }, // ... } 这个例子显示了一些实用的方法,可以触发并订阅事件调解对象的一个​​非常基本实现。 我经常提到的这种类型的对象是在过去“工作流”的对象,但事实是,它是一个中介。它是处理许多其他对象之间的工作流程,聚合的该工作流知识的责任成一个单一的对象的对象。其结果是工作流更容易理解和维护。 异同 还有,毫无疑问,我已经在这里显示的事件聚合和调解员的例子之间的相似性。这些相似归结为两个主要项目:事件和第三方的对象。这些差异充其量肤浅的,虽然。当我们挖入图案的意图和看到,实施方式可以显着地不同,图案的性质变得更明显。 活动 两个事件聚合和介体使用事件,在上述的例子。事件聚合与事件显然涉及 - 它在名称后的所有。调解员只使用事件,因为它使生活容易与现代的JavaScript web应用框架的时候。没有什么说的调解人必须用事件来构建。你可以建立回调方法调停,靠,或任何其他的一些手段交给调解员参考子对象。 差,那么,是为什么这两个模式都使用事件。事件聚合器,作为图案,被设计来处理事件。调解员,虽然只使用它们,因为它的方便。 第三方对象 这两个事件聚合和调解员,在设计上,采用了第三方对象,以方便的事情。本次活动聚集本身是一个第三方的事件发布者和事件订阅。它作为一个中心枢纽事件通过。介体也是第三方其它的目的,虽然。那么,是区别?我们为什么不叫事件聚合调解员?答案在很大程度上归结到应用程序逻辑和工作流进行编码。 在一个事件聚合的情况下,第三方对象是存在的只是从一个未知的数源事件的直通,促进一个未知的数字处理程序。需要被踢掉所有工作流和业务逻辑被直接放入触发事件和处理事件的对象的对象。 在调解员的情况下,虽然,业务逻辑和工作流聚合到中介本身。介决定当一个对象应该叫其方法和属性更新的基础上,该介体知道的因素。它封装了工作流程和过程,协调多个对象,以产生所期望的系统行为。参与这一工作流程中的各个对象的每个知道如何执行自己的任务。但它告诉对象时,在高于单个对象更高层次的决策执行的任务的调解人。 事件聚合方便了“射后不理”的沟通模式。触发事件的对象,如果有任何用户并不关心。它只是触发事件和动作上。调解员,虽然可以使用事件作出决定,但它绝对不是“发射后不管”。介体关注一组已知的输入或活动的,以便它可以促进和协调与一组已知的行动者​​(对象)的附加行为。 关系:当要使用哪个 了解一个事件聚合和中介之间的异同是语义重要的原因。了解什么时候使用哪一种模式,虽然它是同样重要的。基本的语义和模式的意图并告知使用模式将帮助您了解更多细微的问题和必须作出细致入微的决定时的问题,但实际体验。 事件聚合使用 一般情况下,一个事件聚合器是用来当你要么有太多的对象,直接听,或者你有一个完全不相关的对象。 当两个对象有直接的关系已经 - 说,父视图和子视图 - 则可能是使用一个事件聚合没有什么好处。让孩子认为触发一个事件与父视图可以处理该事件。在JavaScript框架而言,这是最常见于骨干的收集和型号,所有型号的事件冒泡并通过其父集合。一件收藏品经常使用模型事件来修改自己或其他型号的状态。集合中的处理“中选择”项目就是一个很好的例子。 jQuery的方法上作为一个事件聚合器是太多的对象来听一个很好的例子。如果你有一个可以触发一个“点击”事件10,20或200 DOM元素,这可能是一个坏主意,设立一个监听器上所有的人单独。这可能会迅速恶化的应用和用户体验性能。相反,使用jQuery的上方法允许我们聚集所有的事件,减少的10,20,或200的事件处理程序的开销降低到1。 间接的关系也是一个伟大的时间使用事件聚合。在现代的应用程序,这是很常见的有需要通信,但没有直接的关系的多个视图的对象。例如,一个菜单系统可能具有处理该菜单项的点击次数的图。但是,我们不希望在菜单中直接连接到显示所有的细节和信息,单击菜单项时内容的看法。具有连接在一起的内容和菜单会使代码很难维持,从长远来看。相反,我们可以使用事件聚合器来触发“菜单:单击:foo”的事件,并有一个“foo”的对象处理click事件,以显示其屏幕上的内容。 使用中保 当两个或多个对象有间接的工作关系的介体最好的应用,和业务逻辑或工作流需要决定这些对象的交互和协作。 一个向导界面就是一个很好的例子,如图所示的“组织结构图”的例子,上面。有促进向导的整个工作流程多个视图。而不是让他们直接引用对方死死的观点耦合在一起,我们就可以去耦,并通过引入中介更明确它们之间的工作流模型。 调解员提取从实施细则的工作流程和在更高层次上产生更自然的抽象,我们展示以更快便知的工作流程是什么。我们不再需要深入到工作流程中的每个视图的细节,看看有什么工作流程实际上是。 事件聚合器(发布/订阅)和中介一起 一个事件聚合和介体,以及为什么这些图案名字不应彼此互换之间的差的核心,是通过说明如何可以将它们一起使用示出最好的。一个事件聚合器菜单的例子是引入调解员以及完美的地方。 点击菜单项可能会引发一系列的整个应用程序中的变化。其中的一些变化将是独立于其它的,并且使用一个事件聚合为这是有意义的。其中一些变化可能是内部互相关联,不过,可以使用介质制定这些变化。 调解员的话,可以设置为监听事件聚合器。它可以运行它的逻辑和处理,以促进和坐标彼此相关的多个对象,但无关的原始事件源。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 var MenuItem = MyFrameworkView.extend({ events: { "click .thatThing": "clickedIt" }, clickedIt: function(e){ e.preventDefault(); // assume this triggers "menu:click:foo" MyFramework.trigger("menu:click:" + this.model.get("name")); } }); // ... somewhere else in the app var MyWorkflow = function(){ MyFramework.on("menu:click:foo", this.doStuff, this); }; MyWorkflow.prototype.doStuff = function(){ // instantiate multiple objects here. // set up event handlers for those objects. // coordinate all of the objects into a meaningful workflow. }; 在这个例子中,被点击以正确的模式的菜单项时,该“menu:click:foo”事件将被触发。在“MyWorkflow”对象的实例,假设一个已经实例化,将处理这一特定事件,并协调所有它知道的物体,以创建所需的用户体验和工作流程。 一个事件聚合和介体已被组合以创建在代码和应用程​​序本身既更加有意义的经验。我们现在有通过事件聚合器的菜单和工作流程之间的清晰的分离,我们仍保持工作流本身的清洁和维护通过使用调解人。 优势与劣势 中保图案的最大好处是它减少了从许多系统中的对象或组件之间所需的许多只多对一通信信道。添加新的发布者和订阅相对容易由于解耦本的水平。 也许使用模式最大的缺点是,它可以引入单一故障点。配售模块之间的协调也可能会导致性能的下降,因为他们总是间接地通信。由于松散耦合的性质,很难建立一个怎样的系统可能只在看广播反应。 这就是说,要提醒自己,分离的系统有一些其他的好处是有用的 - 如果我们的模块直接互相沟通,更改模块(例如,另一个模块抛出异常)可以很容易地对我们的应用程序的其它部分的多米诺骨牌效应。这个问题是以下,相分离的系统一个关注的问题。 在一天结束的时候,紧耦合引起各种头痛,这是另一种替代解决方案,但一个,如果正确实施,可以很好地工作。 中保比。正面 我们将很快覆盖Facade模式,而是以供参考一些开发商也可能会问,是否有中保和门面模式之间的相似之处。他们这样做既抽象现有模块的功能,但也有一些细微的差别。 调解员集中在那里进行了明确由这些模块引用的模块之间的通信。在某种意义上,这是多向。门面但是只是定义了一个简单的界面,模块或系统,但不会增加任何额外的功能。系统中的其他模块是不直接知道的外观的概念,并且可以考虑单向的。 原型模式 GoF的参考原型模式是一种能够创建基于克隆通过现有对象的模板对象。 我们可以把原型模式,因为是基于我们创建一个作为原型的其他对象的对象原型继承。原型对象本身有效地用作蓝本每个对象的构造函数创建。如果所使用的构造函数的原型含有一种叫物业name为例(按代码示例低了下去),然后通过同样的构造函数创建的每个对象也会有同样的性质。 检讨现有(非JavaScript),文学这种模式的定义,我们可以再次找到对类的引用。现实情况是,原型继承避免使用类干脆。没有一个“定义”对象,也不在理论上一个核心对象。我们简单地创建现有的功能对象的副本。 对使用的原型模式的好处是,我们正在与原型优势的JavaScript所提供的本地,而不是试图去模仿其他语言的特色工作。与其它的设计模式,这并非总是如此。 不仅是模式来实现继承的一种简单的方法,但它也可以配备了性能提升,以及:一个对象定义一个函数的时候,他们都通过引用创建(使所有子对象指向同一个函数)而不是创建自己的个人副本。 对于那些有兴趣,真正的原型继承,如5的ECMAScript标准定义,需要使用Object.create(这是我们以前在本节前面介绍过)。要提醒自己,Object.create创建了一个具有特定原型的对象,并可选地包含指定的属性,以及(例如Object.create( prototype, optionalDescriptorObjects ))。 我们可以看到这足以证明在下面的例子: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var myCar = { name: "Ford Escort", drive: function () { console.log( "Weeee. I'm driving!" ); }, panic: function () { console.log( "Wait. How do you stop this thing?" ); } }; // Use Object.create to instantiate a new car var yourCar = Object.create( myCar ); // Now we can see that one is a prototype of the other console.log( yourCar.name ); Object.create也可以让我们轻松实现先进的概念,如继承差的对象在哪里都能够直接从其他对象继承。我们在前面看到的那Object.create让我们来初始化使用第二个提供的参数对象属性。例如: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var vehicle = { getModel: function () { console.log( "The model of this vehicle is.." + this.model ); } }; var car = Object.create(vehicle, { "id": { value: MY_GLOBAL.nextId(), // writable:false, configurable:false by default enumerable: true }, "model": { value: "Ford", enumerable: true } }); 这里的属性可以对第二个参数进行初始化Object.create使用对象文本与类似于由使用的语法Object.defineProperties和Object.defineProperty我们看着前面的方法。 值得注意的是枚举对象的属性时,和原型关系可以带来麻烦(如克罗克福德建议)包裹循环的内容中hasOwnProperty()检查。 如果我们希望实现的原型模式而不直接使用Object.create,我们可以模拟模式按照上面的例子如下: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 var vehiclePrototype = { init: function ( carModel ) { this.model = carModel; }, getModel: function () { console.log( "The model of this vehicle is.." + this.model); } }; function vehicle( model ) { function F() {}; F.prototype = vehiclePrototype; var f = new F(); f.init( model ); return f; } var car = vehicle( "Ford Escort" ); car.getModel(); 注意:这种替代不允许用户以相同的方式定义只读属性(作为vehiclePrototype可能如果不小心被改变)。 最后一种选择的实现原型模式可能是以下几点: ? 1 2 3 4 五 6 7 8 9 var beget = (function () { function F() {} return function ( proto ) { F.prototype = proto; return new F(); }; })(); 人们可以从引用此方法vehicle的功能。但请注意,vehicle这里是模拟一个构造函数,因为原型模式不包括初始化任何概念超越的对象链接到一个原型。 命令模式 Command模式的目的是封装方法调用,请求或操作成一个对象,并给了我们两个参数的能力,并通过方法调用周围,可以在我们的判断来执行。此外,它使我们能够分离对象从其中实现它们的对象上调用的动作,使我们的整体灵活性在换出混凝土更大程度的类(对象)。 混凝土类在基于类的编程语言方面最好的解释,并与抽象类的想法。一个抽象类定义了一个接口,但并不一定对所有的其成员函数提供实现。它作为从该其他衍生的基类。它实现了缺失的功能派生类称为具体类。 命令模式背后的一般思路是,它提供了我们分离发出从任何执行命令的命令,委派这个责任不同的对象,而不是的职责的装置。 实施明智的,简单的命令对象绑定在一起既是一个行动,并希望调用操作的对象。他们始终包括执行操作(如run()或execute())。根据需要具有相同接口的所有命令的对象可以容易地被交换,这被认为是该图案的更大的好处之一。 为了证明我们将创建一个简单的购车服务Command模式。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 (function(){ var carManager = { // request information requestInfo: function( model, id ){ return "The information for " + model + " with ID " + id + " is foobar"; }, // purchase the car buyVehicle: function( model, id ){ return "You have successfully purchased Item " + id + ", a " + model; }, // arrange a viewing arrangeViewing: function( model, id ){ return "You have successfully booked a viewing of " + model + " ( " + id + " ) "; } }; })(); 以一看上面的代码,这将是微不足道的调用我们carManager通过直接访问对象的方法。我们都可以原谅的想法有没有错-在技术上,它是完全有效的JavaScript。然而有场景中,这可能是不利的。 例如,想象一下,如果背后的核心API的carManager变化。这将需要直接访问我们的应用程序中,这些方法也可以修改的所有对象。这可以被看作是耦合层,有效地违背尽可能松散耦合对象的面向对象的方法。相反,我们可以通过远进一步抽象的API解决这个问题。 现在让我们来扩展我们carManager,使我们在命令模式应用成果如下:接受可以在上执行任何命名方法carManager的对象,以及可能用于诸如汽车模型和ID的任何数据传递。 这是我们想怎么能够实现: ? 1 carManager.execute( "buyVehicle", "Ford Escort", "453543" ); 按照这种结构,我们现在应该增加一个定义为carManager.execute方法如下: ? 1 2 3 carManager.execute = function ( name ) { return carManager[name] && carManager[name].apply( carManager, [].slice.call(arguments, 1) ); }; 我们的最终样本通话将因此如下所示: ? 1 2 3 4 carManager.execute( "arrangeViewing", "Ferrari", "14523" ); carManager.execute( "requestInfo", "Ford Mondeo", "54323" ); carManager.execute( "requestInfo", "Ford Escort", "34232" ); carManager.execute( "buyVehicle", "Ford Escort", "34232" ); Facade模式 当我们提出了一个门面,我们提出了一个外观的世界可能掩盖一个非常不同的现实。这是我们要检讨下一个模式背后的名字灵感 - Facade模式。这种模式提供了一个方便的高级接口的代码较大的机身,隐藏其真正的潜在的复杂性。把它看成是简化了被提交给其他开发者的API,东西几乎总是提高了可用性。 外立面是可以经常在JavaScript库像jQuery在这里可以看出,虽然实现可支持具有广泛的行为,只是一个“门面”,或这些方法的有限抽象的方法呈现给公众使用的结构模式。 这使我们能够与门面,而不是直接在幕后子系统进行交互。每当我们使用jQuery的$(el).css()或$(el).animate()方法,我们实际上使用的是门面-简单的公共接口,避免我们不得不手动调用得到一些行为的工作要求的jQuery核心的许多内部的方法。这也避免了需要手动的DOM API互动,保持状态变量。 jQuery的核心方法,应考虑中间抽象。更直接的负担,开发商是DOM API和外墙是什么使jQuery库很容易使用。 要建立什么我们学到,Facade模式既简化了类的接口,它也从使用它的代码分离出来的类。这给我们的方式,有时可以比直接访问子系统不容易出现误差的子系统来间接相互作用的能力。门面的优点包括易于使用和实施模式通常体积小,占用空间。 让我们来看看在行动模式。这是一个未经优化的代码示例,但是在这里我们利用门面简化的接口用于监听事件的跨浏览器。我们通过创建可在一个人的代码,这确实为特征的存在检查,以便它可以提供安全和跨浏览器兼容的解决方案的任务中使用的常用方法做到这一点。 ? 1 2 3 4 五 6 7 8 9 10 11 var addMyEvent = function( el,ev,fn ){ if( el.addEventListener ){ el.addEventListener( ev,fn, false ); }else if(el.attachEvent){ el.attachEvent( "on" + ev, fn ); } else{ el["on" + ev] = fn; } }; 以类似的方式,我们都熟悉的jQuery的$(document).ready(..)。在内部,这实际上是被供电由称为方法bindReady(),它是这样做的: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 bindReady: function() { ... if ( document.addEventListener ) { // Use the handy event callback document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); // A fallback to window.onload, that will always work window.addEventListener( "load", jQuery.ready, false ); // If IE event model is used } else if ( document.attachEvent ) { document.attachEvent( "onreadystatechange", DOMContentLoaded ); // A fallback to window.onload, that will always work window.attachEvent( "onload", jQuery.ready ); ... 这是一个正面,在世界的其余部分只是使用由露出的有限接口的另一个例子$(document).ready(..)和更复杂的实现供电其保持从视线隐藏。 外立面不只是必须对自己使用的,但是。它们也可以与其他模式集成诸如模块图案。正如我们下面可以看到,我们的模块模式的实例包含许多已私下定义的方法。然后一个门面来一个更简单的API提供给访问这些方法: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 var module = (function() { var _private = { i: 5, get: function() { console.log( "current value:" + this.i); }, set: function( val ) { this.i = val; }, run: function() { console.log( "running" ); }, jump: function(){ console.log( "jumping" ); } }; return { facade: function( args ) { _private.set(args.val); _private.get(); if ( args.run ) { _private.run(); } } }; }()); // Outputs: "current value: 10" and "running" module.facade( {run: true, val: 10} ); 在这个例子中,主叫module.facade()确实会触发一组私人行为模块内,但再次,用户并不关心这一点。我们已经取得了更容易让他们消耗的功能,而不需要担心的是执行层面的细节。 在抽象的注意事项 外立面一般有一些缺点,但有担心值得注意的是性能。即,必须确定是否存在到抽象隐式成本门面提供给我们的实现,并且如果是这样,这成本是否正当。回过头来看看这个jQuery库,我们大多数人都知道这两个getElementById("identifier")和$("#identifier")可用于通过其ID来查询页面上的元素。 你知道那不过getElementById()对自己显著快是数量级的高阶?看看这个jsPerf测试,看看在每个浏览器级别的结果:http://jsperf.com/getelementbyid-vs-jquery-id。现在当然,我们必须记住,jQuery的(和灒-它的选择器引擎)都做了很多幕后来优化我们的查询(和jQuery对象,则返回不仅仅是一个DOM节点)。 与此特定门面的挑战是,为了提供能够接受并解析多种类型的查询的优雅选择器功能,还有抽象隐式成本。用户不需要访问jQuery.getById("identifier")或jQuery.getByClass("identifier")等等。这就是说,在权衡性能已经在实践中多年来的测试,并给出jQuery的成功,简单的外观其实还算不错的球队。 当使用模式,尽量了解所涉及的任何性能成本,使他们是否值得抽象所提供的级别的呼叫。 工厂模式 工厂模式是关心创建对象的概念,另一个创建模式。在那里从在其类别中的其他模式不同的是,它没有明确要求我们使用构造。相反,工厂可以提供用于创建对象,在这里我们可以指定希望创建工厂对象的类型的通用接口。 试想一下,我们有一个UI工厂,我们被要求创建一个类型的UI组件。而不是直接创建这个组件使用new运营商或通过其他造物的构造函数,我们要求工厂对象为一个新的组件,而不是。我们告知需要什么(例如“按钮”,“小组”)的对象类型的工厂,并实例化这一点,就返回到我们的使用。 这是特别有用的,如果对象创建过程比较复杂,例如,如果它强烈地依赖于动态因素或应用程序配置。 这种模式的例子可以在UI库中找到,如ExtJS的,其中,用于创建对象或组件的方法可以进一步子类。 以下是建立在我们以前使用构造图形逻辑来定义汽车片段的例子。它演示了如何一个汽车厂可以使用工厂模式来实现: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 // Types.js - Constructors used behind the scenes // A constructor for defining new cars function Car( options ) { // some defaults this.doors = options.doors || 4; this.state = options.state || "brand new"; this.color = options.color || "silver"; } // A constructor for defining new trucks function Truck( options){ this.state = options.state || "used"; this.wheelSize = options.wheelSize || "large"; this.color = options.color || "blue"; } // FactoryExample.js // Define a skeleton vehicle factory function VehicleFactory() {} // Define the prototypes and utilities for this factory // Our default vehicleClass is Car VehicleFactory.prototype.vehicleClass = Car; // Our Factory method for creating new Vehicle instances VehicleFactory.prototype.createVehicle = function ( options ) { switch(options.vehicleType){ case "car": this.vehicleClass = Car; break; case "truck": this.vehicleClass = Truck; break; //defaults to VehicleFactory.prototype.vehicleClass (Car) } return new this.vehicleClass( options ); }; // Create an instance of our factory that makes cars var carFactory = new VehicleFactory(); var car = carFactory.createVehicle( { vehicleType: "car", color: "yellow", doors: 6 } ); // Test to confirm our car was created using the vehicleClass/prototype Car // Outputs: true console.log( car instanceof Car ); // Outputs: Car object of color "yellow", doors: 6 in a "brand new" state console.log( car ); 方法1:修改VehicleFactory实例以使用卡车类 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 var movingTruck = carFactory.createVehicle( { vehicleType: "truck", state: "like new", color: "red", wheelSize: "small" } ); // Test to confirm our truck was created with the vehicleClass/prototype Truck // Outputs: true console.log( movingTruck instanceof Truck ); // Outputs: Truck object of color "red", a "like new" state // and a "small" wheelSize console.log( movingTruck ); 方法2:子类VehicleFactory创建一个工厂类,建立卡车 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 function TruckFactory () {} TruckFactory.prototype = new VehicleFactory(); TruckFactory.prototype.vehicleClass = Truck; var truckFactory = new TruckFactory(); var myBigTruck = truckFactory.createVehicle( { state: "omg..so bad.", color: "pink", wheelSize: "so big" } ); // Confirms that myBigTruck was created with the prototype Truck // Outputs: true console.log( myBigTruck instanceof Truck ); // Outputs: Truck object with the color "pink", wheelSize "so big" // and state "omg. so bad" console.log( myBigTruck ); 当使用工厂模式 当应用于下列情形工厂模式可以是特别有用: 当我们的对象或组件设置涉及复杂的高水平 当我们需要方便地生成依赖于我们在环境中的对象的不同实例 当我们有许多小对象或组件的工作共享相同的属性 在编写与其他物体仅需要满足API契约的实例对象(又名鸭打字)工作。这是去耦有用。 当不使用工厂模式 当应用到错误类型的问题,这种模式可以引入复杂的不必要的大量工作的应用程序。除非提供创建对象的接口是图书馆或框架的设计目标,我们正在写,我会建议坚持明确的构造函数,以避免不必要的开销。 由于这样的事实,对象创建的过程中有效抽象接口后面,这也可以引入具有取决于这一过程可能只是多么复杂的单元测试的问题。 摘要工厂 也是有用要意识到的抽象工厂模式,其目的是封装一组个别工厂的一个共同的目标。它分离实施从他们一般使用一组对象的细节。 一个抽象工厂应或用在一个系统必须独立于产生它创建的对象的方式,它需要与多个对象类型的工作。 这是既简单又容易理解的例子是一个汽车厂,它定义的方式来获取或登记的车辆类型。抽象工厂可以被命名abstractVehicleFactory。抽象工厂将允许的类型,如“汽车”或“卡车”和混凝土工厂车辆的定义将实施仅满足车辆合同(例如类Vehicle.prototype.drive和Vehicle.prototype.breakDown)。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 var abstractVehicleFactory = (function () { // Storage for our vehicle types var types = {}; return { getVehicle: function ( type, customizations ) { var Vehicle = types[type]; return (Vehicle ? new Vehicle(customizations) : null); }, registerVehicle: function ( type, Vehicle ) { var proto = Vehicle.prototype; // only register classes that fulfill the vehicle contract if ( proto.drive && proto.breakDown ) { types[type] = Vehicle; } return abstractVehicleFactory; } }; })(); // Usage: abstractVehicleFactory.registerVehicle( "car", Car ); abstractVehicleFactory.registerVehicle( "truck", Truck ); // Instantiate a new car based on the abstract vehicle type var car = abstractVehicleFactory.getVehicle( "car", { color: "lime green", state: "like new" } ); // Instantiate a new truck in a similar manner var truck = abstractVehicleFactory.getVehicle( "truck", { wheelSize: "medium", color: "neon yellow" } ); 的混入模式 在传统的编程语言如C ++和Lisp,混入是它提供可通过子类的子类或一组很容易地继承了功能重复使用的目的功能的类。 子类 对于开发人员不熟悉子类,我们将通过一个简短的初学者底漆他们潜入混入和装饰前进一步。 子分级是指继承属性,用于从基或一个新的对象一个术语超类对象。在传统的面向对象的程序设计,一类B是能延长另一个类A。这里我们考虑A一个超类和B子类A。因此,所有实例B继承的方法A。B然而仍能够定义自己的方法,包括最初定义的那些重写方法A。 如果B需要调用的方法A已被重写,我们将此称为方法链接。如果B需要调用构造函数A(超类),我们称此构造链接。 为了证明子分级,我们首先需要一个基础对象,可以有创建的本身新的实例。让我们建模此周围的人的概念。 ? 1 2 3 4 五 6 7 var Person = function( firstName, lastName ){ this.firstName = firstName; this.lastName = lastName; this.gender = "male"; }; 接下来,我们将要指定一个新的类(对象),这是现有的子类Person对象。让我们想象一下,我们要添加不同的特性来区分一个Person从Superhero而继承的财产Person“超”。作为超级英雄与正常的人(如姓名,性别等)有着许多共同的特征,这应该有希望说明子类是如何工作的充分。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // a new instance of Person can then easily be created as follows: var clark = new Person( "Clark", "Kent" ); // Define a subclass constructor for for "Superhero": var Superhero = function( firstName, lastName, powers ){ // Invoke the superclass constructor on the new object // then use .call() to invoke the constructor as a method of // the object to be initialized. Person.call( this, firstName, lastName ); // Finally, store their powers, a new array of traits not found in a normal "Person" this.powers = powers; }; Superhero.prototype = Object.create( Person.prototype ); var superman = new Superhero( "Clark", "Kent", ["flight","heat-vision"] ); console.log( superman ); // Outputs Person attributes as well as powers 该Superhero构造函数创建从继承的对象Person。这种类型的对象具有在它上面链中的对象的属性,如果我们已在设置默认值Person对象,Superhero是能够与特定于它的对象的值覆盖任何继承的值。 混入 在JavaScript中,我们可以看看从混入继承通过扩展功能收集的手段。我们定义每个新对象都有一个原型从它可以继承进一步属性。原型可以从其他对象的原型继承,但更重要的是,可以为任意数量的对象实例定义属性。我们可以利用这一点来促进功能的再利用。 混入允许对象从它们借(或继承)的功能与复杂性的最小量。由于该模式运行良好与JavaScript的对象原型,它使我们从不只是一个混入共享功能相当灵活的方式,而是通过多重继承有效很多。 它们可以被看作是可跨多个其他对象原型容易地共用属性和方法的对象。试想一下,我们定义包含在一个标准的对象字面如下效用函数一个mixin: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 var myMixins = { moveUp: function(){ console.log( "move up" ); }, moveDown: function(){ console.log( "move down" ); }, stop: function(){ console.log( "stop! in the name of love!" ); } }; 然后,我们可以轻松地扩展现有的构造函数的原型使用的辅助,如Underscore.js包括此行为_.extend()方式: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 // A skeleton carAnimator constructor function CarAnimator(){ this.moveLeft = function(){ console.log( "move left" ); }; } // A skeleton personAnimator constructor function PersonAnimator(){ this.moveRandomly = function(){ /*..*/ }; } // Extend both constructors with our Mixin _.extend( CarAnimator.prototype, myMixins ); _.extend( PersonAnimator.prototype, myMixins ); // Create a new instance of carAnimator var myAnimator = new CarAnimator(); myAnimator.moveLeft(); myAnimator.moveDown(); myAnimator.stop(); // Outputs: // move left // move down // stop! in the name of love! 正如我们所看到的,这允许在公共行为我们很容易地“混”进对象构造相当平凡。 在下面的例子中,我们有两个构造函数:一辆汽车,一个mixin。我们现在要做的是增加(话说延长另一种方式)的车,所以它能够继承的混入,即确定具体方法driveForward()和driveBackward()。这一次,我们不会使用Underscore.js。 相反,这个例子将演示如何扩充构造,包括功能,而不需要重复这个过程,我们可以有充分的构造函数。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 // Define a simple Car constructor var Car = function ( settings ) { this.model = settings.model || "no model provided"; this.color = settings.color || "no colour provided"; }; // Mixin var Mixin = function () {}; Mixin.prototype = { driveForward: function () { console.log( "drive forward" ); }, driveBackward: function () { console.log( "drive backward" ); }, driveSideways: function () { console.log( "drive sideways" ); } }; // Extend an existing object with a method from another function augment( receivingClass, givingClass ) { // only provide certain methods if ( arguments[2] ) { for ( var i = 2, len = arguments.length; i < len; i++ ) { receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]]; } } // provide all methods else { for ( var methodName in givingClass.prototype ) { // check to make sure the receiving class doesn't // have a method of the same name as the one currently // being processed if ( !Object.hasOwnProperty.call(receivingClass.prototype, methodName) ) { receivingClass.prototype[methodName] = givingClass.prototype[methodName]; } // Alternatively (check prototype chain as well): // if ( !receivingClass.prototype[methodName] ) { // receivingClass.prototype[methodName] = givingClass.prototype[methodName]; // } } } } // Augment the Car constructor to include "driveForward" and "driveBackward" augment( Car, Mixin, "driveForward", "driveBackward" ); // Create a new Car var myCar = new Car({ model: "Ford Escort", color: "blue" }); // Test to make sure we now have access to the methods myCar.driveForward(); myCar.driveBackward(); // Outputs: // drive forward // drive backward // We can also augment Car to include all functions from our mixin // by not explicitly listing a selection of them augment( Car, Mixin ); var mySportsCar = new Car({ model: "Porsche", color: "red" }); mySportsCar.driveSideways(); // Outputs: // drive sideways 优点缺点 混入协助降低功能重复和增加的系统功能的再利用。凡有申请可能需要跨对象实例共享的行为,我们可以很容易地通过保持一个mixin这个共享功能,从而专注于实现只在我们的系统,这是真正独特的功能避免任何重复。 这就是说,缺点是混入多一点值得商榷。一些开发商认为,注入功能集成到一个对象的原型是一个坏主意,因为它会导致两种原型污染和不确定性,关于我们的功能原点的水平。在大型系统中,这可能是这种情况。 我认为,强大的文档可以在最大限度地减少混乱关于混合函数源量协助,但与每一个模式,如果护理实施过程中采取了我们应该没问题。 修饰模式 装饰是旨在促进代码复用结构设计模式。类似混入,它们可以被认为是另一种可行的替代对象子分级。 经典,装饰提供给系统中的动态行为添加到现有类的能力。当时的想法是,该装饰本身并不是对类的基本功能必不可少,否则会被烤成超本身。 它们可以被用来修改在这里我们要添加额外的功能,对象,而不需要使用它们大量修改底层代码的现有系统。为什么开发人员使用他们的常见原因是他们的应用程序可能包含要求不同类型的对象量大的特点。想象一下,不必定义数百个不同的对象构造为说,一个JavaScript游戏。 对象的构造函数可以代表不同的球员 ​​类型,每种类型具有不同的功能。一个指环王游戏可能需要构造函数Hobbit,Elf,Orc,Wizard,Mountain Giant,Stone Giant等等,但很容易被数以百计的这些。如果我们再在能力因素,试想不得不为能力的类型如的每个组合创建子类HobbitWithRing,HobbitWithSword,HobbitWithRingAndSword等。本是不是很实用,肯定是不可控的时候,我们在越来越多的不同能力的因素。 Decorator模式是不是严重依赖于如何创建对象,而是专注于扩展其功能的问题。而不是仅仅依赖于原型继承,我们使用一个基本对象工作,并逐步添加装饰物而提供额外的功能。我们的想法是,与其子类中,我们添加(装饰)属性或方法到基本对象,所以它是一个小更精简。 添加新的属性在JavaScript对象是一个非常简单的过程所以考虑到这一点,一个非常简单的装饰可以实现如下: 例1:装饰的构造新功能 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 43 44 // A vehicle constructor function Vehicle( vehicleType ){ // some sane defaults this.vehicleType = vehicleType || "car"; this.model = "default"; this.license = "00000-000"; } // Test instance for a basic vehicle var testInstance = new Vehicle( "car" ); console.log( testInstance ); // Outputs: // vehicle: car, model:default, license: 00000-000 // Lets create a new instance of vehicle, to be decorated var truck = new Vehicle( "truck" ); // New functionality we're decorating vehicle with truck.setModel = function( modelName ){ this.model = modelName; }; truck.setColor = function( color ){ this.color = color; }; // Test the value setters and value assignment works correctly truck.setModel( "CAT" ); truck.setColor( "blue" ); console.log( truck ); // Outputs: // vehicle:truck, model:CAT, color: blue // Demonstrate "vehicle" is still unaltered var secondInstance = new Vehicle( "car" ); console.log( secondInstance ); // Outputs: // vehicle: car, model:default, license: 00000-000 这种类型的简单执行的是功能性的,但它并没有真正表现出所有的力量装饰所提供的。对于这一点,我们首先要经过我的咖啡例子的变化从所谓的优秀图书深入浅出设计模式弗里曼,Sierra和贝茨,这是围绕一台Macbook购买建模。 例2:装饰对象使​​用多个装饰 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 // The constructor to decorate function MacBook() { this.cost = function () { return 997; }; this.screenSize = function () { return 11.6; }; } // Decorator 1 function memory( macbook ) { var v = macbook.cost(); macbook.cost = function() { return v + 75; }; } // Decorator 2 function engraving( macbook ){ var v = macbook.cost(); macbook.cost = function(){ return v + 200; }; } // Decorator 3 function insurance( macbook ){ var v = macbook.cost(); macbook.cost = function(){ return v + 250; }; } var mb = new MacBook(); memory( mb ); engraving( mb ); insurance( mb ); // Outputs: 1522 console.log( mb.cost() ); // Outputs: 11.6 console.log( mb.screenSize() ); 在上面的例子中,我们的装饰被覆盖MacBook()超类对象.cost()函数返回的目前的价格Macbook加上升级的费用被指定。 它被认为一个装饰作为原始Macbook它们不重写对象的构造方法(例如screenSize()),以及我们可将其定义为的一部分的任何其它性质Macbook保持不变,完好。 确实没有定义的接口在上述的例子,并从制作者到接收器移动时,我们移远的确保的对象的责任符合一个接口。 伪古典装饰 现在我们要首先检查在JavaScript形式呈现的装饰的变化专业JavaScript的设计模式(PJDP)由达斯汀·迪亚兹和罗斯Harmes。 不像一些早期的例子中,迪亚兹和Harmes坚持更紧密地如何装饰在其他编程语言(如Java或C ++)使用一个“接口”,我们将详细界定不久的概念实现的。 注: Decorator模式的这种特殊的变化提供了参考。如果发现它过于复杂,我建议选择了前面介绍的简单的实现之一。 接口 PJDP描述了装饰作为被用于透明包装相同的接口的其它对象中的对象的图形。一个接口是定义对象的方法的一个方式应具有,但是,它实际上并不直接指定这些方法应该如何实现。 它们还可以指示什么参数的方法采取,但这被认为是可选的。 那么,我们为什么要在JavaScript中使用的界面?我们的想法是,他们是自我说明,并促进可重用性。从理论上讲,接口也使通过确保对它们进行更改也必须实施它们的对象作出代码更加稳定。 下面是使用鸭打字的JavaScript接口实现的一个例子 - 一种方式,可以帮助确定对象是否是基于它实现的方法构造函数/对象的实例。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 // Create interfaces using a pre-defined Interface // constructor that accepts an interface name and // skeleton methods to expose. // In our reminder example summary() and placeOrder() // represent functionality the interface should // support var reminder = new Interface( "List", ["summary", "placeOrder"] ); var properties = { name: "Remember to buy the milk", date: "05/06/2016", actions:{ summary: function (){ return "Remember to buy the milk, we are almost out!"; }, placeOrder: function (){ return "Ordering milk from your local grocery store"; } } }; // Now create a constructor implementing the above properties // and methods function Todo( config ){ // State the methods we expect to be supported // as well as the Interface instance being checked // against Interface.ensureImplements( config.actions, reminder ); this.name = config.name; this.methods = config.actions; } // Create a new instance of our Todo constructor var todoItem = new Todo( properties ); // Finally test to make sure these function correctly console.log( todoItem.methods.summary() ); console.log( todoItem.methods.placeOrder() ); // Outputs: // Remember to buy the milk, we are almost out! // Ordering milk from your local grocery store 在上述中,Interface.ensureImplements提供了这两个与严格的功能检查和代码Interface构造可以找到这里。 与接口的最大问题是,因为没有内置的JavaScript中对他们的支持,有我们试图仿效这可能不是一个理想的选择另一种语言的一个特征的危险。轻量级接口,可以在不很大的性能开销却使用,我们接下来看看抽象装饰使用此相同的概念。 摘要装饰 为了证明这个版本的Decorator模式的结构,我们要想象我们有一个超类车型Macbook再次和存储,可以让我们“修饰”我们的Macbook与一些增强功能支付额外费用。 改进包括升级到4GB或8GB内存,雕刻,Parallel或情况。现在,如果我们使用的是单独的子类的增强选项的每一种组合来模拟这一点,它可能是这个样子: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 var Macbook = function(){ //... }; var MacbookWith4GBRam = function(){}, MacbookWith8GBRam = function(){}, MacbookWith4GBRamAndEngraving = function(){}, MacbookWith8GBRamAndEngraving = function(){}, MacbookWith8GBRamAndParallels = function(){}, MacbookWith4GBRamAndParallels = function(){}, MacbookWith8GBRamAndParallelsAndCase = function(){}, MacbookWith4GBRamAndParallelsAndCase = function(){}, MacbookWith8GBRamAndParallelsAndCaseAndInsurance = function(){}, MacbookWith4GBRamAndParallelsAndCaseAndInsurance = function(){}; 等等。 这将是一个不切实际的溶液作为新的子类将需要增强了可用的每个可能的组合。正如我们宁愿让事情变得简单不维护大集的子类,让我们看看如何可以用来装饰,以更好地解决这个问题。 而不是要求所有我们前面看到的组合,我们应该只需要创建五个新的装饰类。被称为对这些增强类的方法将被传递给我们的Macbook课。 在我们的下一个例子,透明装饰环绕它们的组件和它们使用相同的接口,可有趣的互换。 下面是我们要定义为MacBook接口: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 var Macbook = new Interface( "Macbook", ["addEngraving", "addParallels", "add4GBRam", "add8GBRam", "addCase"]); // A Macbook Pro might thus be represented as follows: var MacbookPro = function(){ // implements Macbook }; MacbookPro.prototype = { addEngraving: function(){ }, addParallels: function(){ }, add4GBRam: function(){ }, add8GBRam:function(){ }, addCase: function(){ }, getPrice: function(){ // Base price return 900.00; } }; 为了便于在需要以后我们添加为更多的选择,一个抽象装饰类与实现所需的默认方法定义的Macbook接口,它的其余选项将子类。摘要装饰确保我们能够独立装饰一个基类与尽可能多的装饰,无需派生类为每个可能的组合需要不同的组合(还记得前面的例子吗?)。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 // Macbook decorator abstract decorator class var MacbookDecorator = function( macbook ){ Interface.ensureImplements( macbook, Macbook ); this.macbook = macbook; }; MacbookDecorator.prototype = { addEngraving: function(){ return this.macbook.addEngraving(); }, addParallels: function(){ return this.macbook.addParallels(); }, add4GBRam: function(){ return this.macbook.add4GBRam(); }, add8GBRam:function(){ return this.macbook.add8GBRam(); }, addCase: function(){ return this.macbook.addCase(); }, getPrice: function(){ return this.macbook.getPrice(); } }; 这是怎么回事上述样本中的Macbook装饰接受一个对象(一台Macbook)作为我们的基础组件使用。它使用Macbook我们前面定义的接口,并为每个方法只是调用组件上的方法相同。现在,我们可以创造我们的选项类可以加什么,只是使用的Macbook装饰。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // First, define a way to extend an object a // with the properties in object b. We'll use // this shortly! function extend( a, b ){ for( var key in b ) if( b.hasOwnProperty(key) ) a[key] = b[key]; return a; } var CaseDecorator = function( macbook ){ this.macbook = macbook; }; // Let's now extend (decorate) the CaseDecorator // with a MacbookDecorator extend( CaseDecorator, MacbookDecorator ); CaseDecorator.prototype.addCase = function(){ return this.macbook.addCase() + "Adding case to macbook"; }; CaseDecorator.prototype.getPrice = function(){ return this.macbook.getPrice() + 45.00; }; 我们在这里所做的是覆盖addCase()并getPrice()需要进行装饰的方法和我们首先调用原始这些方法实现这一目标macbook,然后只需追加一个字符串或数值相应(如45.00)给他们。 因为一直颇多至今在本节所介绍的信息,让我们尝试在一个单一的例子,希望能突出我们已经学会了把它放在一起。 ? 1 2 3 4 五 6 7 8 9 10 11 // Instantiation of the macbook var myMacbookPro = new MacbookPro(); // Outputs: 900.00 console.log( myMacbookPro.getPrice() ); // Decorate the macbook var decoratedMacbookPro = new CaseDecorator( myMacbookPro ); // This will return 945.00 console.log( decoratedMacbookPro.getPrice() ); 由于装饰可以动态修改的对象,他们对于改变现有系统的最佳模式。有时,它只是简单的物体周围的维护与各个子类每个对象类型的麻烦创建装饰。这使得可能需要大量的子类对象显著更直截了当的维护应用程序。 这个例子中的一个功能版本上可以找到JSBin。 装饰使用jQuery 如同我们已经讨论的其他型态,也有可与jQuery来实现的装饰图案的例子。jQuery.extend()让我们来扩展(或合并)两个或多个对象(和它们的属性)在一起成为一个单一的对象在运行时。 在这种情况下,目标对象可以与新的功能,而不必破坏或覆盖在源/超类对象的现有方法(尽管这是可以做到)装饰。 在下面的例子中,我们定义了三个对象:默认设置,选项和设置。该任务的目的是装饰defaults在发现附 ​​加功能的对象optionssettings,我们必须: (一)离开“默认”以不变的状态,我们不会失去访问它发现性能或功能的能力后点(B)增益使用装饰性和功能中的“选项”中的能力 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 var decoratorApp = decoratorApp || {}; // define the objects we're going to use decoratorApp = { defaults: { validate: false, limit: 5, name: "foo", welcome: function () { console.log( "welcome!" ); } }, options: { validate: true, name: "bar", helloWorld: function () { console.log( "hello world" ); } }, settings: {}, printObj: function ( obj ) { var arr = [], next; $.each( obj, function ( key, val ) { next = key + ": "; next += $.isPlainObject(val) ? printObj( val ) : val; arr.push( next ); } ); return "{ " + arr.join(", ") + " }"; } }; // merge defaults and options, without modifying defaults explicitly decoratorApp.settings = $.extend({}, decoratorApp.defaults, decoratorApp.options); // what we have done here is decorated defaults in a way that provides // access to the properties and functionality it has to offer (as well as // that of the decorator "options"). defaults itself is left unchanged $("#log") .append( decoratorApp.printObj(decoratorApp.settings) + + decoratorApp.printObj(decoratorApp.options) + + decoratorApp.printObj(decoratorApp.defaults)); // settings -- { validate: true, limit: 5, name: bar, welcome: function (){ console.log( "welcome!" ); }, // helloWorld: function (){ console.log( "hello world" ); } } // options -- { validate: true, name: bar, helloWorld: function (){ console.log( "hello world" ); } } // defaults -- { validate: false, limit: 5, name: foo, welcome: function (){ console.log("welcome!"); } } 优点缺点 开发者喜欢使用这种模式,因为它可以透明地使用,也是相当灵活 - 正如我们看到的,对象可以被包扎或“饰”新的行为,然后继续,而无需担心基本对象要使用被修改。在更广的范围内,这种模式也避免了我们需要依靠大量的子类来获得同样的好处。 然而,有缺点,我们应该认识到实施模式时的感觉。如果管理不善,它可以显著我们的应用程序架构它引入了许多小的,但类似的物体进入我们的空间复杂化。这里关注的是,除了成为难以管理,其他开发商不熟悉的模式可能很难有时间抓为什么它被使用。 足够评论或模式的研究应协助后者,但只要我们保持我们如何广泛使用在我们的应用装饰的句柄,我们应该在这两方面的罚款。 飞锤 该享元模式是优化代码是重复的,缓慢的一个经典的结构解决方案和低效共享数据。它旨在通过与相关对象(如应用程序配置,状态等)共享尽可能多的数据,以尽量减少在应用程序中使用的内存。 该模式最早是由保罗·考尔德和马克·林顿于1990年设想和包括战斗机重量小于112lb拳击重量级的名字命名的。这个名字本身飞锤从这个重量分级衍生,因为它是指小,重量轻(内存占用)模式的目的是帮助我们实现。 在实践中,飞锤数据共享可涉及服用由多个对象的使用的几个类似的对象或数据结构并将该数据放置到单个外部对象。我们可以通过这个对象传递给根据该数据,而不是在每一个存储相同数据的那些。 使用飞铁 有在其中可以应用Flyweight模式两种方式。首先是在数据层,在那里我们处理的大批量存储在存储器中类似的对象之间共享数据的概念。 第二个是在其中飞锤可以用作中央事件管理器,以避免在我们希望有一些类似的行为父容器的事件处理程序安装到每个子元素的DOM的层。 由于数据层就是轻量级模式最常用的传统,我们就来看看这个第一。 飞铁和共享数据 对于这种应用,大约有古典享元模式几个概念,我们需要做到心中有数。在享元模式有两种状态的概念 - 内在和外在。可以通过我们的对象内部方法,他们绝对不能没有功能需要内在的信息。外在信息然而,可取出并存储在外部。 具有相同的固有的数据对象可以与单个共享对象,由一个工厂的方法创建的更换。这使我们可以减少隐式数据的总量被储存相当显著。 这样做的好处是,我们能够保持一个眼睛上已经被实例化,这样,如果内在的状态,从我们已经有了对象不同,新的副本只有永远创建的对象。 我们使用管理器来处理外在状态。这是如何实现的可变化,但一个方法这具有管理对象包含非本征状态和它们所属的轻量级对象的一个​​中央数据库。 实施古典飞铁 由于享元模式还没有被大量使用JavaScript近年来应用,许多我们可能会使用获取灵感的实现来自于Java和C ++的世界。 我们先来看看飞铁的代码是我的JavaScript执行维基百科(享元模式的Java示例的http://en.wikipedia.org/wiki/Flyweight_pattern)。 我们将利用三种类型的在本实施方式中,其中列举如下飞锤的组件组成: 轻量级对应一个接口,通过它飞铁都能够接收和作用于外在状态 混凝土飞锤真正实现飞锤接口和商店内在状态。混凝土飞铁必须共享,并且能够操作状态是外在的 飞锤工厂管理flyweight对象并创建它们。它确保我们的飞铁共享和管理它们作为一组可如果我们要求各个实例进行查询的对象。如果一个对象有它返回它的组已经创建成功了,否则它增加了一个新的对象池并返回。 这些对应于在我们的实现以下定义: CoffeeOrder:飞锤 CoffeeFlavor:混凝土飞锤 CoffeeOrderContext:助手 CoffeeFlavorFactory:飞锤厂 testFlyweight:我们的飞铁的利用 鸭打孔“工具” 鸭冲孔允许我们而不一定需要修改运行源延伸的语言或解决方案的功能。由于这下一个解决方案需要使用一个Java关键字(implements)实现接口,在JavaScript中没有找到本地,让我们先冲鸭它。 Function.prototype.implementsFor 适用于一个对象的构造函数和接受一个父类(功能)或对象,并使用普通的继承(对于函数)或虚拟继承(的对象)从此无论是继承。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 // Simulate pure virtual inheritance/"implement" keyword for JS Function.prototype.implementsFor = function( parentClassOrObject ){ if ( parentClassOrObject.constructor === Function ) { // Normal Inheritance this.prototype = new parentClassOrObject(); this.prototype.constructor = this; this.prototype.parent = parentClassOrObject.prototype; } else { // Pure Virtual Inheritance this.prototype = parentClassOrObject; this.prototype.constructor = this; this.prototype.parent = parentClassOrObject; } return this; }; 我们可以用这个来修补缺少的implements通过具有功能明确继承的接口关键字。下面,CoffeeFlavor实现了CoffeeOrder接口,并且必须包含为了它的接口方法为我们分配这些实现的一个对象供电的功能。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 // Flyweight object var CoffeeOrder = { // Interfaces serveCoffee:function(context){}, getFlavor:function(){} }; // ConcreteFlyweight object that creates ConcreteFlyweight // Implements CoffeeOrder function CoffeeFlavor( newFlavor ){ var flavor = newFlavor; // If an interface has been defined for a feature // implement the feature if( typeof this.getFlavor === "function" ){ this.getFlavor = function() { return flavor; }; } if( typeof this.serveCoffee === "function" ){ this.serveCoffee = function( context ) { console.log("Serving Coffee flavor " + flavor + " to table number " + context.getTable()); }; } } // Implement interface for CoffeeOrder CoffeeFlavor.implementsFor( CoffeeOrder ); // Handle table numbers for a coffee order function CoffeeOrderContext( tableNumber ) { return{ getTable: function() { return tableNumber; } }; } function CoffeeFlavorFactory() { var flavors = {}, length = 0; return { getCoffeeFlavor: function (flavorName) { var flavor = flavors[flavorName]; if (typeof flavor === "undefined") { flavor = new CoffeeFlavor(flavorName); flavors[flavorName] = flavor; length++; } return flavor; }, getTotalCoffeeFlavorsMade: function () { return length; } }; } // Sample usage: // testFlyweight() function testFlyweight(){ // The flavors ordered. var flavors = new CoffeeFlavor(), // The tables for the orders. tables = new CoffeeOrderContext(), // Number of orders made ordersMade = 0, // The CoffeeFlavorFactory instance flavorFactory; function takeOrders( flavorIn, table) { flavors[ordersMade] = flavorFactory.getCoffeeFlavor( flavorIn ); tables[ordersMade++] = new CoffeeOrderContext( table ); } flavorFactory = new CoffeeFlavorFactory(); takeOrders("Cappuccino", 2); takeOrders("Cappuccino", 2); takeOrders("Frappe", 1); takeOrders("Frappe", 1); takeOrders("Xpresso", 1); takeOrders("Frappe", 897); takeOrders("Cappuccino", 97); takeOrders("Cappuccino", 97); takeOrders("Frappe", 3); takeOrders("Xpresso", 3); takeOrders("Cappuccino", 3); takeOrders("Xpresso", 96); takeOrders("Frappe", 552); takeOrders("Cappuccino", 121); takeOrders("Xpresso", 121); for (var i = 0; i < ordersMade; ++i) { flavors[i].serveCoffee(tables[i]); } console.log(" "); console.log("total CoffeeFlavor objects made: " + flavorFactory.getTotalCoffeeFlavorsMade()); } 转换代码来使用享元模式 接下来,让我们继续看看飞铁通过实施一个系统来管理图书馆的所有书籍。最重要的元数据为每本书也许可以细分如下: ID 标题 作者 类型 页数 发行人ID 国际标准书号 我们也将需要以下属性来跟踪哪些成员已经检查了某本书,他们已经检查出来的日期,以及预期回报日期。 checkoutDate checkoutMember dueReturnDate 可用性 每本书将因此而被表示为如下,之前使用享元模式任何优化: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 var Book = function( id, title, author, genre, pageCount,publisherID, ISBN, checkoutDate, checkoutMember, dueReturnDate,availability ){ this.id = id; this.title = title; this.author = author; this.genre = genre; this.pageCount = pageCount; this.publisherID = publisherID; this.ISBN = ISBN; this.checkoutDate = checkoutDate; this.checkoutMember = checkoutMember; this.dueReturnDate = dueReturnDate; this.availability = availability; }; Book.prototype = { getTitle: function () { return this.title; }, getAuthor: function () { return this.author; }, getISBN: function (){ return this.ISBN; }, // For brevity, other getters are not shown updateCheckoutStatus: function( bookID, newStatus, checkoutDate, checkoutMember, newReturnDate ){ this.id = bookID; this.availability = newStatus; this.checkoutDate = checkoutDate; this.checkoutMember = checkoutMember; this.dueReturnDate = newReturnDate; }, extendCheckoutPeriod: function( bookID, newReturnDate ){ this.id = bookID; this.dueReturnDate = newReturnDate; }, isPastDue: function(bookID){ var currentDate = new Date(); return currentDate.getTime() > Date.parse( this.dueReturnDate ); } }; 这可能正常工作最初是为书小型集合,然而由于库扩展到包括多个版本,并提供每本书的副本较大的库存,我们可能会发现管理体系运行一段时间慢。使用上千本书的对象可能会压倒可用的内存,但我们可以用享元模式,以改善该优化我们的系统。 现在,我们可以如下分离我们的数据转换成内在的和外在的状态:相关的书对象(数据title,author等)是本征而检出数据(checkoutMember,dueReturnDate等)被认为是外源性。有效地,这意味着只有一个预定对象需要书属性的每个组合。它仍然是对象的数量可观,但比我们以前有显著减少。 我们的书的元数据的组合的以下单实例将在所有的一本书的与特定标题的拷贝共享。 ? 1 2 3 4 五 6 7 8 9 10 11 // Flyweight optimized version var Book = function ( title, author, genre, pageCount, publisherID, ISBN ) { this.title = title; this.author = author; this.genre = genre; this.pageCount = pageCount; this.publisherID = publisherID; this.ISBN = ISBN; }; 正如我们所看到的,外在的状态已被删除。一切都与库退房将被移动到一个经理和对象数据现在是分段的,一个工厂,可用于实例化。 一个基本厂 现在让我们来定义一个非常基本的工厂。我们打​​算把它做的是执行检查,看是否与特定的标题一书曾在系统内以前创建的; 如果它有,我们将返回它 - 如果不是,一本新书将被创建并存储,以便它可以在以后访问。这将确保我们只创造每一个独特的内在一块数据的一个副本: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // Book Factory singleton var BookFactory = (function () { var existingBooks = {}, existingBook; return { createBook: function ( title, author, genre, pageCount, publisherID, ISBN ) { // Find out if a particular book meta-data combination has been created before // !! or (bang bang) forces a boolean to be returned existingBook = existingBooks[ISBN]; if ( !!existingBook ) { return existingBook; } else { // if not, let's create a new instance of the book and store it var book = new Book( title, author, genre, pageCount, publisherID, ISBN ); existingBooks[ISBN] = book; return book; } } }; })(); 管理外在状态 接下来,我们需要存储的地方,从书的对象中删除的状态 - 幸运的是经理(我们会被定义为一个Singleton)可以用于封装它们。一个Book对象而这检查了他们的库成员的组合将被称为图书记录。我们的经理会被存储和双方还将包括我们为Book类的轻量级优化过程中剥离出来结账相关的逻辑。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 三十 31 32 33 34 35 36 37 38 39 // BookRecordManager singleton var BookRecordManager = (function () { var bookRecordDatabase = {}; return { // add a new book into the library system addBookRecord: function ( id, title, author, genre, pageCount, publisherID, ISBN, checkoutDate, checkoutMember, dueReturnDate, availability ) { var book = bookFactory.createBook( title, author, genre, pageCount, publisherID, ISBN ); bookRecordDatabase[id] = { checkoutMember: checkoutMember, checkoutDate: checkoutDate, dueReturnDate: dueReturnDate, availability: availability, book: book }; }, updateCheckoutStatus: function ( bookID, newStatus, checkoutDate, checkoutMember, newReturnDate ) { var record = bookRecordDatabase[bookID]; record.availability = newStatus; record.checkoutDate = checkoutDate; record.checkoutMember = checkoutMember; record.dueReturnDate = newReturnDate; }, extendCheckoutPeriod: function ( bookID, newReturnDate ) { bookRecordDatabase[bookID].dueReturnDate = newReturnDate; }, isPastDue: function ( bookID ) { var currentDate = new Date(); return currentDate.getTime() > Date.parse( bookRecordDatabase[bookID].dueReturnDate ); } }; })(); 这些变化的结果是,所有这就是被从书中提取的数据的类,现在被存储在BookManager的单(BookDatabase)的属性-这相当比大量我们先前使用的对象的更有效。预订检出相关的方法现在也设在这里,因为他们对付那外在而非内在数据。 这个过程不加少许复杂我们的最终解决方案,但是它的时相比,已解决了性能问题的小问题。数据明智的,如果我们有同样的书30份,我们现在只保存一次。此外,每个函数占用的内存。随着轻量级模式这些功能在一个地方(在经理),而不是每个对象上不存在,因此节省了内存使用。对于我们存储上述轻量级版本未优化只是链接到,因为我们使用的图书构造函数的原型函数对象,但如果它是在其他的方式实现,将为每本书实例创建功能。 该享元模式和DOM DOM(文档对象模型)支持,使物体检测事件两种方法 - 无论是自上而下(捕获事件)还是自下而上(事件冒泡)。 在事件捕获,事件首先被最外层元件捕获并传播到最内的元素。在事件冒泡,事件被捕获并提供给最内元件,然后传播到外元件。 一个用于描述在这种情况下飞铁的最好隐喻的被写了加里·奇泽姆和它会有点像这样: 尝试在一个池塘的角度考虑的轻量级的。鱼打开它的嘴(事件),气泡上升到表面(鼓泡)苍蝇位于顶飞开当气泡到达表面(动作)。在这个例子中,我们可以很容易地调换鱼开其口,以一个按钮被点击,气泡冒泡效应和苍蝇飞走了一些功能正在运行 鼓泡被介绍给处理情况,即单一事件(如点击)可以通过在不同级别的DOM层次结构定义了多个事件处理程序进行处理的情况。其中,这种情况下,事件冒泡执行用于在可能的最低程度的特定元素定义的事件处理程序。从那里,事件冒泡之前去那些更高了包含元素。 飞铁可以用于进一步调整的情况下鼓泡过程中,我们将会看到不久。 例1:集中事件处理 对于我们的第一个实际的例子,假设我们拥有一批在文档中相似的元素同当用户操作(例如点击,鼠标悬停)是对他们进行执行类似的行为。 通常情况下我们做什么构建我们自己的手风琴组成部分,菜单或其他基于列表的控件时,绑定一个click事件在父容器(每个链接元素如$('ul li a').on(..)与其结合的点击多个元素,我们可以很容易地附加一个飞锤来我们的容器,其可以监听从下面来的事件的顶部,这些然后可以使用逻辑根据需要是简单或复杂的处理。 的各类提到组分通常具有每个部分相同的重复标记(例如手风琴的每个部分),有一个很好的机会,可以点击将是非常相似并且相对于类似的类附近的各元素的行为。我们将利用这些信息来建立基于以下飞锤一个非常基本的手风琴。 StateManager的一个命名空间在这里用来封装我们的轻量级逻辑,而jQuery是用来绑定初始点击一个容器div。为了确保在页面上没有其他的逻辑被附着类似手柄到容器中,首先施加一个解除绑定事件。 现在建立正好在容器中的子元素被点击了什么,我们利用一种的target支票提供单击的,不管它的父的元素的引用。然后,我们使用这些信息而实际上根本没到事件发生到具体的孩子我们的页面加载绑定来处理单击事件。 HTML ? 1 2 3 4 五 6 7 8 9 10 11
    More Info (Address) This is more information
    Even More Info (Map)