企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
学习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 <button id="addNewObserver">Add New Observer checkbox</button> <input id="mainCheckbox" type="checkbox"/> <div id="observersContainer"></div> 示例脚本: ? 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 <script id="userTemplate" type="text/html"> <li><%= name="" %=""></li> </script> <script id="ratingsTemplate" type="text/html"> <li><strong><%= %=""></strong> was rated <%= rating="" %="">/5</li> </script> <div id="container"> <div class="sampleForm"> <p> <label for="twitter_handle">Twitter handle:</label> <input type="text" id="twitter_handle" /> </p> <p> <label for="movie_seen">Name a movie you've seen this year:</label> <input type="text" id="movie_seen" /> </p> <p> <label for="movie_rating">Rate the movie you saw:</label> <select id="movie_rating"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> <option value="4">4</option> <option value="5" selected>5</option> </select> </p> <p> <button id="add">Submit rating</button> </p> </div> <div class="summaryTable"> <div id="users"><h3>Recent users</h3></div> <div id="ratings"><h3>Recent movies rated</h3></div> </div> </div> <!--%=--><!--%=--><!--%=--> 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 <form id="flickrSearch"> <input type="text" name="tag" id="query"/> <input type="submit" name="submit" value="submit"/> </form> <div id="lastQuery"></div> <ol id="searchResults"></ol> <script id="resultTemplate" type="text/html"> <% _.each(items,="" function(="" item="" ){="" %=""> <li><img src="<%= item.media.m="" %="">"/></li> <% });%=""> </script> <!--%--><!--%=--><!--%--> 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("<p>Searched for:<strong>" + tags + "</strong></p>"); }); // 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 <div id="container"> <div class="toggle" href="#">More Info (Address) <span class="info"> This is more information </span></div> <div class="toggle" href="#">Even More Info (Map) <span class="info"> <iframe src="http://www.map-generator.net/extmap.php?name=London&amp;address=london%2C%20england&amp;width=500...gt;"</iframe> </span> </div> </div> JavaScript的 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 var stateManager = { fly: function () { var self = this; $( "#container" ) .unbind() .on( "click", "div.toggle", function ( e ) { self.handleClick( e.target ); }); }, handleClick: function ( elem ) { elem.find( "span" ).toggle( "slow" ); } }; 这里的好处是,我们转换许多独立行动统一到一个共享的人(可能节省内存)。 例2:使用飞锤的性能优化 在我们的第二个例子中,我们引用可以使用飞铁与jQuery实现了进一步的性能提升。 詹姆斯Padolsey以前写过一篇文章,呼吁76个字节更快jQuery的,他提醒我们,每次的jQuery触发了一个回调,而不管类型(过滤器,每一个,事件处理程序)的,我们能够访问函数的上下文(DOM元素通过与之相关的)this的关键字。 不幸的是,我们许多已成为用于包装的想法this中$()或jQuery(),这意味着jQuery的的新实例被不必要地建造的每时间,而不是简单地做这样的: ? 1 2 3 4 五 6 7 8 9 10 11 $("div").on( "click", function () { console.log( "You clicked: " + $( this ).attr( "id" )); }); // we should avoid using the DOM element to create a // jQuery object (with the overhead that comes with it) // and just use the DOM element itself like this: $( "div" ).on( "click", function () { console.log( "You clicked:" + this.id ); }); 詹姆斯本来想用jQuery的jQuery.text在以下范围内,但他的观点不同意一个新的jQuery对象必须在每次迭代创建: ? 1 2 3 $( "a" ).map( function () { return $( this ).text(); }); 现在,对于多余的包装,尽可能使用jQuery的实用方法,这是更好地使用jQuery.methodName(例如jQuery.text),而不是jQuery.fn.methodName(例如jQuery.fn.text)其中MethodName代表一个实用工具,如each()或text。这避免了需要调用抽象进一步级别或每个我们的功能被称为随着时间的构造一个新的jQuery对象jQuery.methodName是什么库本身使用在较低的水平,以功率jQuery.fn.methodName。 由于然而jQuery的方法并不是所有的都有相应的单节点功能,Padolsey制定了jQuery.single实用的想法。 这里的想法是,一​​个单一的jQuery对象被创建并用于每个调用jQuery.single(有效意思只有一个jQuery对象被创建)。对于此实施可以在下面找到并作为我们合并为多个可能的对象数据转换成一个多中心的单一结构,它在技术上是还一个飞锤。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 jQuery.single = (function( o ){ var collection = jQuery([1]); return function( element ) { // Give collection the element: collection[0] = element; // Return the collection: return collection; }; })(); 在与链接动作的这样的一个例子是: ? 1 2 3 4 五 6 $( "div" ).on( "click", function () { var html = jQuery.single( this ).next().html(); console.log( html ); }); 注意:虽然我们可以相信,只是缓存我们的jQuery代码可能只提供等同的性能提升,Padolsey声称$。单()仍然是值得使用,可以更好的表现。这并不是说在所有不适用任何缓存,仅仅是意识到,这种方法可以帮助。有关$。单的详细信息,我建议你阅读Padolsey的全部职务。 JavaScript的MV *模式 在本节中,我们将审查三个非常重要的架构模式 - MVC(模型 - 视图 - 控制器),MVP(模型 - 视图 - 演示)和MVVM(模型 - 视图 - 视图模型)。在过去,这些模式已经被大量用于构建桌面和服务器端应用程序,但它只有在最近几年,来应用到的JavaScript。 由于大多数目前使用这些模式,JavaScript开发人员选择利用库如Backbone.js的用于实现MVC / MV *式的结构,我们将比较解决方案如何现代化,如在他们的MVC的解释有所不同相比,经典呈现这些模式。 首先让我们现在覆盖的基础知识。 MVC MVC是通过分离关注鼓励提高应用程序的组织架构的设计模式。它强制从用户界面(查看)业务数据(模型)的分离,与第三成分(控制器)传统管理逻辑和用户输入。该模式由最初的设计特里夫Reenskaug在他的时间在它最初被称为模型-视图-控制器-编辑器的Smalltalk-80(1979年)的工作。MVC继续向纵深于1995年的描述“设计模式:可复用面向对象软件的元素”(在“四人帮”一书),在推广它的使用发挥了作用。 的Smalltalk-80 MVC 了解一下原来的MVC模式正致力于解决,因为它是因为它的起源的日子突变相当严重是很重要的。早在上世纪70年代,图形用户界面是少之又少并称为概念分居演示开始被用作手段,使该模型在现实世界中的概念域对象之间有明确的分工(如照片,一个人),并且被呈现给用户的屏幕的展现对象。 Smalltalk的-80 MVC实现进一步采取这一概念,并有从用户界面分离出应用程序逻辑的一个目标。该想法是去耦应用程序的这些部分也将允许的模型中应用的其他接口的重用。也有一些值得注意的Smalltalk左右-80的MVC架构有意思的观点: 模型代表特定于域的数据,是无知的用户界面(视图和控制器)的。当模型发生改变,则通知其观察员。 一个视图中表示模型的当前状态。用于让视图知道每当型号进行了更新或修改的观察者模式。 演讲被查看的照顾,但不只是一个单一的视图和控制器 - 需要有一个视图 - 控制器对每个部分或屏幕上显示的元素。 在这双控制器的作用是处理用户交互(如按键事件和行动,例如点击),为View决策。 当他们得知观察者模式(一般时下的发布/订阅的变化实现)被列为MVC架构的一部分,几十年前开发者有时会感到惊讶。在Smalltalk-80的MVC中,视图观察模型。作为子弹点如上所述,任何时候,模型的变化,视图作出反应。这方面的一个简单的例子是股票市场数据备份的应用程序 - 应用程序是有用的,在我们的模型的任何更改数据应该导致查看被瞬间刷新秩序。 Martin Fowler的做了写关于一个出色的工作起源 MVC多年来,如果感兴趣的Smalltalk左右-80的MVC一些进一步的历史信息,我建议你阅读他的作品。 MVC对于JavaScript开发者 我们已经回顾了70年代,但现在让我们回到这里和现在。到了近代,MVC模式已经被应用到各种不同的编程语言,包括最相关给我们的:JavaScript的。JavaScript的目前已拥有一批拥有的框架MVC的支持(或变化就可以了,这是我们称之为MV *系列),允许开发者轻松地添加结构,他们的应用程序不花大力气的。 这些框架包括骨干网,Ember.js和AngularJS的喜欢。考虑到由于缺乏结构避免“意大利面条”的代码,它描述的代码是非常难以阅读或维持长期的重要性,它的当务之急是现代JavaScript开发明白这是什么模式提供。这使我们能够有效地欣赏一下这些框架使我们能够做不同。 我们知道,MVC由三个核心部件: 楷模 模型管理数据的应用程序。他们关心既不用户接口也不表示层,而是代表一个应用程序可能需要的数据的唯一形式。当一个模型更改(例如,当它被更新),它通常会通知已发生的变化及其观察员(如意见,我们会尽快覆盖概念),使他们可以做出相应的反应。 为了进一步了解车型,让我们想象,我们有一个JavaScript照片库应用程序。在照片库,因为它代表了一种独特的特定于域的数据的照片的概念将值得自己的模式。这样的模型可含有相关的属性,如标题,图像源和附加元数据。一个具体的照片将被保存在一个模型的实例和模型也可能是可重复使用的。下面,我们可以看到使用骨干实现一个非常简单的模型的一个例子。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 var Photo = Backbone.Model.extend({ // Default attributes for the photo defaults: { src: "placeholder.jpg", caption: "A default image", viewed: false }, // Ensure that each photo created has an `src`. initialize: function() { this.set( { "src": this.defaults.src} ); } }); 模型的内置功能横跨框架而变化,但它是相当常见的他们支持的属性,其中属性表示所述模型的属性的验证,如一个型号标识符。当在现实世界的应用程序中使用的模型,我们一般也希望模型的持久性。持久性使我们能够编辑和更新机型,其最近的状态将被保存在任一知识:内存,在用户的localStorage的数据存储或与数据库同步。 此外,模型还可以具有多个视图观察它。如果说,我们的相片模型包含的元数据,例如其位置(经度和纬度),即存在于照片(标识符的列表)和标签列表的朋友,开发者可以决定提供单一视图来显示这三个方面的。 它并非罕见现代的MVC / MV *框架向组模型提供一个装置一起(例如在骨干,这些基团被称为“集合”)。在群体管理模式使我们能够编写基于从小组通知应用程序逻辑应该包含任何模式进行更改。这就避免了需要手动观察单个模型实例。 模型的样本组合成一个简化的骨干集合可以看到下面。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var PhotoGallery = Backbone.Collection.extend({ // Reference to this collection's model. model: Photo, // Filter down the list of all photos // that have been viewed viewed: function() { return this.filter(function( photo ){ return photo.get( "viewed" ); }); }, // Filter down the list to only photos that // have not yet been viewed unviewed: function() { return this.without.apply( this, this.viewed() ); } }); MVC的年长文本还可能包含参考模型的管理应用概念的状态。在JavaScript应用程序状态都有不同的内涵,通常指的是当前的“状态”,即在用户视图或子视图(具体数据)屏一个固定的点。国家是在看单页应用中,需要模拟国家的概念时,这是定期讨论的一个话题。 总结一下,车型主要关注业务数据。 意见 视图是那可凭当前状态的过滤视图模型的可视化表示。虽然Smalltalk的意见是关于绘画和维护一个位图,JavaScript的意见是关于建立和维护一个DOM元素。 视图通常观察的模型,当模式的转变,使以相应地更新本身通知。设计模式文献通常指的意见,因为他们在应用程序的模型和控制器的知识是有限的“哑巴”。 用户可以与视图进行交互,这包括阅读和编辑的能力(即获取或设置属性值)的模型。由于视图是表示层,我们一般呈现给编辑以用户友好的方式的能力和更新。例如,在我们前面讨论过的原照片库应用程序,模型编辑可以通过“编辑”视图,谁选择了一个特定的照片,用户可以编辑其元数据来促进。 更新模型的实际任务落在控制器(我们将很快覆盖)。 让我们来探讨意见远一点使用香草的JavaScript样本实现。下面我们可以看到,创建一个单一的图片浏览,既消耗模型实例和一个控制器实例的功能。 我们定义了一个render()我们认为这是负责呈现的内容,程序内photoModel使用JavaScript模板引擎(下划线模板)和更新我们的观点,通过引用的内容photoEl。 在photoModel随后增加了我们的render()回调为它的用户之一,使得通过Observer模式,我们都可以触发模型的变化更新视图。 人们可能不知道在哪里的用户交互来这里发挥作用。当用户在视图中的任何元素的点击,这不是视图的责任,知道下一步该怎么做。它依靠在控制器上做出这个决定吧。在我们的示例实现,这是通过添加事件侦听器来实现photoEl,这将处理委托的点击行为反馈给控制器,通过模型信息与它一起的情况下,它的需要。 这种结构的好处是,各成分起着根据需要使应用功能自己单独的作用。 ? 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 var buildPhotoView = function ( photoModel, photoController ) { var base = document.createElement( "div" ), photoEl = document.createElement( "div" ); base.appendChild(photoEl); var render = function () { // We use a templating library such as Underscore // templating which generates the HTML for our // photo entry photoEl.innerHTML = _.template( "#photoTemplate", { src: photoModel.getSrc() }); }; photoModel.addSubscriber( render ); photoEl.addEventListener( "click", function () { photoController.handleEvent( "click", photoModel ); }); var show = function () { photoEl.style.display = ""; }; var hide = function () { photoEl.style.display = "none"; }; return { showView: show, hideView: hide }; }; 模板 在JavaScript框架,支持MVC / MV *的背景下,这是值得讨论的简单的JavaScript模板及其关系的意见,我们简要地在最后一节在它感动。 长期以来,人们一直认为(和证明)性能不好的做法,通过手动字符串连接创建内存HTML标记大块。开发商这样做也曾被inperformantly通过他们的数据迭代,嵌套的div包裹,并使用这些过时的技术document.write注入了“模板”到DOM。因为这通常意味着保持与我们的标准标记脚本标记行内,它可以迅速变得既难以阅读和更重要的是,保持这样的灾害,构建非平凡尺寸应用时尤其如此。 JavaScript的模板解决方案(如Handlebars.js和胡子)经常被用来定义模板的意见为标记(无论是外部存储或使用自定义类型的脚本标记内 - 如文/模板)包含模板变量。变量可以使用变量语法delimitated(例如{{名}})和框架通常是足够聪明,接受一个JSON形式的数据(模型实例可以转换成),使得我们只需要维持清洁的车型关心,干净的模板。最繁重的工作做的人口是由框架本身的照顾。这有大量的好处,选择加入存储模板外,因为这可以让位给模板被动态加载在需要的基础上,当谈到构建大型应用时尤为如此。 下面我们可以看到HTML模板两个例子。一个实现使用流行的Handlebars.js框架,另一个使用下划线的模板。 Handlebars.js: ? 1 2 3 4 五 6 7 <li class="photo"> <h2>{{caption}}</h2> <img class="source" src="{{src}}"/> <div class="meta-data"> {{metadata}} </div> </li> Underscore.js Microtemplates: ? 1 2 3 4 五 6 7 <li class="photo"> <h2><%= caption %></h2> <img class="source" src="<%= src %>"/> <div class="meta-data"> <%= metadata %> </div> </li> 请注意,模板本身没有意见。开发人员一个Struts Model 2架构未来可能会觉得自己像一个模板* *是一个观点,但事实并非如此。视图是一个对象,观察模型,并保持视觉表现了最新的。模板*可能*是指定部分甚至全部视图对象的,以便它可以从模板规范生成的声明性方式。 还值得一提的是,在传统的Web开发,独立的见解之间的导航需要使用页面刷新。在单页JavaScript应用程序,但是,一旦数据是通过Ajax的服务器获取,它可以简单地动态呈现在同一页面中一个新的观点,没有任何这样的刷新是必​​要的。 导航的作用因而下降到一个“路由器”,这有助于管理应用程序的状态(例如允许用户书签的特定视图,他们已经导航到)。由于路由器,然而,既不是MVC的一部分,也不存在于每一个MVC的框架一样,我不会在本节进入他们的更多细节。 总结,观点是我们的应用程序的数据的可视化表示。 控制器 控制器模型和视图这是经典的负责当用户操纵视图更新模型之间的中介。 在我们的照片库应用程序,处理切换到编辑视图作出特定照片的用户,更新当用户完成编辑一个特定的照片模式控制器将负责。 请记住,控制器履行MVC一个角色:视图的策略模式的便利。在战略模式方面,鉴于与会代表在视图的自由裁量权的控制。所以,这就是策略模式是如何工作的。该视图可以委托处理用户事件到控制器的观点认为合适的时候。视图*可能*委托处理模式改变事件给控制器如果视图认为合适,但这并不是控制器的传统角色。 在大多数的JavaScript MVC框架,从一个通常被认为有损术语“MVC”,然而,它与控制器。造成这种情况的原因各不相同,但在我的真实想法,它是框架作者最初看MVC的服务器端解释,认识到它不翻译1:在客户端1和重新解释在C MVC意味着他们觉得东西更有意义。然而与此的问题是,它是主观的,在这两个理解古典MVC模式,当然控制器在现代框架的作用增加了复杂性。 作为一个例子,让我们简要回顾一下流行的架构框架Backbone.js的架构。主干包含模型和视图(有点类似于我们之前审查),但实际上它并不具有真正的控制器。其次和路由器充当稍类似的控制器,但既不是实际上自己的控制器。 在这方面,相反的是可能会在正式文件或博客文章提到,骨干既不是真正的MVC / MVP也不MVVM框架。这其实不如考虑它的MV *家族的一员而以自己的方式方法架构。这里当然是没有错的,但经典的MVC和MV *区别开来,我们应该开始依靠从咨询古典文学对前者帮助后者是很重要的。 在另一个库(Spine.js)与Backbone.js的控制器 Spine.js 我们现在知道,控制器是当用户更新视图更新模型传统上负责。这是有趣的是,在写(骨干)的当时最流行 ​​的JavaScript的MVC / MV *框架不会有它自己的控制器明确的概念。 因而它可以是有用的为我们从另一个MVC框架审查控制器欣​​赏在实施方式的差异,并进一步说明框架如何nontraditionally接近控制器的作用。对于这一点,让我们来看看从Spine.js样本控制器: 在这个例子中,我们将有一个叫做控制器PhotosController将负责在申请个人照片。这将确保在视图更新(例如,一个用户编辑照片的元数据)相应的模型也会做。 注意:我们不会在很大程度上钻研Spine.js可言,而只是采取什么样的控制器可以做十英尺的看法: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // Controllers in Spine are created by inheriting from Spine.Controller var PhotosController = Spine.Controller.sub({ init: function () { this.item.bind( "update", this.proxy( this.render )); this.item.bind( "destroy", this.proxy( this.remove )); }, render: function () { // Handle templating this.replace( $( "#photoTemplate" ).tmpl( this.item ) ); return this; }, remove: function () { this.el.remove(); this.release(); } }); 在脊柱,控制器被认为是一个应用程序的胶水,增加和响应DOM事件,呈现模板,并确保视图和模型保持同步(这是有道理的在我们所知道的是一个控制器的上下文中)。 我们正在做在上面的例子是建立在听众update和destroy使用事件render()和remove()。如果照片的输入被更新,我们重新渲染视图以反映更改元数据。同样,如果照片被从库中删除,我们从视图中删除它。在render()函数中,我们使用下划线微模板(通过_.template()),以使与ID #photoTemplate一个JavaScript模板。这个简单的返回用于填充的内容的编译HTML字符串photoEl。 这是什么为我们提供的是管理模型和视图之间的变化非常轻巧,简单的方法。 Backbone.js的 后来在这一节,我们要重新审视骨干网和传统的MVC的区别,但现在让我们专注于控制器。 在骨干网,人们共享一个控制器同时与责任Backbone.View和Backbone.Router。前段时间骨干也曾经带了它自己的Backbone.Controller,但作为命名此组件没有意义在其中正在使用它的背景下,它后来被更名为路由器。 路由器处理更多一点控制器的责任,因为它是可能有绑定事件模型,并有我们的看法,以DOM事件和渲染响应。蒂姆Branyen(另一个基于Bocoup骨干贡献者)此前还曾指出,这可能与不需要脱身Backbone.Router在所有的这一点,所以某种程度上使用路由器范式来想想大概是: ? 1 2 3 4 五 6 7 8 9 10 var PhotoRouter = Backbone.Router.extend({ routes: { "photos/:id": "route" }, route: function( id ) { var item = photoCollection.get( id ); var view = new PhotoView( { model: item } ); $('.content').html( view.render().el ); } }); 总之,从本节的重点是:控制器管理应用模型和视图之间的逻辑和协调。 什么是MVC给我们? 在MVC关注这种分离方便了应用程序的功能,简单的模块化和可实现: 整体更容易维护。当需要更新的应用程序来进行是非常清楚的变化是否以数据为中心,这意味着修改模型和可能的控制器,或者仅仅是视觉,这意味着修改意见。 解耦模型和视图意味着它是显著更直截了当写业务逻辑单元测试 低层次的模型和控制器代码重复(即,我们可能已被使用,而不是什么),横跨应用程序淘汰 取决于应用和角色的分离的大小,这个模块化允许负责核心逻辑开发者和开发者的用户接口的工作,以同时工作 的Smalltalk-80 MVC在JavaScript 尽管大多数现代JavaScript框架的尝试演变MVC范例,以更好地适应网络应用开发的不同需求,有一个框架,它试图坚持在Smalltalk-80发现模式的纯粹的形式。Maria.js(https://github.com/petermichaux/maria)由彼得·米肖提供其忠实于MVCS起源的实现-模型是模型,视图是视图和控制器却都控制器。虽然一些开发商可能会觉得一个MV *框架应解决更多的关注,这是一个有益的参考,以做到心中有数,如果你想一个JavaScript实现原MVC的。 更深层次的钻研 在书中这一点上,我们应该有什么样的MVC模式提供了一个基本的了解,但还是有它值得一提的一些有趣的信息。 GoF的不指MVC作为一种设计模式,而是认为这是一个组类来构建用户界面。在他们看来,这实际上是三种经典设计模式的变化:观察,策略和复合模式。取决于如何MVC已经在一个框架中实现,它也可以使用工厂与模板图案。与MVC时GoF的书中提到这些图案作为有用的附加 ​​功能。 正如我们所讨论的,模型代表的意见,而应用程序数据是用户呈现在屏幕上的内容。因此,MVC依赖于(一些令人惊讶的是不包括在对MVC模式的许多文章)一些核心通信的观察者模式。当模型改变了它通知其观察的东西已被更新(查看) - 这也许是MVC中最重要的关系。这种关系的观察者性质也是什么便于多个视图被连接到相同的模型。 对于有兴趣了解更多有关MVC的解耦性质(再次,取决于实现)的开发,格局的目标之一是帮助确定一个主题(数据对象)及其观察员之间的一个一对多的关系。当一个主题的变化,及其观察员更新。视图和控制器有一个稍微不同的关系。控制器促进次向不同的用户输入作出响应,并且策略模式的一个例子。 概要 在审查了经典的MVC模式,我们现在应该明白它是如何使我们能够清晰地在应用程序中分离关注。我们也应该明白现在怎么的JavaScript MVC框架可以在其MVC模式,它虽然比较开放的变化,还是全体的基本概念,原有格局所提供的解释不同。 在审查一个新的JavaScript MVC / MV *框架,记住 - 这可能是有用的退后一步,回顾它是如何选择进场架构(具体而言,它如何支持实现模型,视图,控制器或其他替代品),因为这样可以更好地帮助我们神交框架期望如何被使用。 MVP 模型-视图-演示者(MVP)是专注于提高表现逻辑MVC设计模式的一种衍生物。它起源于一家名为Taligent公司在90年代初,当他们上了C ++ CommonPoint环境的模式工作。虽然这两个MVC和MVP的目标跨多个组件关注点分离,它们之间存在着一些根本性的分歧。 对于本摘要的目的,我们将着重版本MVP的最适合的基于Web的架构。 模型,视图与主持人 在MVP的P代表主持人。这其中包含该视图的用户界面的业务逻辑的组件。与MVC,从视图中调用都被委托给主持人,这是从视图中分离,并通过一个接口,而不是谈论它。这使得各种有用的东西,如能嘲笑意见单元测试。 MVP的最常见的实现是其中之一采用了被动视图,包含几乎没有任何逻辑(这是所有意图和目的“哑”视图)。如果MVC和MVP是不同的这是因为C和P做不同的事情。在MVP,在P模式时改变观察模型和更新的意见。在P有效结合车型的看法,这是以前在MVC控制器召开的责任。 由视图请求,主持人执行任何工作,做用户请求和数据传递回给他们。在这方面,他们中检索数据,处理它,并确定如何将数据应在视图中显示。在一些实现中,演示者还与一个服务层坚持数据(模型)相互作用。模型可能触发事件,但它的作用主持人订阅他们,以便它可以更新视图。在这种被动的架构,我们没有直接的数据绑定的概念。查看公开哪些演示者可以用它来设置数据的setter方法​​。 从MVC这种变化的好处是,它增加了我们的应用程序的可测试性,并提供视图和模型之间更清晰的分离。这不是没有但其成本缺乏数据的模式绑定的支持往往意味着公司将不得不单独照顾这个任务。 虽然一个共同实施被动视图是实现接口的观点,也有它的变化,包括使用可消除来自演示者视图多一点的事件。由于我们没有在JavaScript中的界面结构,我们使用超过这里显式接口的协议。这是在技术上还是一个API,它可能是公平的,我们把它称为从这个角度的接口。 也有一个监督控制器 MVP,这是更接近MVC和变化MVVM模式,因为它提供数据绑定从模型直接从观。键-值观察(志愿)插件(如德里克贝利的Backbone.ModelBinding插件)往往会带来骨干摆脱被动的查看多地进入监督控制器或MVVM变化。 MVP或MVC? MVP一般最常使用的企业级应用中,有必要重新使用尽可能多的表示逻辑尽可能。用非常复杂的观点和用户交互大量的应用程序可能会发现MVC并不完全在这里适合该法案为解决这个问题,可能意味着严重依赖于多个控制器。在MVP,这一切复杂的逻辑可以在演示,从而可以大大简化维护进行封装。 作为MVP的意见通过接口定义和接口在技术上是系统和视图(除主持人等)之间的接触止点,这种模式还允许开发人员编写的表示逻辑,而无需等待的设计师,生产布局和图形应用程序。 根据实现,MVP可能更容易比MVC自动单元测试。经常引述这样做的原因是,演示者可以用作用户接口的一个完整的模拟,因此可以进行单元测试独立于其他组件。在我的经验,这实际上取决于我们在执行MVP语言(有选择了最有价值球员为一个JavaScript项目在一个比方说,ASP.net中有很大差异)。 在一天结束时,底层的顾虑我们可具有MVC定它们之间的差异主要是语义MVP的将有可能成立。只要我们清晰地分离问题纳入模型,视图和控制器(或演示)我们应该实现大多数同样的好处,无论我们选择的变化。 MVC,MVP和Backbone.js的 有极少数,如果声称实现他们的古典形式尽可能多的JavaScript开发人员并不认为MVC和MVP作为是相互排斥的MVC或MVP模式的架构JavaScript框架(我们其实更有可能看到MVP严格实施时看着Web框架,如ASP.net或GWT)。这是因为它可能在我们的应用程序的其他主持人/视图逻辑,但仍然认为这是MVC的味道。 骨干贡献者艾琳罗斯(总部位于波士顿的Bocoup)的赞同思想为这种方式时,她分离出来的意见进入自己不同的组件,她需要一些实际组装它们给她。这可能是某个控制器途径(如Backbone.Router,在本书后面介绍)或回调响应数据被取出。 话虽如此,但有些开发商确实感觉到Backbone.js的更适应MVP比它的MVC的描述。他们的观点是: 在MVP更好的演示介绍了Backbone.View比控制器执行(查看模板之间,并绑定到它的数据层) 该模型拟合Backbone.Model(它不是在MVC模型有很大不同的话) 意见最能代表模板(如把手/胡子标记模板) 为了对此作出回应可能是该观点也可以只是一个视图(按照MVC),因为骨干具有足够的灵活性让它可以用于多种用途。MVC中的V和在MVP在P可以通过两者来完成Backbone.View,因为他们能达到两个目的:既渲染原子弹部件,组装其他意见呈现这些组件。 我们还可以看出,在骨干控制器的责任与两个Backbone.View和Backbone.Router共享,并在下面的例子中我们实际上可以看到,各方面都确实存在。 我们的骨干PhotoView使用Observer模式为“预订”变为查看在该行的模型this.model.bind("change",...)。它还处理模板化的render()方法,但不同于某些其它实现中,用户交互的视图(见还处理events)。 ? 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 var PhotoView = Backbone.View.extend({ //... is a list tag. tagName: "li", // Pass the contents of the photo template through a templating // function, cache it for a single photo template: _.template( $("#photo-template").html() ), // The DOM events specific to an item. events: { "click img": "toggleViewed" }, // The PhotoView listens for changes to // its model, re-rendering. Since there's // a one-to-one correspondence between a // **Photo** and a **PhotoView** in this // app, we set a direct reference on the model for convenience. initialize: function() { this.model.on( "change", this.render, this ); this.model.on( "destroy", this.remove, this ); }, // Re-render the photo entry render: function() { $( this.el ).html( this.template(this.model.toJSON() )); return this; }, // Toggle the `"viewed"` state of the model. toggleViewed: function() { this.model.viewed(); } }); 另一个(完全不同)的意见是,骨干更加接近的Smalltalk-80 MVC,我们通过前面去了。 定期骨干博客德里克-贝利此前所说的那样,这是最终最好不要强迫骨干,以适应任何具体的设计模式。设计模式应考虑灵活的指南应用程序如何可以是结构化,在这方面,骨干既不适合也不MVC MVP。相反,它借用了一些来自多个架构模式的最佳概念,并创建一个灵活的框架,只是效果很好。 它是不过值得理解的地方以及为什么这些概念的起源,所以我希望我的MVC和MVP的解释已经帮助。说它主干路,MV *或任何帮助引用其应用程序架构的味道。大部分结构性JavaScript框架将采用自己拿古典图案,无论是有意还是偶然,但重要的是,他们帮助我们开发这是有组织的,干净的,可以很容易维护的应用程序。 MVVM MVVM(模型视图视图模型)是基于MVC和MVP,它试图以更清楚地从该应用程序中的业务逻辑和行为的分开的用户接口(UI)的发展一种架构模式。为此,这种图案的许多实现利用声明性数据绑定的,以允许工作对从其它层次的分离。 这有利于同一个代码库中的几乎同时发生的用户界面和开发工作。UI开发人员编写自己的文档标记(HTML),其中模型和视图模型是通过在应用程序的逻辑的开发人员保持内绑定到视图模型。 历史 MVVM(按名称)最初是由微软的Windows Presentation Foundation中(使用定义WPF)和Silverlight的,有正式于2005年被宣布约翰·格罗斯曼在博客中关于阿瓦隆(代号为WPF)。研究还发现,在Adobe Flex社区一些人气,以替代简单地使用MVC。 此前微软采用MVVM名字,但是有在社会运动从MVP去MVPM:模型视图的PresentationModel。Martin Fowler的写了一篇文章在2004年PresentationModels回那些有兴趣阅读更多关于它。一个的想法的PresentationModel一直围绕更长的时间比这篇文章,但它被认为是在理念的大突破,极大地捧红了。 有相当多的沸沸扬扬的“alt.net”圈微软宣布MVVM作为替代MVPM后。许多人声称该公司在GUI世界霸主地位是让​​他们有机会到社区接管作为一个整体,重命名现有的概念,因为它们高兴的营销目的。渐进人群的认可,虽然MVVM和MVPM是有效同样的想法,他们来到略有不同的封装。 近年来,MVVM一直用JavaScript实现在结构框架,如形式KnockoutJS,剑道MVVM和Knockback.js,来自社会的总体积极响应。 现在让我们回顾了组成MVVM的三个组成部分。 模型 由于与MV *家族的其他成员,在MVVM模型代表特定于域的数据或信息,我们的应用程序将是工作。特定领域的数据,一个典型的例子是一个用户帐户(如姓名,头像,电子邮件)或音乐曲目(如名称,年份,专辑)。 模型保存信息,但通常不处理的行为。因为这不是他们的责任,他们不格式化的信息或数据的方式出现在浏览器的影响。相反,数据格式是由处理视图,而行为被认为应该在与模型交互另一个层封装业务逻辑 - 视图模型。 唯一的例外往往是验证和它被认为可以接受的模型验证被用来定义或更新现有的模型数据(如没有一个电子邮件地址为输入满足特定的正则表达式的要求是什么?)。 在KnockoutJS,秋款上述定义之下,但往往使Ajax调用到服务器端的服务来读取和​​写入模型数据。 如果我们构建了一个简单的Todo应用程序,KnockoutJS型号代表一个待办事项可能如下所示: ? 1 2 3 4 五 var Todo = function ( content, done ) { this.content = ko.observable(content); this.done = ko.observable(done); this.editing = ko.observable(false); }; 注:在我们调用的方法上面的代码中的一个可能会注意到observable()在KnockoutJS命名空间ko。在KnockoutJS,观测值是可以变更的有关通知用户,并自动检测依赖特殊的JavaScript对象。这使我们能够在一个模型属性的值被修改同步模型和的ViewModels。 视图 与MVC中,视图是用户实际交互应用程序的一部分。它们是代表一个视图模型的状态的交互式用户界面。在这个意义上说,鉴于被认为是主动而非被动的,但这也是如此意见MVC和MVP。在MVC,MVP和MVVM一个视图也可以是被动的,但是这是什么意思? 被动仅查看输出显示,并且不接受任何用户输入。 这种观点也可具有在我们申请的车型没有真正的知识和可以由演示者操纵。MVVM的活动视图包含数据绑定,事件和行为,需要的视图模型的理解。虽然这些行为可以映射到的属性,查看仍然是负责处理从视图模型的事件。 它使这与视图模型同步 - 这要记住的看法是不负责这里处理状态是很重要的。 一个KnockoutJS视图是一个简单的HTML文档与声明绑定其链接到视图模型。KnockoutJS视图显示从视图模型的信息,传递命令给它(如一个元素上的用户点击)和更新作为视图模型的状态发生改变。模板生成使用从视图模型数据标记可以但是也可以被用于此目的。 为了给出一个简要的最初的例子,我们可以看一下的JavaScript框架MVVM为KnockoutJS它是如何让一个视图模型及其相关的绑定在标记的定义: 视图模型: ? 1 2 3 4 var aViewModel = { contactName: ko.observable("John") }; ko.applyBindings(aViewModel); 视图: ? 1 2 3 4 五 <p><input id="source" data-bind="value: contactName, valueUpdate: 'keyup'" /></p> <div data-bind="visible: contactName().length > 10"> You have a really long name! </div> <p>Contact name: <strong data-bind="text: contactName"></strong></p> 我们的输入文本框(源)获得它从初始值contactName,自动更新该值时CONTACTNAME变化。由于数据绑定是双向的,输入到文本框将更新contactName相应因此它们的值总是保持同步。 虽然实施具体到KnockoutJS中,<div>包含了“你有一个很长的名字!” 文中还包含简单的验证(在一次又一次的数据绑定的形式)。如果输入超过10个字符,它会显示,否则会被隐藏起来。 移动到一个更高级的例子,我们可以回到我们的Todo应用。一个下调KnockoutJS查看该,包括所有必要的数据绑定可能如下所示。 ? 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 <div id="todoapp"> <header> <h1>Todos</h1> <input id="new-todo" type="text" data-bind="value: current, valueUpdate: 'afterkeydown', enterKey: add" placeholder="What needs to be done?"/> </header> <section id="main" data-bind="block: todos().length"> <input id="toggle-all" type="checkbox" data-bind="checked: allCompleted"> <label for="toggle-all">Mark all as complete</label> <ul id="todo-list" data-bind="foreach: todos"> <!-- item --> <li data-bind="css: { done: done, editing: editing }"> <div class="view" data-bind="event: { dblclick: $root.editItem }"> <input class="toggle" type="checkbox" data-bind="checked: done"> <label data-bind="text: content"></label> <a class="destroy" href="#" data-bind="click: $root.remove"></a> </div> <input class="edit' type="text" data-bind="value: content, valueUpdate: 'afterkeydown', enterKey: $root.stopEditing, selectAndFocus: editing, event: { blur: $root.stopEditing }"/> </li> </ul> </section> </div> 注意,标记式的基本布局是相对直接的,含有一个输入文本(new-todo用于添加新项目,togglers用于标记的项目,如完整和一个列表()todo-list中的表格)与模板的待办事项li。 在上面标记的数据绑定可以细分如下: 输入文本框new-todo有一个数据绑定的current属性,它是存储添加当前项目的价值在哪里。我们的视图模型(如图不久)观察current属性,也有反对有约束力的add事件。当按下回车键,add就会触发事件,那么我们的视图模型可以修剪的价值current,并根据需要将其添加到列表待办事项 输入框toggle-all已完成,如果点击可以标记所有当前的项目。如果选中,则触发allCompleted事件,这可以在我们的视图模型中可以看出 该项目li拥有一流的done。当作为完成的任务标记,CSS类editing则相应标记。如果该项目双击,则$root.editItem回调将被执行 带班的复选框toggle显示的状态done属性 一个标签包含待办事项的文本值(content) 还有一个删除按钮将调用$root.remove点击时的回调。 用于编辑模式的输入文本框还持有待办事项的值content。本enterKey次活动将设定的editing属性设置为true或false 视图模型 视图模型可以认为作为数据转换器的专用控制器。它改变模型​​信息转化为查看信息,传递命令,从视图模型。 例如,让我们想象,我们有一个包含UNIX格式的日期属性的模型(如1333832407)。而不是我们的模型意识到的日期(例如04/07/2012 @下午5:00),其中,这将是必要的属性转换为它的显示格式的用户的视野的,我们的模型简单地保持数据的原始格式。我们的视图包含格式化的日期,我们的视图模型充当两者之间的中间人。 在这个意义上说,视图模型可能在在一个多视图模型来看着,但它确实处理大多数视图的显示逻辑。该视图模型也可能会使帮助维护视图的状态,更新基于该行动的模型上查看视图和触发事件的方法。 综上所述,视图模型坐在我们的UI层后面。它暴露了一个视图所需要的数据(从模型),可以被看作是源我们的意见去的数据和操作。 KnockoutJS解释视图模型为可在一个UI执行的数据和操作的表示。这不是UI本身,也不是持续存在的数据模型,而是一个层,它也能容纳尚未被保存数据的用户正在使用。淘汰赛的的ViewModels都没有HTML标记的知识实现JavaScript对象。这种抽象的方式来执行这些允许他们保持简单,意味着更复杂的行为根据需要在最上可以更容易地管理。 的部分KnockoutJS视图模型为我们的Todo应用程序从而可能如下所示: ? 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 // our main ViewModel var ViewModel = function ( todos ) { var self = this; // map array of passed in todos to an observableArray of Todo objects self.todos = ko.observableArray( ko.utils.arrayMap( todos, function ( todo ) { return new Todo( todo.content, todo.done ); })); // store the new todo value being entered self.current = ko.observable(); // add a new todo, when enter key is pressed self.add = function ( data, event ) { var newTodo, current = self.current().trim(); if ( current ) { newTodo = new Todo( current ); self.todos.push( newTodo ); self.current(""); } }; // remove a single todo self.remove = function ( todo ) { self.todos.remove( todo ); }; // remove all completed todos self.removeCompleted = function () { self.todos.remove(function (todo) { return todo.done(); }); }; // writeable computed observable to handle marking all complete/incomplete self.allCompleted = ko.computed({ // always return true/false based on the done flag of all todos read:function () { return !self.remainingCount(); }, // set all todos to the written value (true/false) write:function ( newValue ) { ko.utils.arrayForEach( self.todos(), function ( todo ) { //set even if value is the same, as subscribers are not notified in that case todo.done( newValue ); }); } }); // edit an item self.editItem = function( item ) { item.editing( true ); }; .. 上面我们基本上提供了添加,编辑或删除项目,以及逻辑来标记所有其余的项目为已完成注所需的方法:唯一的区别值得在我们的视图模型前面的例子指出的是观察到的数组。在KnockoutJS,如果我们希望检测和响应单个对象上的变化,我们会用observables。然而,如果我们想检测和响应的事物的集合的变化,我们可以使用observableArray来代替。如何使用一个简单的例子观测阵列可以如下所示: ? 1 2 3 4 五 // Define an initially an empty array var myObservableArray = ko.observableArray(); // Add a value to the array and notify our observers myObservableArray.push( 'A new todo item' ); 注:上面我们审查的完整Knockout.js的Todo应用程序可以从抓住TodoMVC如果有兴趣。 简要回顾:视图和视图模型 意见及的ViewModels通信使用数据绑定和事件。正如我们在最初的视图模型的例子所看到的,视图模型并不仅仅暴露模型属性同时也获得了其他方法和功能,例如验证。 我们的浏览处理它们自己的用户界面的事件,它们映射到视图模型是必要的。模型和视图模型属性同步,并通过双向数据绑定更新。 触发器(数据触发器)也让我们进一步在我们的模型属性的状态变化作出反应。 简要回顾:视图模型和模型 虽然它可能会出现在视图模型是在MVVM模型完全负责,也有一些细微之处有了这层关系值得关注。该视图模型可以公开一个模或者​​模属性数据绑定的目的,还可以包含用于读取和操作视图公开的属性界面。 优点和缺点 现在,我们希望有什么MVVM以及它是如何工作的更好的欣赏。现在让我们回顾优势和用人模式的优缺点: 优点 MVVM便于用户界面更易于并行开发和构建块,它的功率 抽象化视图,从而减少业务逻辑(或胶水)的数量在它后面的代码需要 该视图模型能够比事件驱动的代码更容易进行单元测试 该视图模型(比查看更多机型)可以在不UI自动化,互动化的关注进行测试 缺点 对于简单的用户界面,MVVM可能是矫枉过正 虽然数据绑定可以声明和漂亮的工作,他们可以是难度比必要的代码,我们只需设置断点调试 在不平凡的应用程序的数据绑定可以创造很多簿记。我们也不想的情况下就结了哪里绑定比对象绑定到重 在较大的应用程序,也可以是更难以设计视图模型前面得到概括的必要量 MVVM随着宽松的数据绑定 它的情况并不少见,从一个MVC或MVP背景审查MVVM和抱怨它的真正关注点分离JavaScript开发人员。也就是说,内联数据绑定的数量保持在视图的HTML标记。 我必须承认,当我第一次审查MVVM的实现(如KnockoutJS,击退),我很惊讶,任何开发人员想回到昔日的天,我们与我们的标记混合逻辑(JavaScript的),并发现它很快难以维护。然而现实情况是,MVVM这是否为一些很好的理由(我们已经介绍),包括促进设计人员能够更轻松地从他们的标记绑定到逻辑。 对于我们之间的纯粹主义者,你会很高兴知道,我们现在也可以大大降低我们的感谢数据绑定到称为自定义绑定提供商功能如何依赖,介绍了KnockoutJS 1.3和提供,因为所有的版本。 默认KnockoutJS具有数据绑定提供商它搜索与任何元件data-bind对他们的属性,如在下面的例子。 ? 1 <input id="new-todo" type="text" data-bind="value: current, valueUpdate: 'afterkeydown', enterKey: add" placeholder="What needs to be done?"/> 当供应商找到这个属性的元素,它分析它,并把它变成使用当前数据上下文绑定对象。这是KnockoutJS的方式一直努力,让我们以声明绑定添加到KnockoutJS绑定到数据在这层因素。 一旦我们开始构建不再是微不足道的意见,我们最终可能与大量元素和属性的绑定在标记会变得难以管理。然而,对于自定义绑定提供商而言,这已经不再是一个问题。 有约束力的提供者是两件事情主要感兴趣: 当给定一个DOM节点,它包含任何数据绑定? 如果节点传递的第一个问题,什么是绑定对象看起来像在当前的数据上下文? 结合供应商实现两个功能: nodeHasBindings:这需要在这并不一定必须是一个元件DOM节点 getBindings:当施加到当前数据上下文返回表示绑定对象 因此,一个骨架结合提供商可能如下所示: ? 1 2 3 4 五 6 7 8 9 var ourBindingProvider = { nodeHasBindings: function( node ) { // returns true/false }, getBindings: function( node, bindingContext ) { // returns a binding object } }; 之前,我们得到充实这个供应商,让数据绑定属性简要讨论一下逻辑。 如果使用淘汰赛的MVVM当我们发现自己不满意的应用逻辑过于扎成您查看的想法,我们可以改变这一点。我们可以实现的东西有点类似CSS类的名字绑定分配到的元素。瑞恩·尼迈耶(的knockmeout.net)已使用先前建议data-class这个避免混淆演示教学班,数据类,让我们得到我们的nodeHasBindings功能支持这一点: ? 1 2 3 4 // does an element have any bindings? function nodeHasBindings( node ) { return node.getAttribute ? node.getAttribute("data-class") : false; }; 接下来,我们需要一个合理的getBindings()功能。当我们正在与CSS类的想法坚持,何不也考虑支持空格分隔类,允许我们不同元素之间共享绑定规格? 让我们首先看一下我们的绑定将是什么样子。我们创建一个对象来保存他们在那里我们的属性名必须匹配,我们希望在我们的数据类使用的键。 注:没有一个KnockoutJS应用程序使用传统的数据绑定到与自定义绑定提供不显眼的绑定转换所需的工作很大。我们只是拉我们所有的数据绑定的属性,在绑定对象按下面的数据类属性替换它们,并把我们的绑定: ? 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 var viewModel = new ViewModel( todos || [] ), bindings = { newTodo: { value: viewModel.current, valueUpdate: "afterkeydown", enterKey: viewModel.add }, taskTooltip: { visible: viewModel.showTooltip }, checkAllContainer: { visible: viewModel.todos().length }, checkAll: { checked: viewModel.allCompleted }, todos: { foreach: viewModel.todos }, todoListItem: function() { return { css: { editing: this.editing } }; }, todoListItemWrapper: function() { return { css: { done: this.done } }; }, todoCheckBox: function() { return { checked: this.done }; }, todoContent: function() { return { text: this.content, event: { dblclick: this.edit } }; }, todoDestroy: function() { return { click: viewModel.remove }; }, todoEdit: function() { return { value: this.content, valueUpdate: "afterkeydown", enterKey: this.stopEditing, event: { blur: this.stopEditing } }; }, todoCount: { visible: viewModel.remainingCount }, remainingCount: { text: viewModel.remainingCount }, remainingCountWord: function() { return { text: viewModel.getLabel(viewModel.remainingCount) }; }, todoClear: { visible: viewModel.completedCount }, todoClearAll: { click: viewModel.removeCompleted }, completedCount: { text: viewModel.completedCount }, completedCountWord: function() { return { text: viewModel.getLabel(viewModel.completedCount) }; }, todoInstructions: { visible: viewModel.todos().length } }; .... 然而有两行从以上片段缺失-我们仍然需要我们的getBindings功能,将通过各自在我们的数据类属性的关键,建立生成的对象从他们每个人的循环。如果我们检测到绑定的对象是一个函数,我们使用的背景下我们目前的数据调用它this。我们完整的定制绑定提供者将如下所示: ? 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 // We can now create a bindingProvider that uses // something different than data-bind attributes ko.customBindingProvider = function( bindingObject ) { this.bindingObject = bindingObject; // determine if an element has any bindings this.nodeHasBindings = function( node ) { return node.getAttribute ? node.getAttribute( "data-class" ) : false; }; }; // return the bindings given a node and the bindingContext this.getBindings = function( node, bindingContext ) { var result = {}, classes = node.getAttribute( "data-class" ); if ( classes ) { classes = classes.split( "" ); //evaluate each class, build a single object to return for ( var i = 0, j = classes.length; i < j; i++ ) { var bindingAccessor = this.bindingObject[classes[i]]; if ( bindingAccessor ) { var binding = typeof bindingAccessor === "function" ? bindingAccessor.call(bindingContext.$data) : bindingAccessor; ko.utils.extend(result, binding); } } } return result; }; }; 因此,我们的的最后几行bindings对象可以被定义如下: ? 1 2 3 4 五 6 7 // set ko's current bindingProvider equal to our new binding provider ko.bindingProvider.instance = new ko.customBindingProvider( bindings ); // bind a new instance of our ViewModel to the page ko.applyBindings( viewModel ); })(); 我们在这里所做的是有效地定义构造函数为我们它接受一个对象(绑定),我们用它来查找我们的绑定绑定处理程序。使用数据类,如下所示,我们可以再重新写的标记我们的应用程序查看: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <div id="create-todo"> <input id="new-todo" data-class="newTodo" placeholder="What needs to be done?" /> <span class="ui-tooltip-top" data-class="taskTooltip" style="display: none;">Press Enter to save this task</span> </div> <div id="todos"> <div data-class="checkAllContainer" > <input id="check-all" class="check" type="checkbox" data-class="checkAll" /> <label for="check-all">Mark all as complete</label> </div> <ul id="todo-list" data-class="todos" > <li data-class="todoListItem" > <div class="todo" data-class="todoListItemWrapper" > <div class="display"> <input class="check" type="checkbox" data-class="todoCheckBox" /> <div class="todo-content" data-class="todoContent" style="cursor: pointer;"></div> <span class="todo-destroy" data-class="todoDestroy"></span> </div> <div class="edit'> <input class="todo-input" data-class="todoEdit'/> </div> </div> </li> </ul> </div> 尼尔Kerkin放在一起使用上述完整TodoMVC演示应用程序,它可以访问和周围打了这里。 虽然它可能看起来像在上面的说明相当多的工作,现在我们有一个通用的getBindings方法写的,这是一个很大简单地重新使用它,并使用数据类,而不是严格的数据绑定的更多平凡的编写我们KnockoutJS应用程序来代替。最终的结果是希望更清洁的标记从视图被改变了我们的数据绑定到一个绑定的对象,而不是。 MVC比。MVP比。MVVM 这两个MVP和MVVM是MVC的衍生物。它和它的衍生物之间的主要区别是依赖每个层对其他层以及它们彼此的紧密的约束。 在MVC中,视图坐在我们的旁边控制器架构之上。模型坐在下面的控制器,因此我们的观点了解我们的控制器和控制器了解模型。在这里,我们的观点有模型的直接访问。然而,露出完整的模型视图可能有安全和性能成本,这取决于我们的应用程序的复杂性。MVVM试图避免这些问题。 在MVP中,控制器的角色被替换为主持人。主持人坐在同一水平的意见,听从视图和模型和调解他们之间的行动,这两个事件。与MVVM,没有绑定视图来的ViewModels的机制,所以我们不是靠每个View实现一个接口,使演示者与观交互。 因此MVVM允许我们创建一个可以包含的状态和逻辑信息模型的特定视图的子集,避免需要整个模型暴露于查看。不像MVP的主持人,一个ViewModel不需要参照。视图可以绑定到视图模型反过来揭露包含在模型到视图的数据属性。正如我们前面提到的,视图的抽象意味着在它后面的代码需要更少的逻辑。 其中一个缺点如此但是是需要的视图模型和视图,这可能有性能成本之间演绎的水平。在这种解释的复杂性也可能有所不同 - 它可以作为复制数据简单或操纵他们为我们想查看看到一个形式复杂。作为整个模型是现成的,可避免这种操纵的MVC没有这个问题。 Backbone.js的比。KnockoutJS 了解MVC,MVP和MVVM之间的细微差别是很重要的,但开发商最终将询问他们是否应该考虑使用基于什么我们学到了骨干KnockoutJS。以下注意事项可能会有所帮助在这里: 这两个库设计时考虑到不同的目标和它往往还不如刚刚选择MVC或MVVM简单 如果数据绑定和双向沟通是你的主要关注点,KnockoutJS绝对是可以映射到JavaScript来go.Practically存储在DOM节点的属性或值的方式,这种方法的对象。 主干其难易程度RESTful服务一体化的过人之处,而KnockoutJS模型只是需要更新模型必须由开发者编写的JavaScript对象和代码。 KnockoutJS有一个专注于自动化UI绑定,这需要显著更详细的自定义代码,如果试图与骨干做到这一点。这是不是与骨干本身本身有问题,因为它有目的地试图置身事外的UI。然而,击退并试图帮助这个问题。 随着KnockoutJS,我们可以绑定自己的功能视图模型观测,这是任何时候执行的观察到的变化。这使得我们的灵活性的水平相同可以在主干上找到 主干还内置了坚实的路由解决方案,同时KnockoutJS不提供路由选项的开箱。人们可以很容易但在填写如使用本Alman的需要这种行为烧烤插件或独立的路由系统像米勒梅德罗斯出色的 十字路口。 最后,我个人觉得KnockoutJS对于小型应用更适合,而骨干网的功能设置真正建立什么不平凡的时候眼前一亮。尽管如此,很多开发商都用了框架来写不同复杂程度的应用程序,我建议在做这可能最适合您的项目作出决定之前在小规模尝试两者。 为进一步阅读有关MVVM还是淘汰赛,我推荐以下相关文章: 作者MVVM的优势 SO:什么是与MVVM的问题? MVVM解释 如何MVVM比较MVC? 在KnockoutJS自定义绑定 探索与淘汰赛TodoMVC 现代模块化的JavaScript设计模式 退耦应用的重要性 在可扩展的JavaScript的世界里,当我们说一个应用程序是模块化的,我们往往意味着它是由一组高度解耦,功能不同的部分存储在模块。松 ​​散耦合通过去除方便的应用程序更易于维护的依赖在可能的情况。当这有效地实现,这是很容易看到如何改变一个系统的一个组成部分可能会影响到另一个。 不像一些更传统的编程语言不过,JavaScript代码当前迭代(ECMA-262)没有提供的手段开发者在清洁,有组织的方式导入代码这样的模块。这是与规范,没有经过仔细思量,直到最近几年在那里需要更多的有组织的JavaScript应用程序变得明显关注的问题之一。 相反,开发人员目前都留给依傍的变化模块或对象文字图案,这是我们之前在书中覆盖。随着许多这些,模块,脚本由一个单一的全局对象,它仍有 ​​可能招致我们的架构命名冲突所描述的命名空间串成的DOM。但也没有干净的方式来处理依赖管理没有一些手工劳动或第三方工具。 虽然这些问题本地解决方案将在抵达ES和谐(可能是JavaScript的下一个版本),好消息是,编写模块化的JavaScript从未如此简单,我们可以从今天开始这样做。 在本节中,我们将看看三种格式编写模块化的JavaScript:AMD,CommonJS的和建议的JavaScript,下一版本的和谐。 一个注脚本装载机 这是很难讨论AMD和CommonJS的模块,而不谈在房间里的大象- 脚本装载机。在写这本书的时候,脚本加载是一个目标的手段,这一目标是模块化的JavaScript,可在今天的应用中使用-这一点,使用兼容的脚本加载器是不幸必要的。为了充分利用这一部分,我建议获得了基本的了解流行的脚本加载工具如何工作等等的模块格式的解释在上下文中才有意义。 有许多在AMD和CommonJS的格式处理模块加载很大的装载机,但我个人的偏好RequireJS和curl.js。对这些工具的完整教程这本书的范围,但我可以推荐阅读约翰·汉恩的有关文章curl.js和詹姆斯·伯克的RequireJS更多的API文档。 从生产的角度来看,这样的模块工作时,建议部署使用优化工具(如RequireJS优化器)来连接的脚本。有趣的是,与杏仁 AMD的垫片,RequireJS不需要在展开部位被轧制和人们可能考虑一个脚本装载机可以开发之外很容易地移动。 这就是说,詹姆斯·伯克可能会说,能够动态地加载脚本后,页面加载仍然有它的用例和RequireJS可以用这个帮助了。考虑到这些票据,让我们开始吧。 AMD 一个格式化为编写模块化的JavaScript在浏览器 对于AMD(异步模块定义)格式的总体目标是提供模块化的JavaScript开发人员现在可以使用的解决方案。它脱胎于使用XHR +评估和这种格式的支持者Dojo的现实世界的经验要避免那些过去的​​弱点受到任何未来的解决方案。 AMD的模块格式本身是定义其中两个模块和依赖性,可模块的建议异步加载。它有许多明显的优点,包括被异步和由性质从而消除了紧密耦合的代码和模块身份之间的一个可能通常发现高度灵活。许多开发人员喜欢使用它,人们可以认为这是朝 ​​着一个可靠的垫脚石模块系统,提出了ES和谐。 AMD开始作为CommonJS的列表上的模块格式的规范草案,但因为它是不能够达到完全一致,格式的进一步发展移动到amdjs组。 今天,它是由项目,包括道场,MooTools的,萤火虫,甚至jQuery的拥抱。尽管术语CommonJS的AMD格式已经在一次野外被看见,这是最好的参照,把它当成AMD或异步模块的支持,而不是CommonJS的名单上的所有参与者都希望追求它。 注:有一段时间,该提案被作为模块运输/ C所指的时间,但是由于规范并没有对现有的传输模块CommonJS的齿轮,而是-定义模块-它更有意义选择AMD的命名约定。 入门模块 前两个概念值得注意约AMD是一家的理念define促进模块定义和方法require来处理依赖装载方法。定义用于定义使用下面的基于签名的有名或无名的模块: ? 1 2 3 4 五 define( module_id /*optional*/, [dependencies] /*optional*/, definition function /*function for instantiating the module or object*/ ); 正如我们可以通过内嵌批注知道的,module_id就是这是在同时使用非AMD拼接工具通常只要求一个可选的参数(可能还有一些其他的优势情况下,它也是有用的)。如果这种说法忽略了,我们指以模块为匿名。 当使用匿名模块的工作,一个模块的身份的想法是干的,使得它琐碎,以避免文件名 ​​和代码的重复。因为代码更轻便,它可以很容易地移动到其它位置(或周围的文件系统),而不需要改变代码本身或更改其模块ID。考虑module_id类似的文件夹路径的概念。 注:开发人员可以只通过使用AMD优化,与一个CommonJS的环境下工作,如运行在多种环境相同的代码r.js。 回到define签名,依赖参数表示这是由我们所定义的模块和第三个参数(“定义函数”或“工厂函数”)所需的依赖数组是的执行来实例化模块的功能。裸骨模块可以被定义如下: 了解AMD:定义() ? 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 // A module_id (myModule) is used here for demonstration purposes only define( "myModule", ["foo", "bar"], // module definition function // dependencies (foo and bar) are mapped to function parameters function ( foo, bar ) { // return a value that defines the module export // (i.e the functionality we want to expose for consumption) // create your module here var myModule = { doStuff: function () { console.log( "Yay! Stuff" ); } }; return myModule; }); // An alternative version could be.. define( "myModule", ["math", "graph"], function ( math, graph ) { // Note that this is a slightly different pattern // With AMD, it's possible to define modules in a few // different ways due to it's flexibility with // certain aspects of the syntax return { plot: function( x, y ){ return graph.drawPie( math.randomGrid( x, y ) ); } }; }); 需要在另一方面,通常使用在顶层JavaScript文件或模块中,我们应该要动态地获取依赖加载代码。其使用的一个例子是: 了解AMD:需要() ? 1 2 3 4 五 6 7 8 9 // Consider "foo" and "bar" are two external modules // In this example, the "exports" from the two modules // loaded are passed as function arguments to the // callback (foo and bar) so that they can similarly be accessed require(["foo", "bar"], function ( foo, bar ) { // rest of your code here foo.doSomething(); }); 动态加载的依赖 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 define(function ( require ) { var isReady = false, foobar; // note the inline require within our module definition require(["foo", "bar"], function ( foo, bar ) { isReady = true; foobar = foo() + bar(); }); // we can still return a module return { isReady: isReady, foobar: foobar }; }); 了解AMD:插件 以下是定义一个AMD兼容插件的例子: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 // With AMD, it's possible to load in assets of almost any kind // including text-files and HTML. This enables us to have template // dependencies which can be used to skin components either on // page-load or dynamically. define( ["./templates", "text!./template.md","css!./template.css" ], function( templates, template ){ console.log( templates ); // do something with our templates here } }); 注:虽然CSS!是包括在上述例子中加载的CSS相关性,它是要记住,这种方法有一些需要注意,如它不能被完全可能建立在CSS是满载是很重要的。这取决于我们如何对待我们的构建过程中,它也可能导致CSS被包括在优化文件的依赖关系,因此使用CSS在这种情况下,谨慎加载的依赖。如果有兴趣做上述情况,我们也可以探索@ VIISON的RequireJS CSS插件进一步这里。 加载AMD模块使用RequireJS ? 1 2 3 4 五 6 7 8 require(["app/myModule"], function( myModule ){ // start the main module which in-turn // loads other modules var module = new myModule(); module.doStuff(); }); 这个例子可以随心所欲地为来看待requirejs(["app/myModule"], function(){})这表明正在使用的加载器的顶尖水平全局。这是如何揭开序幕不同的AMD装载机模块的顶级装载然而与define()功能,如果它是通过当地要求所有require([])示例适用于这两种类型的装载机(curl.js和RequireJS)的。 加载AMD模块使用curl.js ? 1 2 3 4 五 6 7 8 9 curl(["app/myModule.js"], function( myModule ){ // start the main module which in-turn // loads other modules var module = new myModule(); module.doStuff(); }); 模块,递延依赖 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 // This could be compatible with jQuery's Deferred implementation, // futures.js (slightly different syntax) or any one of a number // of other implementations define(["lib/Deferred"], function( Deferred ){ var defer = new Deferred(); require(["lib/templates/?index.html","lib/data/?stats"], function( template, data ){ defer.resolve( { template: template, data:data } ); } ); return defer.promise(); }); AMD模块,道场 定义使用Dojo的AMD兼容模块是相当直接的。根据上述,在一个数组作为第一个参数定义任何模块依赖,并提供一个回调(工厂),一旦依赖已经被加载,这将执行该模块。例如: ? 1 2 3 4 五 6 define(["dijit/Tooltip"], function( Tooltip ){ //Our dijit tooltip is now available for local use new Tooltip(...); }); 需要注意的模块,它由一个Dojo异步加载,RequireJS或标准,现在可以既消耗的匿名性质dojo.require()模块加载。 没有与模块的引用是知道这里有用的一些有趣的陷阱。虽然引用模块的AMD-倡导的方式宣告他们与一组匹配参数的依赖关系列表,这是不是上了年纪的Dojo 1.6版本系统支持 - 它真的只适用于兼容AMD-装载机。例如: ? 1 2 3 4 五 6 define(["dojo/cookie", "dijit/Tooltip"], function( cookie, Tooltip ){ var cookieValue = cookie( "cookieName" ); new Tooltip(...); }); 这个拥有超过嵌套命名空间的许多优点,模块不再需要直接引用完整的命名空间的每一次 - 我们所要求的是“道场/曲奇”的依赖,曾经化名为参数的路径,可以通过该变量引用。这样就不需要重复键入了“道场”。在我们的应用。 最后的疑难杂症要注意的是,如果我们希望继续使用旧的Dojo构建系统,或者希望旧的模块迁移到这个新的AMD风格,下面的更详细的版本,使更容易迁移。请注意,Dojo和的dijit和引用为依赖太: ? 1 2 3 4 define(["dojo", "dijit', "dojo/cookie", "dijit/Tooltip"], function( dojo, dijit ){ var cookieValue = dojo.cookie( "cookieName" ); new dijit.Tooltip(...); }); AMD模块设计模式(道场) 正如我们在前面章节所看到的,设计模式可以提高我们如何处理结构化的解决方案,共同发展的问题非常有效。约翰·汉恩给有关AMD模块的设计模式,涵盖了辛格尔顿,装饰,中介和其他人,我的一些精彩演讲强烈建议您检查出他的幻灯片,如果我们得到一个机会。 AMD的设计模式的选择可以在下面找到。 Decorator模式: ? 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 // mylib/UpdatableObservable: a Decorator for dojo/store/Observable define(["dojo", "dojo/store/Observable"], function ( dojo, Observable ) { return function UpdatableObservable ( store ) { var observable = dojo.isFunction( store.notify ) ? store : new Observable(store); observable.updated = function( object ) { dojo.when( object, function ( itemOrArray) { dojo.forEach( [].concat(itemOrArray), this.notify, this ); }); }; return observable; }; }); // Decorator consumer // a consumer for mylib/UpdatableObservable define(["mylib/UpdatableObservable"], function ( makeUpdatable ) { var observable, updatable, someItem; // make the observable store updatable updatable = makeUpdatable( observable ); // `new` is optional! // we can then call .updated() later on if we wish to pass // on data that has changed //updatable.updated( updatedItem ); }); Adapter模式 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 // "mylib/Array" adapts `each` function to mimic jQuerys: define(["dojo/_base/lang", "dojo/_base/array"], function ( lang, array ) { return lang.delegate( array, { each: function ( arr, lambda ) { array.forEach( arr, function ( item, i ) { lambda.call( item, i, item ); // like jQuery's each }); } }); }); // Adapter consumer // "myapp/my-module": define(["mylib/Array"], function ( array ) { array.each( ["uno", "dos", "tres"], function ( i, esp ) { // here, `this` == item }); }); AMD模块使用jQuery 不像道场,jQuery的真的只配备了一个文件,但是考虑到库的基于插件的性质,我们可以证明它是多么直截了当定义使用它低于AMD模块。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 define(["js/jquery.js","js/jquery.color.js","js/underscore.js"], function( $, colorPlugin, _ ){ // Here we've passed in jQuery, the color plugin and Underscore // None of these will be accessible in the global scope, but we // can easily reference them below. // Pseudo-randomize an array of colors, selecting the first // item in the shuffled array var shuffleColor = _.first( _.shuffle( "#666","#333","#111"] ) ); // Animate the background-color of any elements with the class // "item" on the page using the shuffled color $( ".item" ).animate( {"backgroundColor": shuffleColor } ); // What we return can be used by other modules return {}; }); 然而有一些从这个例子缺失和它的注册的概念。 jQuery的注册作为一个异步兼容模块 其中一个在jQuery的1.7降落的主要特点是注册的jQuery作为异步模块的支持。有使用异步模块的格式将数字兼容脚本装载机(包括RequireJS和卷曲),其能够装载的模块,这意味着较少的黑客需要得到的东西的工作。 如果开发者想使用AMD和不希望自己的jQuery版本泄露到全球的空间,他们应该叫noConflict在使用jQuery的顶层模块。另外,由于jQuery的多个版本可以是一个页面上有一些一个AMD装载机必须考虑特殊的考虑,因此jQuery的只与已经认识到这些问题,这是由装载机指定指示的AMD装载机寄存器define.amd.jQuery。RequireJS和卷曲是两个加载器,这样做 命名AMD提供的既是坚固和安全的大多数用例安全毯。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 // Account for the existence of more than one global // instances of jQuery in the document, cater for testing // .noConflict() var jQuery = this.jQuery || "jQuery", $ = this.$ || "$", originaljQuery = jQuery, original$ = $; define(["jquery"], function ( $ ) { $( ".items" ).css( "background","green" ); return function () {}; }); 为什么AMD更好的选择对于编写模块化的JavaScript? 为如何处理定义灵活的模块一份明确的建议。 显著比现在全局命名空间和更清洁的<script>标签解决方案,我们许多人的依赖。有宣布独立模块和依赖关系,他们可能有一个干净的方式。 模块定义封装,帮助我们避免了全局命名空间的污染。 按理说作品比一些替代解决方案(如CommonJS的,我们将着眼于不久)更好。它不具有与跨域,本地或调试问题,并且没有被使用于服务器端工具的依赖。大多数AMD装载机支持在浏览器中加载模块,而构建过程。 提供了包括一个单一文件中的多个模块“运输”的做法。像CommonJS的其他方法还没有对传输格式达成一致。 如果这是需要有可能延迟加载脚本。 注:上述许多可对YUI的模块加载策略可以说为好。 相关阅读 该RequireJS指南AMD 什么是加载模块,AMD最快的方法? AMD与CommonJS的,有什么更好的格式? AMD是更好地为网络比CommonJS的模块 未来是不是模块框架 AMD不再是一个CommonJS的规格 在发明的JavaScript模块格式和文字装载机 AMD的邮件列表 什么脚本装载机和框架的支持AMD? 在浏览器: RequireJS http://requirejs.org curl.js http://github.com/unscriptable/curl bdLoad http://bdframework.com/bdLoad Yabble http://github.com/jbrantly/yabble PINF http://github.com/pinf/loader-js (和更多) 服务器端: RequireJS http://requirejs.org PINF http://github.com/pinf/loader-js AMD结论 已经采用了AMD的一些项目,我的结论是,它蜱大量的复选框开发商造成严重的应用程序可能会从一个更好的模块格式的愿望。它避免了需要担心全局,支持命名模块,不需要服务器转型发挥作用,是用于依赖管理一种乐趣。 这也是使用Backbone.js的,ember.js或任意数量的保持组织的其他应用程序的结构框架的模块化开发一个极好的补充。 作为AMD已经为Dojo和CommonJS的世界里近两年来被大量讨论,我们知道这是有时间成熟和发展。我们也知道它已经在野外被一些大公司打造不平凡的应用程序(IBM,BBC的iPlayer)等,如果它不工作,机会是他们应该已经放弃了它的实战检验,但没有。 尽管如此,仍有一些AMD可以改进的地方。谁使用的格式了一段时间的开发人员可能会觉得AMD的样板/封装代码是一个恼人的开销。虽然我有同样的忧虑,有工具,如瓦罗,可以帮助解决这些问题的工作,我认为,从整体上看,随着采用AMD的优点远远多于缺点。 CommonJS的 优化的服务器模块格式 该CommonJS的模块建议指定一个简单的API来声明模块服务器端和AMD不同尝试将覆盖更广泛的问题,如IO,文件系统,承诺等等。 该格式是提出了CommonJS的 -其目的是设计,原型和规范的JavaScript API的志愿者工作组。到目前为止,他们已经尝试批准为标准模块和包。 入门 从结构的角度来看,一个CommonJS的模块是JavaScript的一个可重复使用的一块其中出口提供给任何相关的代码的特定对象。AMD不同,通常有这样的周围没有任何模块函数封装(所以我们不会看到define这里的例子)。 CommonJS的模块主要包含两个主要部分:一个名为自由变量exports,其中包含一个模块希望提供给其他模块和对象require的模块可以使用导入其他模块的功能的出口。 了解CommonJS的:需要()和出口 ? 1 2 3 4 五 6 7 8 9 10 // package/lib is a dependency we require var lib = require( "package/lib" ); // behaviour for our module function foo(){ lib.log( "hello world!" ); } // export (expose) foo to other modules exports.foo = foo; 出口的基本消费 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // define more behaviour we would like to expose function foobar(){ this.foo = function(){ console.log( "Hello foo" ); } this.bar = function(){ console.log( "Hello bar" ); } } // expose foobar to other modules exports.foobar = foobar; // an application consuming "foobar" // access the module relative to the path // where both usage and module files exist // in the same directory var foobar = require("./foobar").foobar, test = new foobar(); // Outputs: "Hello bar" test.bar(); AMD当量的第一例CommonJS的 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 define(function(require){ var lib = require( "package/lib" ); // some behaviour for our module function foo(){ lib.log( "hello world!" ); } // export (expose) foo for other modules return { foobar: foo }; }); 这是可以做到像AMD支持简化CommonJS的缠绕特性。 消费多个依赖 app.js ? 1 2 3 4 五 6 7 8 9 10 var modA = require( "./foo" ); var modB = require( "./bar" ); exports.app = function(){ console.log( "Im an application!" ); } exports.foo = function(){ return modA.helloWorld(); } bar.js ? 1 exports.name = "bar"; foo.js ? 1 2 3 4 require( "./bar" ); exports.helloWorld = function(){ return "Hello World!!" } 什么装载机和框架的支持CommonJS的? 在浏览器: curl.js http://github.com/unscriptable/curl SproutCore的1.1 http://sproutcore.com PINF http://github.com/pinf/loader-js 服务器端: 节点http://nodejs.org 独角鲸https://github.com/tlrobinson/narwhal 坚持http://www.persvr.org/ Wakanda http://www.wakandasoft.com/ 是CommonJS的适合浏览器? 有开发商觉得CommonJS的是更适合于服务器端的开发这是一个原因还有目前水平的分歧在哪一种格式应,将用作和谐预时代前进的事实标准。一些对CommonJS的参数包括一张纸条,上面很多CommonJS的API的解决面向服务器的功能,其中之一就根本无法实现在JavaScript的浏览器级别的-例如,IO,系统和JS可以考虑通过自然unimplementable其功能。 这就是说,它是非常有用的知道如何构建CommonJS的模块,而不管这样我们就可以更好地理解它们是如何界定可到处被使用模块时。这在客户端和服务器上的应用模块包括验证,转换和模板引擎。一些开发商已经接近选择使用哪种格式的方法是选择了CommonJS的当一个模块可以在服务器端环境中使用,并使用AMD如果不是这种情况。 作为AMD模块能够使用的插件,可以这样定义构造函数和功能更精细的东西,这个是有道理的。CommonJS的模块只能定义可以是与繁琐,如果我们试图获得构造出他们的工作对象。 虽然它超出了本节的范围,人们可能还注意到,有不同类型的讨论AMD和CommonJS的时候提到“规定”的方法。用类似的命名约定的关注当然是混乱和社会各界正在分裂,在全球需要的功能的优点。在这里约翰·汉恩的建议是,与其称之为“需要”,这很可能将无法实现全球和内蒙两地通知用户不同的要求,这可能更有意义重命名的全球装载 ​​机法别的目标(如:库的名称)。这是出于这个原因,像curl.js装载机使用curl(),而不是require。 相关阅读 揭秘CommonJS的模块 JavaScript的成长 该RequireJS札记CommonJS的 采取婴孩步骤,Node.js和受CommonJS的 - 创建自定义模块 异步CommonJS的模块浏览器 在CommonJS的邮件列表 AMD && CommonJS的竞争,但同样有效的标准 AMD和CommonJS的有效模块格式,不同的最终目标。 AMD采用了浏览器的第一的发展方针,选择了异步行为和简化的向后兼容,但它不具有文件I / O的任何概念。它支持对象,函数,构造函数,字符串,JSON和许多其他类型的模块,在浏览器中运行的本身。这是令人难以置信的灵活性。 CommonJS的,另一方面需要一个服务器第一的方针,假设同步行为,没有全球性的行李,并试图应付未来的(在服务器上)。我们的意思是,由于CommonJS的支持展开模块,它可以感觉有点更贴近ES.next/Harmony规格,释放我们的define(),AMD公司实施包装。CommonJS的模块但是只支持对象的模块。 UMD:AMD和CommonJS的兼容模块插件 对于那些希望创建可以在浏览器都和服务器端环境中工作模块的开发,现有的解决方案可以考虑稍有欠缺。为了帮助减轻这些的,詹姆斯·伯克,我和其他一些开发人员创建的UMD(通用模块定义)https://github.com/umdjs/umd。 UMD是一个实验性的模块格式,允许符合可在写作时流行的脚本加载技术的全部或大部分客户端和服务器的环境中工作模块的定义。虽然(还)其它模块格式的想法可能是艰巨的,我们将介绍UMD简要的完整性考虑。 我们最初开始采取看看在AMD规范的支持简化CommonJS的包装定义UMD。为希望如同它们是CommonJS的模块写模块的开发者,以下CommonJS的兼容的格式可以使用: 基本AMD混合格式 ? 1 2 3 4 五 6 7 8 define( function ( require, exports, module ){ var shuffler = require( "lib/shuffle" ); exports.randomize = function( input ){ return shuffler.shuffle( input ); } }); 要注意,一个模块是真正仅视为CommonJS的模块,如果它不包含一个依赖阵列和定义函数包含在最小的一个参数是多么重要。这也将无法在某些设备上正常工作(如PS3)。有关上述包装的详细信息,请参阅http://requirejs.org/docs/api.html#cjsmodule。 有鉴于此进一步,我们希望提供一个数字,不仅仅是AMD和CommonJS的工作不同的模式,而且还解决了那些希望开发这样的模块有与其他环境中很常见的兼容性问题的开发人员。 一个这样的变化,我们可以看到下面让我们使用CommonJS的,AMD或浏览器全局来创建一个模块。 使用CommonJS的,AMD或浏览器全局来创建一个模块 定义一个模块commonJsStrict,这取决于另一个名为模块b。该模块的名称由文件名 ​​和文件名 ​​,导出的全局名称相同的最佳实践暗示。 如果模块b也使用相同类型的样板中的浏览器,它会创建一个全球性.b的使用。如果我们不希望支持的浏览器补丁全球,我们可以删除root和传递this的第一个参数顶端的功能。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 (function ( root, factory ) { if ( typeof exports === 'object' ) { // CommonJS factory( exports, require('b') ); } else if ( typeof define === 'function' && define.amd ) { // AMD. Register as an anonymous module. define( ['exports', 'b'], factory); } else { // Browser globals factory( (root.commonJsStrict = {}), root.b ); } }(this, function ( exports, b ) { //use b in some fashion. // attach properties to the exports object to define // the exported module properties. exports.action = function () {}; })); 该UMD库包含变化涵盖在浏览器中最佳状态工作,这些最适合提供出口,那些最适合CommonJS的运行时间,甚至那些最适合定义jQuery插件,我们将看看在下次模块。 jQuery插件,在所有环境中正常运行 UMD提供使用jQuery插件工作两种模式 - 其中一个定义了与AMD和浏览器全局,另一个也可以在CommonJS的环境中工作,工作得很好的插件。jQuery是不太可能在大多数CommonJS的被使用的环境,以便记住这一点,除非我们与它没有发挥它很好的环境中工作。 现在,我们将定义一个芯和一个扩展该核心组成插件。核心插件加载到一个$.core命名空间中,然后可以通过命名空间模式使用插件扩展轻松扩展。通过脚本标签加载的插件自动填充plugin下空间core(即$.core.plugin.methodName())。 该模式可以是相当不错的,因为与扩展插件可以访问在基本定义的属性和方法,或用少许调整,覆盖默认行为,以便它可以扩展到做更多的工作。也没有需要一个装载机做任何的这个功能齐全。 对于正在做什么的详细信息,请参见下面的代码示例中内嵌批注。 usage.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 <script type="text/javascript" src="jquery-1.7.2.min.js"></script> <script type="text/javascript" src="pluginCore.js"></script> <script type="text/javascript" src="pluginExtension.js"></script> <script type="text/javascript"> $(function(){ // Our plugin "core" is exposed under a core namespace in // this example, which we first cache var core = $.core; // Then use use some of the built-in core functionality to // highlight all divs in the page yellow core.highlightAll(); // Access the plugins (extensions) loaded into the "plugin" // namespace of our core module: // Set the first div in the page to have a green background. core.plugin.setGreen( "div:first"); // Here we're making use of the core's "highlight" method // under the hood from a plugin loaded in after it // Set the last div to the "errorColor" property defined in // our core module/plugin. If we review the code further down, // we can see how easy it is to consume properties and methods // between the core and other plugins core.plugin.setRed("div:last"); }); </script> pluginCore.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 // Module/Plugin core // Note: the wrapper code we see around the module is what enables // us to support multiple module formats and specifications by // mapping the arguments defined to what a specific format expects // to be present. Our actual module functionality is defined lower // down, where a named module and exports are demonstrated. // // Note that dependencies can just as easily be declared if required // and should work as demonstrated earlier with the AMD module examples. (function ( name, definition ){ var theModule = definition(), // this is considered "safe": hasDefine = typeof define === "function" && define.amd, // hasDefine = typeof define === "function", hasExports = typeof module !== "undefined" && module.exports; if ( hasDefine ){ // AMD Module define(theModule); } else if ( hasExports ) { // Node.js Module module.exports = theModule; } else { // Assign to common namespaces or simply the global object (window) ( this.jQuery || this.ender || this.$ || this)[name] = theModule; } })( "core", function () { var module = this; module.plugins = []; module.highlightColor = "yellow"; module.errorColor = "red"; // define the core module here and return the public API // This is the highlight method used by the core highlightAll() // method and all of the plugins highlighting elements different // colors module.highlight = function( el,strColor ){ if( this.jQuery ){ jQuery(el).css( "background", strColor ); } } return { highlightAll:function(){ module.highlight("div", module.highlightColor); } }; }); pluginExtension.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 // Extension to module core (function ( name, definition ) { var theModule = definition(), hasDefine = typeof define === "function", hasExports = typeof module !== "undefined" && module.exports; if ( hasDefine ) { // AMD Module define(theModule); } else if ( hasExports ) { // Node.js Module module.exports = theModule; } else { // Assign to common namespaces or simply the global object (window) // account for for flat-file/global module extensions var obj = null, namespaces, scope; obj = null; namespaces = name.split("."); scope = ( this.jQuery || this.ender || this.$ || this ); for ( var i = 0; i < namespaces.length; i++ ) { var packageName = namespaces[i]; if ( obj && i == namespaces.length - 1 ) { obj[packageName] = theModule; } else if ( typeof scope[packageName] === "undefined" ) { scope[packageName] = {}; } obj = scope[packageName]; } } })( "core.plugin", function () { // Define our module here and return the public API. // This code could be easily adapted with the core to // allow for methods that overwrite and extend core functionality // in order to expand the highlight method to do more if we wish. return { setGreen: function ( el ) { highlight(el, "green"); }, setRed: function ( el ) { highlight(el, errorColor); } }; }); UMD并不打算取代AMD也不CommonJS的,而只是提供了希望得到他们的代码在今天更多的环境中工作的开发人员的一些补充援助。如需了解更多信息或对本实验格式献计献策,看https://github.com/umdjs/umd。 深入阅读 “ 使用AMD装载机编写和管理模块化的JavaScript,”约翰·汉恩 “ 揭秘CommonJS的模块,”亚历克斯杨 “ AMD模块模式:辛格尔顿,”约翰·汉恩 “ 随处运行的JavaScript模块样板代码,”克里斯Zyp也 “ 标准和JavaScript的模块和jQuery的建议,”詹姆斯·伯克 ES和谐 模块的未来 TC39,负责确定ECMAScript和其未来迭代的语法和语义的标准组织是由一些非常聪明的开发者。有些开发商(如亚历克斯·罗素)一直保持在过去几年中使用的JavaScript进行大规模的发展演变密切关注和敏锐地编写更模块化的JS意识到需要更好的语言功能。 出于这个原因,目前有对一些令人兴奋的补充包括灵活的语言提案模块可在客户端两者和服务器,一个上工作模块加载和更多。在本节中,我们将探讨使用拟在ES.next模块的语法,所以我们可以得到什么样的来一尝代码示例。 注意:尽管和谐是仍处于建议阶段,我们已经可以尝试ES.next的(部分)功能,满足模块化的JavaScript写的感谢谷歌的原生支持Traceur编译器。起床,在一分钟内用Traceur运行,请阅读本入门指南。还有一个JSConf 介绍一下这是值得考虑的,如果想了解更多有关项目。 模块,进口和出口 通过AMD和CommonJS的模块部分读过你可能熟悉模块依赖关系(进口)和模块的出口(或公共API /变量,我们让其他模块消耗)的概念。在ES.next,这些概念已经在一个稍微更简洁的方式与使用被指定的依赖提出的import关键字。export是不是我们所期望的,很多开发商会看代码样本低了下去,瞬间抓住他们很大的不同。 进口报关绑定组件出口作为局部变量,可能被重命名为避免名称冲突/冲突。 出口报关声明一个模块的本地绑定是外部可见的,使得其他模块可以读取的出口,但不能修改它们。有趣的是,模块可能出口子模块,但不能导出已在其他地方定义的模块。所以他们的外部名称从当地的名字不同,我们还可以重命名出口。 ? 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 module staff{ // specify (public) exports that can be consumed by // other modules export var baker = { bake: function( item ){ console.log( "Woo! I just baked " + item ); } } } module skills{ export var specialty = "baking"; export var experience = "5 years"; } module cakeFactory{ // specify dependencies import baker from staff; // import everything with wildcards import * from skills; export var oven = { makeCupcake: function( toppings ){ baker.bake( "cupcake", toppings ); }, makeMuffin: function( mSize ){ baker.bake( "muffin", size ); } } } 加载从远程源模块 该模块建议还迎合这是基于远程使其简单化加载从外部位置模块模块(如第三方库)。下面是我们上面并利用它定义模块拉动的例子: ? 1 2 3 module cakeFactory from "http://addyosmani.com/factory/cakes.js"; cakeFactory.oven.makeCupcake( "sprinkles" ); cakeFactory.oven.makeMuffin( "large" ); 模块加载器API 提出的模块加载描述了高度受控的环境中加载模块动态API。支撑在装载机签名包括load(url, moduleInstance, error) 用于装载模块,createModule(object, globalModuleReferences)和其它。 下面是我们最初定义的模块动态加载另一个例子。需要注意的是,我们的模块中拉出从远程源最后一个例子不同的是,模块加载API是更适合于动态环境。 ? 1 2 3 4 Loader.load( "http://addyosmani.com/factory/cakes.js", function( cakeFactory ){ cakeFactory.oven.makeCupcake( "chocolate" ); }); CommonJS的类模块服务器 对于开发商谁更感兴趣的服务器环境,提出了ES.next模块系统不只是受限于看着在浏览器模块。下面举例来说,我们可以看到建议在服务器上使用CommonJS的类模块: ? 1 2 3 // io/File.js export function open( path ) { ... }; export function close( hnd ) { ... }; ? 1 2 3 4 五 6 7 8 9 10 // compiler/LexicalHandler.js module file from "io/File"; import { open, close } from file; export function scan( in ) { try { var h = open( in ) ... } finally { close( h ) } } ? 1 2 3 4 module lexer from "compiler/LexicalHandler"; module stdlib from "@std"; //... scan(cmdline[0]) ... 班,构造函数,吸气和二传手 一个类的概念始终与较真的一个有争议的问题,我们目前有两种回落对JavaScript的相处原型性质或通过使用框架或抽象,提供能够使用类的形式定义了脱糖到相同的原型的行为。 在和谐,类已提出了与构造函数和(最终)一些真正意义上的隐私沿着语言。在下面的例子中,提供内嵌批注,以有助于解释类是如何构成的。 通过阅读,我们也可能会注意到缺少这里所说的“功能”的。这是不是一个错字错误:TC39一直在有意识地努力,以减少我们对滥用function关键字的一切,并希望是,这将有助于简化我们如何编写代码。 ? 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 class Cake{ // We can define the body of a class" constructor // function by using the keyword "constructor" followed // by an argument list of public and private declarations. constructor( name, toppings, price, cakeSize ){ public name = name; public cakeSize = cakeSize; public toppings = toppings; private price = price; } // As a part of ES.next's efforts to decrease the unnecessary // use of "function" for everything, you'll notice that it's // dropped for cases such as the following. Here an identifier // followed by an argument list and a body defines a new method addTopping( topping ){ public( this ).toppings.push( topping ); } // Getters can be defined by declaring get before // an identifier/method name and a curly body. get allToppings(){ return public( this ).toppings; } get qualifiesForDiscount(){ return private( this ).price > 5; } // Similar to getters, setters can be defined by using // the "set" keyword before an identifier set cakeSize( cSize ){ if( cSize < 0 ){ throw new Error( "Cake must be a valid size - either small, medium or large" ); } public( this ).cakeSize = cSize; } } ES和谐结论 正如我们所看到的,和谐可能附带一些令人兴奋的新增加,这将缓解模块化的应用和处理的问题,如依赖管理的发展。 目前,在今天的浏览器中使用的语法和谐我们最好的选择是通过一个transpiler如谷歌Traceur或Esprima。还有一些项目,如要求HM这允许我们使用模块的和谐与AMD。我们最好的赌注然而,直到我们有规范的定稿是AMD(在浏览器模块)和CommonJS的(对于那些在服务器上)。 相关阅读 答:首先看一下即将JavaScript的模块 大卫·赫尔曼于JavaScript / ES.Next(视频) ES和谐模块的建议 ES和谐模块语义/结构原理 ES和谐类提案 结论 在本节中,我们审查了若干可供利用现代模块格式编写模块化的JavaScript选项。 这些格式有许多优于单独使用模块模式包括:避免了需要管理的全局变量,静态和动态依赖管理更好的支持,改善了与脚本装载机的兼容性,服务器和更多的模块,更好的兼容性。 总之,我建议您尝试了什么本章中被认为是这些格式提供了强大功能和灵活性,可以更好的组织我们的应用程序显著的帮助很大。 设计模式的jQuery jQuery是目前最流行的JavaScript DOM操作库,并提供了一​​个安全的,跨浏览器的方式与DOM交互的抽象层。有趣的是,该库也作为如何设计模式可以有效地用于创建的API是可读和易于使用的例子。 虽然在许多情况下写的jQuery核心贡献者并没有特意使用特定的模式,无论他们存在那里,从学习有用的。让我们来看看什么的一些模式,以及它们是如何在API中使用。 Composite模式 的组合模式描述了一组,可以以同样的方式的对象的单个实例可以是被处理的对象。 这使我们能够以统一的方式处理这两个单独的对象和组合物,这意味着相同的行为将被无论我们是否正在与一个项目或千工作的应用。 在jQuery中,当我们运用方法的一个或多个元素的集合,我们可以在一个统一的方式既选择返回一个jQuery对象对待两套。 这是通过使用下面的jQuery选择的代码示例演示。在这里,它可能是添加active类都选择了一个元素(例如,使用一个唯一的ID的元素)或一组具有相同标记名或类的元素,而无需额外的努力: ? 1 2 3 4 五 6 7 8 // Single elements $( "#singleItem" ).addClass( "active" ); $( "#container" ).addClass( "active" ); // Collections of elements $( "div" ).addClass( "active" ); $( ".item" ).addClass( "active" ); $( "input" ).addClass( "active" ); jQuery的addClass()实现可以直接使用本地的回路(或jQuery的jQuery.each()/ jQuery.fn.each()),以迭代通过集合为了应用该方法既单品或组。翻翻我们可以看到这款源确实是这样的: ? 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 addClass: function( value ) { var classNames, i, l, elem, setClass, c, cl; if ( jQuery.isFunction( value ) ) { return this.each(function( j ) { jQuery( this ).addClass( value.call(this, j, this.className) ); }); } if ( value && typeof value === "string" ) { classNames = value.split( rspace ); for ( i = 0, l = this.length; i < l; i++ ) { elem = this[ i ]; if ( elem.nodeType === 1 ) { if ( !elem.className && classNames.length === 1 ) { elem.className = value; } else { setClass = " " + elem.className + " "; for ( c = 0, cl = classNames.length; c < cl; c++ ) { if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { setClass += classNames[ c ] + " "; } } elem.className = jQuery.trim( setClass ); } } } } return this; } Adapter模式 该适配器模式转换的接口对象或类与特定系统兼容的接口。 适配器基本上允许对象或类的共同作用通常不能因自己的不兼容的接口。该适配器转换调用它的接口进入调用原来的界面,并实现这通常是相当小所需的代码。 我们可能已使用的适配器的一个例子是jQuery的jQuery.fn.css()方法。它有助于正常化的接口如何样式可以跨多个浏览器的应用,使得它琐碎我们使用一个简单的语法,其适于使用什么浏览器实际上支持幕后: ? 1 2 3 4 五 6 7 8 9 // Cross browser opacity: // opacity: 0.9; Chrome 4+, FF2+, Saf3.1+, Opera 9+, IE9, iOS 3.2+, Android 2.1+ // filter: alpha(opacity=90); IE6-IE8 // Setting opacity $( ".container" ).css( { opacity: .5 } ); // Getting opacity var currentOpacity = $( ".container" ).css('opacity'); 相应的jQuery芯cssHook这使得上述可能的下面可以看到: ? 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 get: function( elem, computed ) { // IE uses filters for opacity return ropacity.test( ( computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ? ( parseFloat( RegExp.$1 ) / 100 ) + "" : computed ? "1" : ""; }, set: function( elem, value ) { var style = elem.style, currentStyle = elem.currentStyle, opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "", filter = currentStyle && currentStyle.filter || style.filter || ""; // IE has trouble with opacity if it does not have layout // Force it by setting the zoom level style.zoom = 1; // if setting opacity to 1, and no other filters //exist - attempt to remove filter attribute #6652 if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) { // Setting style.filter to null, "" & " " still leave // "filter:" in the cssText if "filter:" is present at all, // clearType is disabled, we want to avoid this style.removeAttribute // is IE Only, but so apparently is this code path... style.removeAttribute( "filter" ); // if there there is no filter style applied in a css rule, we are done if ( currentStyle && !currentStyle.filter ) { return; } } // otherwise, set new filter values style.filter = ralpha.test( filter ) ? filter.replace( ralpha, opacity ) : filter + " " + opacity; } }; Facade模式 正如我们在本书的前面审查,但外观模式提供了一个更简单的抽象接口的代码较大(可能更复杂)身上。 外立面可以经常发现整个jQuery库和处理DOM操作,动画和特别感兴趣,跨​​浏览器的Ajax开发者提供容易获得实现。 以下是jQuery的门面$.ajax(): ? 1 2 3 4 $.get( url, data, callback, dataType ); $.post( url, data, callback, dataType ); $.getJSON( url, data, callback ); $.getScript( url, callback ); 这些被转换的场景背后: ? 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 // $.get() $.ajax({ url: url, data: data, dataType: dataType }).done( callback ); // $.post $.ajax({ type: "POST", url: url, data: data, dataType: dataType }).done( callback ); // $.getJSON() $.ajax({ url: url, dataType: "json", data: data, }).done( callback ); // $.getScript() $.ajax({ url: url, dataType: "script", }).done( callback ); 什么是更有趣的是,上面的外墙其实是门面在自己的权利,躲在幕后的复杂性很大。 这是因为jQuery.ajax()在jQuery的核心实现是一个不平凡的一段代码,至少可以说。在它至少标准化XHR(XMLHttpRequest的)之间的跨浏览器的差异,使平凡的我们执行常用的HTTP操作(例如get,post等),工作,Deferreds等。 因为它会采取整章显​​示所有与上述有关的外墙,这里的代码是不是在jQuery的核心正火XHR的代码: ? 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 // Functions to create xhrs function createStandardXHR() { try { return new window.XMLHttpRequest(); } catch( e ) {} } function createActiveXHR() { try { return new window.ActiveXObject( "Microsoft.XMLHTTP" ); } catch( e ) {} } // Create the request object jQuery.ajaxSettings.xhr = window.ActiveXObject ? /* Microsoft failed to properly * implement the XMLHttpRequest in IE7 (can't request local files), * so we use the ActiveXObject when it is available * Additionally XMLHttpRequest can be disabled in IE7/IE8 so * we need a fallback. */ function() { return !this.isLocal && createStandardXHR() || createActiveXHR(); } : // For all other browsers, use the standard XMLHttpRequest object createStandardXHR; ... 虽然下面的代码块也高于实际的jQuery XHR(A级jqXHR)的实现,它的方便门面,我们实际上是最常见的交互: ? 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 // Request the remote document jQuery.ajax({ url: url, type: type, dataType: "html", data: params, // Complete callback (responseText is used internally) complete: function( jqXHR, status, responseText ) { // Store the response as specified by the jqXHR object responseText = jqXHR.responseText; // If successful, inject the HTML into all the matched elements if ( jqXHR.isResolved() ) { // Get the actual response in case // a dataFilter is present in ajaxSettings jqXHR.done(function( r ) { responseText = r; }); // See if a selector was specified self.html( selector ? // Create a dummy div to hold the results jQuery("<div>") // inject the contents of the document in, removing the scripts // to avoid any 'Permission Denied' errors in IE .append(responseText.replace(rscript, "")) // Locate the specified elements .find(selector) : // If not, just inject the full result responseText ); } if ( callback ) { self.each( callback, [ responseText, status, jqXHR ] ); } } }); return this; } </div> 观察者模式 正如我们之前另一种模式是观察者(发布/订阅)模式。这是在一个系统中的对象可以订阅其它目的和当感兴趣的事件发生时被他们通知。 jQuery的核心已经内置了支持发布/订阅制样了几年,现在,它是指为自定义事件。 在早期版本的库,获得这些自定义的事件是可以使用jQuery.bind()(订阅), jQuery.trigger()(发布)和jQuery.unbind()(退订),但在最近的版本中这是可以做到jQuery.on(),jQuery.trigger()和jQuery.off()。 下面,我们可以看到这是在实践中使用的一个示例: ? 1 2 3 4 五 6 7 8 9 10 // Equivalent to subscribe(topicName, callback) $( document ).on( "topicName", function () { //..perform some behaviour }); // Equivalent to publish(topicName) $( document ).trigger( "topicName" ); // Equivalent to unsubscribe(topicName) $( document ).off( "topicName" ); 调用jQuery.on()并jQuery.off()最终通过jQuery的事件系统。到的Ajax类似,作为本实施是比较长,就可以代替看在何处以及如何对定制事件的实际事件处理附: ? 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 jQuery.event = { add: function( elem, types, handler, data, selector ) { var elemData, eventHandle, events, t, tns, type, namespaces, handleObj, handleObjIn, quick, handlers, special; ... // Init the element's event structure and main handler, //if this is the first events = elemData.events; if ( !events ) { elemData.events = events = {}; } ... // Handle multiple events separated by a space // jQuery(...).bind("mouseover mouseout", fn); types = jQuery.trim( hoverHack(types) ).split( " " ); for ( t = 0; t < types.length; t++ ) { ... // Init the event handler queue if we're the first handlers = events[ type ]; if ( !handlers ) { handlers = events[ type ] = []; handlers.delegateCount = 0; // Only use addEventListener/attachEvent if the special // events handler returns false if ( !special.setup || special.setup.call( elem, data, //namespaces, eventHandle ) === false ) { // Bind the global event handler to the element if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle, false ); } else if ( elem.attachEvent ) { elem.attachEvent( "on" + type, eventHandle ); } } } 对于那些喜欢使用传统的命名方案观察者模式,本Alman创建围绕为我们提供了访问上述方法的简单包装jQuery.publish(),jQuery.subscribe和jQuery.unsubscribe方法。我以前与他们有关本书前面,但是我们可以看到包装在下面满。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 (function( $ ) { var o = $({}); $.subscribe = function() { o.on.apply(o, arguments); }; $.unsubscribe = function() { o.off.apply(o, arguments); }; $.publish = function() { o.trigger.apply(o, arguments); }; }( jQuery )); 在最近版本的jQuery,一物多用的回调对象(jQuery.Callbacks)的提供,使用户能够编写基于回调列出新的解决方案。一个这样的解决方法写使用此功能是另一种发布/订阅系统。这方面的一个实施如下: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 var topics = {}; jQuery.Topic = function( id ) { var callbacks, topic = id && topics[ id ]; if ( !topic ) { callbacks = jQuery.Callbacks(); topic = { publish: callbacks.fire, subscribe: callbacks.add, unsubscribe: callbacks.remove }; if ( id ) { topics[ id ] = topic; } } return topic; }; 然后可以使用如下: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 // Subscribers $.Topic( "mailArrived" ).subscribe( fn1 ); $.Topic( "mailArrived" ).subscribe( fn2 ); $.Topic( "mailSent" ).subscribe( fn1 ); // Publisher $.Topic( "mailArrived" ).publish( "hello world!" ); $.Topic( "mailSent" ).publish( "woo! mail!" ); // Here, "hello world!" gets pushed to fn1 and fn2 // when the "mailArrived" notification is published // with "woo! mail!" also being pushed to fn1 when // the "mailSent" notification is published. // Outputs: // hello world! // fn2 says: hello world! // woo! mail! 迭代器模式 迭代器是一种设计模式,其中的迭代器(对象,使我们能够通过一个集合的所有元素遍历)访问聚合对象顺序的元素,而无需暴露它的基本形式。 迭代封装的特定迭代是如何发生的内部结构。在jQuery的情况下jQuery.fn.each()迭代,我们实际上能够使用底层代码后面jQuery.each()通过集合迭代,而无需看到或理解的代码提供这种能力的幕后工作。 这是一个可以考虑的门面,我们明确地处理有关问题的迭代的特殊情况的模式。 ? 1 2 3 4 五 6 7 $.each( ["john","dave","rick","julian"], function( index, value ) { console.log( index + ": "" + value); }); $( "li" ).each( function ( index ) { console.log( index + ": " + $( this ).text()); }); 在这里,我们可以看到的代码jQuery.fn.each(): ? 1 2 3 4 // Execute a callback for every element in the matched set. each: function( callback, args ) { return jQuery.each( this, callback, args ); } 紧接着后面的代码jQuery.each()用于处理通过迭代对象的方法有两种: ? 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 each: function( object, callback, args ) { var name, i = 0, length = object.length, isObj = length === undefined || jQuery.isFunction( object ); if ( args ) { if ( isObj ) { for ( name in object ) { if ( callback.apply( object[ name ], args ) === false ) { break; } } } else { for ( ; i < length; ) { if ( callback.apply( object[ i++ ], args ) === false ) { break; } } } // A special, fast, case for the most common use of each } else { if ( isObj ) { for ( name in object ) { if ( callback.call( object[ name ], name, object[ name ] ) === false ) { break; } } } else { for ( ; i < length; ) { if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { break; } } } } return object; }; 懒惰初始化模式 延迟初始化是一种设计模式,它允许我们推迟昂贵的过程,直到需要他们的第一个实例。这样的一个例子是.ready()在jQuery函数只有一旦DOM是准备执行的回调。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 $( document ).ready( function () { // The ajax request won't attempt to execute until // the DOM is ready var jqxhr = $.ajax({ url: "http://domain.com/api/", data: "display=latest&order=ascending" }) .done( function( data ) ){ $(".status").html( "content loaded" ); console.log( "Data output:" + data ); }); }); jQuery.fn.ready()由供电jQuery.bindReady(),如下图所示: ? 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 bindReady: function() { if ( readyList ) { return; } readyList = jQuery.Callbacks( "once memory" ); // Catch cases where $(document).ready() is called after the // browser event has already occurred. if ( document.readyState === "complete" ) { // Handle it asynchronously to allow scripts the opportunity to delay ready return setTimeout( jQuery.ready, 1 ); } // Mozilla, Opera and webkit support this event 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 ) { // ensure firing before onload, // maybe late but safe also for iframes document.attachEvent( "onreadystatechange", DOMContentLoaded ); // A fallback to window.onload, that will always work window.attachEvent( "onload", jQuery.ready ); // If IE and not a frame // continually check to see if the document is ready var toplevel = false; try { toplevel = window.frameElement == null; } catch(e) {} if ( document.documentElement.doScroll && toplevel ) { doScrollCheck(); } } }, 而在jQuery的核心不能直接使用,一些开发商也可能是熟悉通过插件,如惰性加载的概念这个。 惰性加载是有效相同延迟初始化并且是一种技术,其中一个页上的附加数据在需要时被加载(例如,当用户已经滚动到的页面的末尾)。近年来这种格局已经变得相当突出,可目前同时在Twitter和Facebook的用户界面上找到。 代理模式 有些时候,有必要对我们能够控制的对象的背后接入和上下文而这正是代理模式可能是有用的。 它可以帮助我们控制在一个昂贵的对象应该被实例化,提供了先进的方式来引用的对象或修改对象的功能在具体情况下的特定方式。 在jQuery的核心,一个jQuery.proxy()方法存在哪些作为输入接受一个函数,并返回一个新的,这将始终有一个特定的上下文。这确保了的值this的函数中是我们期望的值。 在那里,这是非常有用的一个例子是,当我们在一个范围内利用定时器click事件处理程序。试想一下,我们有以下处理程序添加任何计时器之前: ? 1 2 3 4 $( "button" ).on( "click", function () { // Within this function, "this" refers to the element that was clicked $( this ).addClass( "active" ); }); 如果我们希望在之前添加一个硬盘延迟active添加类,我们可以使用setTimeout()来实现这一目标。不幸的是,一个小问题,这个解决方案:无论功能传递给setTimeout()将有不同的值,this该函数内。这反而参考window对象,这是不是我们的愿望。 ? 1 2 3 4 五 6 7 $( "button" ).on( "click", function () { setTimeout(function () { // "this" doesn't refer to our element! // It refers to window $( this ).addClass( "active" ); }); }); 要解决这个问题,我们可以使用jQuery.proxy()来实现一个类型的代理模式。通过与我们想分配给功能和价值调用它this它实际上将返回保留我们正确的上下文中的愿望值的函数。这里是如何做到这一点看看: ? 1 2 3 4 五 6 7 8 9 10 $( "button" ).on( "click", function () { setTimeout( $.proxy( function () { // "this" now refers to our element as we wanted $( this ).addClass( "active" ); }, this), 500); // the last "this" we're passing tells $.proxy() that our DOM element // is the value we want "this" to refer to. }); jQuery的实施jQuery.proxy()可以发现如下: ? 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 // Bind a function to a context, optionally partially applying any // arguments. proxy: function( fn, context ) { if ( typeof context === "string" ) { var tmp = fn[ context ]; context = fn; fn = tmp; } // Quick check to determine if target is callable, in the spec // this throws a TypeError, but we will just return undefined. if ( !jQuery.isFunction( fn ) ) { return undefined; } // Simulated bind var args = slice.call( arguments, 2 ), proxy = function() { return fn.apply( context, args.concat( slice.call( arguments ) ) ); }; // Set the guid of unique handler to the same of original handler, so it can be removed proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; return proxy; } Builder模式 当与DOM的工作中,我们经常要动态地构造新的元素 - 这个过程可以在复杂性取决于最终的标记增加,属性,我们希望我们的构建元素包含属性。 复杂的元素需要被定义的时候特别小心,尤其是如果我们想要的灵活性,无论是字面定义我们的要素最终的标记(这会导致混乱),或采取更可读的面向对象的路由,而不是。其建设我们复杂的DOM对象了一种机制,是独立于对象本身给了我们这样的灵活性,这正是建造模式提供。 助洗剂允许我们通过仅指定对象的类型和内容,从创建或明确表示对象的过程中屏蔽我们构造复杂的对象。 jQuery的美元符号允许我们只是做这,因为它提供了许多不同手段,动态建立新的jQuery(和DOM)对象,由完整的标记或者传递一个元素,部分标记和内容或使用jQuery建设: ? 1 2 3 4 五 6 7 8 9 $( '<div class="foo">bar</div>' ); $( '<p id="test">foo <em>bar</em></p>').appendTo("body"); var newParagraph = $( "<p />" ).text( "Hello world" ); $( "<input />" ) .attr({ "type": "text", "id":"sample"}) .appendTo("#container"); 下面是jQuery的核心内部的一个片段jQuery.prototype方法,从标记传递到了jQuery对象的构建有助于jQuery()选择。不管是否document.createElement被用来建立新的元件,对元件的引用(发现或创建)被注入到返回的对象,以便进一步的方法,例如.attr(),可在其上之后容易使用。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // HANDLE: $(html) -> $(array) if ( match[1] ) { context = context instanceof jQuery ? context[0] : context; doc = ( context ? context.ownerDocument || context : document ); // If a single string is passed in and it's a single tag // just do a createElement and skip the rest ret = rsingleTag.exec( selector ); if ( ret ) { if ( jQuery.isPlainObject( context ) ) { selector = [ document.createElement( ret[1] ) ]; jQuery.fn.attr.call( selector, context, true ); } else { selector = [ doc.createElement( ret[1] ) ]; } } else { ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; } return jQuery.merge( this, selector ); jQuery插件设计模式 jQuery插件开发已经发展了近几年。我们不再只有一个方式来写插件,但许多。在现实中,某些插件的设计模式可能工作比别人一个特定的问题或组件更好。 有些开发者可能希望使用jQuery UI的小部件厂 ; 它非常适合复杂的,灵活的用户界面组件。有些人可能不。 有些人可能喜欢构建他们的插件更像模块(类似于模块模式),或使用更现代的模块格式如AMD。 有些人可能会希望自己的插件,利用原型继承的力量。其他人可能希望使用自定义的事件或发布/订阅插件从传达给他们的应用程序的其余部分。等等。 我开始注意到了一些努力创造一种尺寸适合所有的jQuery插件样板后去想插件模式。虽然这样的样板在理论上是一个伟大的想法,但现实是,我们很少写插件在一个固定的方式,使用单一模式的所有的时间。 让我们假设,我们已经尽了手在某些时候写我们自己的jQuery插件,我们很舒适的放在一起一些作品。它的功能。它做什么,它需要做的,但也许我们觉得它可以更好的构建。也许这可以更灵活或可以被设计为解决更多的开发者通常遇到的各种问题。如果这听起来很熟悉,那么你可能会发现本章很有用。在这里面,我们要探讨一些已经在野外其他开发商行之有效的jQuery插件模式。 注:本章是针对中级到高级开发者,但我们将简要回顾一些jQuery插件基本面开始。 如果你不觉得挺为此做好准备,只是还没有,我很高兴为您推荐官方的jQuery 插件/创作指导,本Alman的插件风格指南和雷米夏普的“ 一个写得不好的jQuery插件的迹象。”作为阅读材料之前,在开始本节。 模式 jQuery插件有几个具体的规则,这是原因对他们是如何在整个社会实现了难以置信的多样性之一。在最基本的层面上,我们可以简单地通过添加新的功能属性jQuery的编写插件jQuery.fn对象,如下所示: ? 1 2 3 $.fn.myPluginName = function () { // our plugin logic }; 这是伟大的紧凑,但下面会是一个更好的基础建立在: ? 1 2 3 4 五 (function( $ ){ $.fn.myPluginName = function () { // our plugin logic }; })( jQuery ); 在这里,我们包裹我们的插件逻辑匿名函数。为了确保我们使用的$是一个速记创建jQuery和其他JavaScript库之间没有冲突的迹象,我们只是把它传递给这个封闭,它映射到美元符号。这保证了它不能由它执行的范围之外的任何的影响。 写这个模式的另一种方法是使用jQuery.extend(),这使我们能够在一次定义多种功能和有时更有意义的语义: ? 1 2 3 4 五 6 7 (function( $ ){ $.extend($.fn, { myplugin: function(){ // your plugin logic } }); })( jQuery ); 现在我们已经回顾了一些jQuery插件的基本面,但很多还可以做得更多,以进一步利用这个。一个轻量级的开始是我们将要探讨的第一个完整的插件设计模式,它涵盖了我们可以使用基本的日常插件的一些最佳实践发展,考虑到实用价值的常见的问题。 注意 虽然大多数低于模式加以说明,我建议你阅读过的代码中的注释,因为他们将提供更深入地了解为什么某些应用的最佳实践。 我还要提到的是,这一切决不可能没有以前的工作,投入和jQuery社区的其他成员的意见。我列出了他们内嵌每个模式,使人们可以,如果有兴趣对他们个人的工作读了。 “轻量级开始'模式 让我们开始我们的插件模式的深入了解的东西基本遵循最佳实践(包括那些jQuery插件创作指南)。这种模式非常适合开发谁或者是新插件的开发或谁只是想实现简单的东西(如工具插件)。轻量级开始使用下列内容: 常见的最佳做法,如分号放在函数调用之前(我们为什么在评论下方穿过) window, document, undefined 作为参数传入。 一个基本的默认对象。 相关的初始创建和劳动要素的分配逻辑一个简单的插件构造函数。 扩展为默认的选项。 周围的构造函数,这有助于避免诸如多实例化问题的轻量级封装。 坚持为最大化可读性jQuery的核心风格指南。 ? 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 /*! * jQuery lightweight plugin boilerplate * Original author: @ajpiano * Further changes, comments: @addyosmani * Licensed under the MIT license */ // the semi-colon before the function invocation is a safety // net against concatenated scripts and/or other plugins // that are not closed properly. ;(function ( $, window, document, undefined ) { // undefined is used here as the undefined global // variable in ECMAScript 3 and is mutable (i.e. it can // be changed by someone else). undefined isn't really // being passed in so we can ensure that its value is // truly undefined. In ES5, undefined can no longer be // modified. // window and document are passed through as local // variables rather than as globals, because this (slightly) // quickens the resolution process and can be more // efficiently minified (especially when both are // regularly referenced in our plugin). // Create the defaults once var pluginName = "defaultPluginName", defaults = { propertyName: "value" }; // The actual plugin constructor function Plugin( element, options ) { this.element = element; // jQuery has an extend method that merges the // contents of two or more objects, storing the // result in the first object. The first object // is generally empty because we don't want to alter // the default options for future instances of the plugin this.options = $.extend( {}, defaults, options) ; this._defaults = defaults; this._name = pluginName; this.init(); } Plugin.prototype.init = function () { // Place initialization logic here // We already have access to the DOM element and // the options via the instance, e.g. this.element // and this.options }; // A really lightweight plugin wrapper around the constructor, // preventing against multiple instantiations $.fn[pluginName] = function ( options ) { return this.each(function () { if ( !$.data(this, "plugin_" + pluginName )) { $.data( this, "plugin_" + pluginName, new Plugin( this, options )); } }); } })( jQuery, window, document ); 用法: ? 1 2 3 $("#elem").defaultPluginName({ propertyName: "a custom value" }); 深入阅读 插件/创作,jQuery的 “ 一个写得不好的jQuery插件的迹象,”雷米夏普 “ 如何创建自己的jQuery插件,”以利亚庄园 “ 风格的jQuery插件和为什么重要,”本·阿尔蒙 “ 创建你的第一个jQuery插件,第2部分,”安德鲁Wirick “完成”窗口小部件工厂模式 而jQuery插件创作指南是一个伟大的介绍插件的开发,它并不能帮助掩盖走,我们必须处理定期就共同插件水暖任务。 jQuery的UI部件厂是这个问题,帮助我们建立一个基于面向对象原则复杂的,有状态的插件的解决方案。它还简化了通信与我们的插件的情况下,混淆了一些基本的插件工作时,我们将不得不代码重复的任务。 状态插件帮助我们跟踪其当前状态,也让我们已经初始化后更改插件的性能。 一个关于Widget Factory中的伟大的事情是,大多数的jQuery UI库的实际使用它作为其组成部分的魅力。这意味着,如果我们正在寻找在超出了此模式的结构进一步的指导,我们不会把眼光局限在GitHub的jQuery用户界面库(https://github.com/jquery/jquery-ui)。 这个jQuery UI部件工厂模式涵盖​​了几乎所有受支持的默认出厂方法,包括触发事件。按照过去的模式,注释包含在所有的使用和进一步指导在内部注释给出的方法。 ? 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 /*! * jQuery UI Widget-factory plugin boilerplate (for 1.8/9+) * Author: @addyosmani * Further changes: @peolanha * Licensed under the MIT license */ ;(function ( $, window, document, undefined ) { // define our widget under a namespace of your choice // with additional parameters e.g. // $.widget( "namespace.widgetname", (optional) - an // existing widget prototype to inherit from, an object // literal to become the widget's prototype ); $.widget( "namespace.widgetname", { //Options to be used as defaults options: { someValue: null }, //Setup widget (e.g. element creation, apply theming //, bind events etc.) _create: function () { // _create will automatically run the first time // this widget is called. Put the initial widget // setup code here, then we can access the element // on which the widget was called via this.element. // The options defined above can be accessed // via this.options this.element.addStuff(); }, // Destroy an instantiated plugin and clean up // modifications the widget has made to the DOM destroy: function () { // this.element.removeStuff(); // For UI 1.8, destroy must be invoked from the // base widget $.Widget.prototype.destroy.call( this ); // For UI 1.9, define _destroy instead and don't // worry about // calling the base widget }, methodB: function ( event ) { //_trigger dispatches callbacks the plugin user // can subscribe to // signature: _trigger( "callbackName", [eventObject], // [uiObject] ) // e.g. this._trigger( "hover", e /*where e.type == // "mouseenter"*/, { hovered: $(e.target)}); this._trigger( "methodA", event, { key: value }); }, methodA: function ( event ) { this._trigger( "dataChanged", event, { key: value }); }, // Respond to any changes the user makes to the // option method _setOption: function ( key, value ) { switch ( key ) { case "someValue": // this.options.someValue = doSomethingWith( value ); break; default: // this.options[ key ] = value; break; } // For UI 1.8, _setOption must be manually invoked // from the base widget $.Widget.prototype._setOption.apply( this, arguments ); // For UI 1.9 the _super method can be used instead // this._super( "_setOption", key, value ); } }); })( jQuery, window, document ); 用法: ? 1 2 3 4 五 var collection = $("#elem").widgetName({ foo: false }); collection.widgetName("methodB"); 深入阅读 jQuery的UI部件厂 “ 介绍状态插件和小部件工厂,”道格Neiner “ 窗口小部件工厂 ”(解释),斯科特·冈萨雷斯 “ 了解的jQuery UI部件:一个教程 ”,在0300黑客 嵌套命名空间插件模式 正如我们在书中覆盖以前,我们的命名空间的代码是为了避免在全局命名空间的其他对象和变量冲突的方式。因为我们想在页面上的另一个脚本使用相同的变量或插件的名称作为我们的事件打破捍卫我们的插件他们是非常重要的。由于全局命名空间的好公民,我们也必须做到最好不要阻止其他开发商从脚本,因为同样的问题执行。 JavaScript并没有真正有内置的命名空间的支持,其他语言做的,但它确实具有可以被用来实现类似的效果的对象。采用顶层对象作为我们命名空间的名称,我们可以很容易地检查具有相同名称的页面上的其他对象的存在。如果这样的对象不存在,那么,我们将其定义; 如果它确实存在的话,我们只需用我们的插件扩展。 对象(或者说,对象文字)可被用于创建嵌套的命名空间,如namespace.subnamespace.pluginName等。但是,为了简单起见,下面的命名空间样板应该告诉我们,我们需要开始使用这些概念的一切。 ? 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 /*! * jQuery namespaced "Starter" plugin boilerplate * Author: @dougneiner * Further changes: @addyosmani * Licensed under the MIT license */ ;(function ( $ ) { if (!$.myNamespace) { $.myNamespace = {}; }; $.myNamespace.myPluginName = function ( el, myFunctionParam, options ) { // To avoid scope issues, use "base" instead of "this" // to reference this class from internal events and functions. var base = this; // Access to jQuery and DOM versions of element base.$el = $( el ); base.el = el; // Add a reverse reference to the DOM object base.$el.data( "myNamespace.myPluginName", base ); base.init = function () { base.myFunctionParam = myFunctionParam; base.options = $.extend({}, $.myNamespace.myPluginName.defaultOptions, options); // Put our initialization code here }; // Sample Function, Uncomment to use // base.functionName = function( parameters ){ // // }; // Run initializer base.init(); }; $.myNamespace.myPluginName.defaultOptions = { myDefaultValue: "" }; $.fn.mynamespace_myPluginName = function ( myFunctionParam, options ) { return this.each(function () { (new $.myNamespace.myPluginName( this, myFunctionParam, options )); }); }; })( jQuery ); 用法: ? 1 2 3 $("#elem").mynamespace_myPluginName({ myDefaultValue: "foobar" }); 深入阅读 “ 命名空间在JavaScript中,”安格斯克罗尔 “ 用你的$ .fn jQuery的命名空间,”瑞恩佛罗伦萨 “ JavaScript的命名空间,”彼得·米肖 “ 模块和命名空间在JavaScript中,”阿克塞尔Rauschmayer先生 自定义事件插件模式(与小部件厂) 在JavaScript的设计模式书的部分,我们讨论了观察者模式,后来又到覆盖jQuery的自定义事件,它提供了实现发布/订阅类似的解决方案的支持。这同一个模式可以写入jQuery插件时使用。 这里的基本想法是,当一些有趣的事情在我们的应用程序出现在页面中的对象可以发布事件通知。其他的对象,则认购(或收听)这些事件,并作出相应的反应。这导致我们的应用是显著更解耦的逻辑,因为每个对象不再需要直接与彼此通信。 在下面的jQuery UI部件工厂模式,我们将实现一个基本的自定义的基于事件的发布/订阅系统,让我们的插件,从我们的应用程序的其他部分,这将是负责发布这些订阅事件通知。 ? 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 /*! * jQuery custom-events plugin boilerplate * Author: DevPatch * Further changes: @addyosmani * Licensed under the MIT license */ // In this pattern, we use jQuery's custom events to add // pub/sub (publish/subscribe) capabilities to widgets. // Each widget would publish certain events and subscribe // to others. This approach effectively helps to decouple // the widgets and enables them to function independently. ;(function ( $, window, document, undefined ) { $.widget( "ao.eventStatus", { options: { }, _create: function() { var self = this; //self.element.addClass( "my-widget" ); //subscribe to "myEventStart" self.element.on( "myEventStart", function( e ) { console.log( "event start" ); }); //subscribe to "myEventEnd" self.element.on( "myEventEnd", function( e ) { console.log( "event end" ); }); //unsubscribe to "myEventStart" //self.element.off( "myEventStart", function(e){ ///console.log( "unsubscribed to this event" ); //}); }, destroy: function(){ $.Widget.prototype.destroy.apply( this, arguments ); }, }); })( jQuery, window, document ); // Publishing event notifications // $( ".my-widget" ).trigger( "myEventStart"); // $( ".my-widget" ).trigger( "myEventEnd" ); 用法: ? 1 2 3 var el = $( "#elem" ); el.eventStatus(); el.eventStatus().trigger( "myEventStart" ); 深入阅读 “ jQuery UI的小部件之间的通信,”本杰明Sternthal 原型继承与DOM到对象Bridge模式 如前所述,在JavaScript中覆盖,我们没有的,我们会发现在等经典编程语言类的传统观念,但我们确实有原型继承。与原型继承,对象从另一个对象继承。我们可以将这个概念jQuery插件开发。 Yepnope.js作者亚历克斯·塞克斯顿和jQuery团队成员斯科特·冈萨雷斯已经看过这个主题的细节。总之,他们发现,有组织模块化开发,明确地分离,定义了从插件生成过程的插件本身可以是有益的逻辑对象。 这样做的好处是,测试我们的插件代码变得显著容易,我们也能调整的东西幕后工作的方式,而不改变所使用我们实现任何对象的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 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 /*! * jQuery prototypal inheritance plugin boilerplate * Author: Alex Sexton, Scott Gonzalez * Further changes: @addyosmani * Licensed under the MIT license */ // myObject - an object representing a concept we wish to model // (e.g. a car) var myObject = { init: function( options, elem ) { // Mix in the passed-in options with the default options this.options = $.extend( {}, this.options, options ); // Save the element reference, both as a jQuery // reference and a normal reference this.elem = elem; this.$elem = $( elem ); // Build the DOM's initial structure this._build(); // return this so that we can chain and use the bridge with less code. return this; }, options: { name: "No name" }, _build: function(){ //this.$elem.html( "<h1>"+this.options.name+"</h1>" ); }, myMethod: function( msg ){ // We have direct access to the associated and cached // jQuery element // this.$elem.append( "<p>"+msg+"</p>" ); } }; // Object.create support test, and fallback for browsers without it if ( typeof Object.create !== "function" ) { Object.create = function (o) { function F() {} F.prototype = o; return new F(); }; } // Create a plugin based on a defined object $.plugin = function( name, object ) { $.fn[name] = function( options ) { return this.each(function() { if ( ! $.data( this, name ) ) { $.data( this, name, Object.create( object ).init( options, this ) ); } }); }; }; 用法: ? 1 2 3 4 五 6 $.plugin( "myobj", myObject ); $("#elem").myobj( {name: "John"} ); var collection = $( "#elem" ).data( "myobj" ); collection.myMethod( "I am a method"); 深入阅读 “ 使用继承模式组织大型jQuery的应用程序,”亚历克斯·塞克斯顿 “ 如何管理大型应用程序使用jQuery或任何 ”(进一步讨论),亚历克斯·塞克斯顿 “ 极品原型继承的实际例子,”Neeraj辛格 “ 原型继承在JavaScript中,”道格拉斯·克罗克福德 jQuery UI的小部件厂桥模式 如果你喜欢基础上产生的最后设计模式的对象插件的想法,那么你可能有兴趣在叫jQuery用户界面的Widget厂找到了一个方法$.widget.bridge。 这个桥基本上用作所创建使用JavaScript对象之间的中间层$.widget和jQuery的核心API,提供了更多的内置在溶液中实现基于对象的插件定义。实际上,我们能够创建使用自定义构造函数状态插件。 此外,$.widget.bridge提供了访问的一些其他功能,包括以下内容: 公共和私有方法的处理正如人们所期望的古典OOP(即public方法被暴露,而私有方法的调用是不可能的)。 自动保护,防止多个初始化。 自动生成一个传递的对象的实例,而选择的内部在其中的存储$.data缓存。 选项​​可以更改初始化后。 有关如何使用这种模式的更多信息,请参见下面的在线评论: ? 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 /*! * jQuery UI Widget factory "bridge" plugin boilerplate * Author: @erichynds * Further changes, additional comments: @addyosmani * Licensed under the MIT license */ // a "widgetName" object constructor // required: this must accept two arguments, // options: an object of configuration options // element: the DOM element the instance was created on var widgetName = function( options, element ){ this.name = "myWidgetName"; this.options = options; this.element = element; this._init(); } // the "widgetName" prototype widgetName.prototype = { // _create will automatically run the first time this // widget is called _create: function(){ // creation code }, // required: initialization logic for the plugin goes into _init // This fires when our instance is first created and when // attempting to initialize the widget again (by the bridge) // after it has already been initialized. _init: function(){ // init code }, // required: objects to be used with the bridge must contain an // "option". Post-initialization, the logic for changing options // goes here. option: function( key, value ){ // optional: get/change options post initialization // ignore if you don't require them. // signature: $("#foo").bar({ cool:false }); if( $.isPlainObject( key ) ){ this.options = $.extend( true, this.options, key ); // signature: $( "#foo" ).option( "cool" ); - getter } else if ( key && typeof value === "undefined" ){ return this.options[ key ]; // signature: $( "#foo" ).bar("option", "baz", false ); } else { this.options[ key ] = value; } // required: option must return the current instance. // When re-initializing an instance on elements, option // is called first and is then chained to the _init method. return this; }, // notice no underscore is used for public methods publicFunction: function(){ console.log( "public function" ); }, // underscores are used for private methods _privateFunction: function(){ console.log( "private function" ); } }; 用法: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 // connect the widget obj to jQuery's API under the "foo" namespace $.widget.bridge( "foo", widgetName ); // create an instance of the widget for use var instance = $( "#foo" ).foo({ baz: true }); // our widget instance exists in the elem's data // Outputs: #elem console.log(instance.data( "foo" ).element); // bridge allows us to call public methods // Outputs: "public method" instance.foo("publicFunction"); // bridge prevents calls to internal methods instance.foo("_privateFunction"); 深入阅读 “ 使用$ .widget.bridge Widget的工厂之外,”埃里克·海因兹 jQuery Mobile的小工具与小部件厂 jQuery Mobile的是一个jQuery项目框架,鼓励的,无论在流行的移动设备和平台,并在桌面上工作普遍存在的Web应用程序的设计。而不是写为每个设备或OS独特的应用,我们简单地写代码一次,它应理想的时刻上的许多A,B和C级的浏览器的运行在那里。 背后jQuery Mobile的基本面,也可以适用于插件和小工具的开发。 是什么在接下来的模式有趣的是,虽然有写“手机” - 优化部件很小,细微的差别,那些熟悉使用jQuery UI控件工厂模式从早期应该能够掌握这个在旁边,没有时间。 下面的移动优化的部件有一些比我们之前看到的标准的UI部件的模式有趣的差异: $.mobile.widget作为现有部件的原型可以继承被引用。对于标准的小工具,通过任何这样的原型是不必要的基本发展,但使用这个jQuery移动特定窗口部件原型提供了进一步的“选项”格式化内部访问。 在_create(),引导提供官方jQuery Mobile的小部件如何处理元素的选择,选择了基于角色的方法,更好地满足JQM加价。这是不是在所有地说,不建议标准选择,只是,这种做法可能会更有意义给jQuery Mobile的页面结构。 还提供了评论形式的准则申请对我们的插件的方法pagecreate,以及选择通过数据的角色和数据属性插件应用程序。 ? 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 /*! * (jQuery mobile) jQuery UI Widget-factory plugin boilerplate (for 1.8/9+) * Author: @scottjehl * Further changes: @addyosmani * Licensed under the MIT license */ ;(function ( $, window, document, undefined ) { // define a widget under a namespace of our choice // here "mobile" has been used in the first argument $.widget( "mobile.widgetName", $.mobile.widget, { // Options to be used as defaults options: { foo: true, bar: false }, _create: function() { // _create will automatically run the first time this // widget is called. Put the initial widget set-up code // here, then we can access the element on which // the widget was called via this.element // The options defined above can be accessed via // this.options // var m = this.element, // p = m.parents( ":jqmData(role="page")" ), // c = p.find( ":jqmData(role="content")" ) }, // Private methods/props start with underscores _dosomething: function(){ ... }, // Public methods like these below can can be called // externally: // $("#myelem").foo( "enable", arguments ); enable: function() { ... }, // Destroy an instantiated plugin and clean up modifications // the widget has made to the DOM destroy: function () { // this.element.removeStuff(); // For UI 1.8, destroy must be invoked from the // base widget $.Widget.prototype.destroy.call( this ); // For UI 1.9, define _destroy instead and don't // worry about calling the base widget }, methodB: function ( event ) { //_trigger dispatches callbacks the plugin user can // subscribe to // signature: _trigger( "callbackName", [eventObject], // [uiObject] ) // e.g. this._trigger( "hover", e /*where e.type == // "mouseenter"*/, { hovered: $(e.target)}); this._trigger( "methodA", event, { key: value }); }, methodA: function ( event ) { this._trigger( "dataChanged", event, { key: value }); }, // Respond to any changes the user makes to the option method _setOption: function ( key, value ) { switch ( key ) { case "someValue": // this.options.someValue = doSomethingWith( value ); break; default: // this.options[ key ] = value; break; } // For UI 1.8, _setOption must be manually invoked from // the base widget $.Widget.prototype._setOption.apply(this, arguments); // For UI 1.9 the _super method can be used instead // this._super( "_setOption", key, value ); } }); })( jQuery, window, document ); 用法: ? 1 2 3 4 五 var instance = $( "#foo" ).widgetName({ foo: false }); instance.widgetName( "methodB" ); 我们还可以自初始化这个小部件每当创建于jQuery Mobile的新的一页。jQuery Mobile的的网页插件调度创建时,jQuery Mobile的页面(通过发现事件data-role="page"属性)首先被初始化。我们可以侦听事件(称为“pagecreate”),每当创建一个新的页面会自动运行我们的插件。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 $(document).on("pagecreate", function ( e ) { // In here, e.target refers to the page that was created // (it's the target of the pagecreate event) // So, we can simply find elements on this page that match a // selector of our choosing, and call our plugin on them. // Here's how we'd call our "foo" plugin on any element with a // data-role attribute of "foo": $(e.target).find( "[data-role="foo"]" ).foo( options ); // Or, better yet, let's write the selector accounting for the configurable // data-attribute namespace $( e.target ).find( ":jqmData(role="foo")" ).foo( options ); }); 现在,我们可以简单地引用包含我们的控件和脚本pagecreate在运行jQuery Mobile的网站页面绑定,它会自动运行像任何其他jQuery Mobile的插件。 RequireJS和jQuery的UI部件厂 正如我们覆盖的部分现代模块设计模式,RequireJS是AMD兼容的脚本加载器,它提供了可管理的封装内的模块应用逻辑干净的解决方案。 它能够加载正确的顺序模块(通过其订单插件),简化了通过结合其优异的r.js优化脚本的过程,并提供了手段对每个模块为基础定义动态依赖。 在下面的样板模式,我们证明了AMD(因此RequireJS)兼容的jQuery UI部件如何可以定义为执行以下操作: 允许插件模块依赖的定义,建立在前面介绍以前的jQuery UI的小部件工厂模式之上。 演示一种方法在HTML模板资产通过创建模板控件(使用Underscore.js微模板)。 包括调整,我们可以让我们的小部件模块,如果我们希望在以后通过它传递给RequireJS优化的快速提示。 ? 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 /*! * jQuery UI Widget + RequireJS module boilerplate (for 1.8/9+) * Authors: @jrburke, @addyosmani * Licensed under the MIT license */ // Note from James: // // This assumes we are using the RequireJS+jQuery file, and // that the following files are all in the same directory: // // - require-jquery.js // - jquery-ui.custom.min.js (custom jQuery UI build with widget factory) // - templates/ // - asset.html // - ao.myWidget.js // Then we can construct the widget as follows: // ao.myWidget.js file: define( "ao.myWidget", ["jquery", "text!templates/asset.html", "underscore", "jquery-ui.custom.min"], function ( $, assetHtml, _ ) { // define our widget under a namespace of our choice // "ao" is used here as a demonstration $.widget( "ao.myWidget", { // Options to be used as defaults options: {}, // Set up widget (e.g. create element, apply theming, // bind events, etc.) _create: function () { // _create will automatically run the first time // this widget is called. Put the initial widget // set-up code here, then we can access the element // on which the widget was called via this.element. // The options defined above can be accessed via // this.options // this.element.addStuff(); // this.element.addStuff(); // We can then use Underscore templating with // with the assetHtml that has been pulled in // var template = _.template( assetHtml ); // this.content.append( template({}) ); }, // Destroy an instantiated plugin and clean up modifications // that the widget has made to the DOM destroy: function () { // this.element.removeStuff(); // For UI 1.8, destroy must be invoked from the base // widget $.Widget.prototype.destroy.call( this ); // For UI 1.9, define _destroy instead and don't worry // about calling the base widget }, methodB: function ( event ) { // _trigger dispatches callbacks the plugin user can // subscribe to // signature: _trigger( "callbackName", [eventObject], // [uiObject] ) this._trigger( "methodA", event, { key: value }); }, methodA: function ( event ) { this._trigger("dataChanged", event, { key: value }); }, // Respond to any changes the user makes to the option method _setOption: function ( key, value ) { switch (key) { case "someValue": // this.options.someValue = doSomethingWith( value ); break; default: // this.options[ key ] = value; break; } // For UI 1.8, _setOption must be manually invoked from // the base widget $.Widget.prototype._setOption.apply( this, arguments ); // For UI 1.9 the _super method can be used instead // this._super( "_setOption", key, value ); } }); }); 用法: index.html的: ? 1 <script data-main="scripts/main" src="http://requirejs.org/docs/release/1.0.1/minified/require.js"></script> main.js ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 require({ paths: { "jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min", "jqueryui": "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.min", "boilerplate": "../patterns/jquery.widget-factory.requirejs.boilerplate" } }, ["require", "jquery", "jqueryui", "boilerplate"], function (req, $) { $(function () { var instance = $("#elem").myWidget(); instance.myWidget("methodB"); }); }); 深入阅读 “ 快速模块化的代码使用jQuery和RequireJS,”詹姆斯·伯克 “ jQuery的最好的朋友,”亚历克斯·塞克斯顿 “ 管理依赖随着RequireJS,”鲁斯兰Matveev 在全球范围内和每个呼叫可覆盖选项(最好的选择模式) 对于我们的下一个模式,我们将看看在配置选项和默认设置插件的最佳手段。方式我们大多数人可能熟悉定义插件选项是通过对象文本默认的$.extend(),在我们的基本的插件样板展示。 但是,如果我们与许多定制选项的插件,我们希望用户能够覆盖全局的或是每次呼叫的水平工作,那么我们就可以多一点优化结构的东西。 相反,参照一个Options对象明确的插件命名空间中定义的(例如$fn.pluginName.options)与合并这与通过对在最初调用它的插件,通过任何选项,用户可以选择是插件初始化或压倒一切的过程中传球选择通过插件以外的选项(如下所示)。 ? 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 /*! * jQuery "best options" plugin boilerplate * Author: @cowboy * Further changes: @addyosmani * Licensed under the MIT license */ ;(function ( $, window, document, undefined ) { $.fn.pluginName = function ( options ) { // Here's a best practice for overriding "defaults" // with specified options. Note how, rather than a // regular defaults object being passed as the second // parameter, we instead refer to $.fn.pluginName.options // explicitly, merging it with the options passed directly // to the plugin. This allows us to override options both // globally and on a per-call level. options = $.extend( {}, $.fn.pluginName.options, options ); return this.each(function () { var elem = $(this); }); }; // Globally overriding options // Here are our publicly accessible default plugin options // that are available in case the user doesn't pass in all // of the values expected. The user is given a default // experience but can also override the values as necessary. // e.g. $fn.pluginName.key ="otherval"; $.fn.pluginName.options = { key: "value", myMethod: function ( elem, param ) { } }; })( jQuery, window, document ); 用法: ? 1 2 3 $("#elem").pluginName({ key: "foobar" }); 深入阅读 jQuery的Pluginization和随行要点,本Alman 一个高度可配置的和可变的插件模式 在这个模式中,类似亚历克斯塞克斯顿的原型继承的插件模式,为我们的插件逻辑不嵌套在一个jQuery插件本身。我们不是用一个构造函数和一个对象上它的原型定义的字面定义我们的插件逻辑。然后jQuery的用于插件对象的实际实例。 定制是采取一个新的水平通过采用两个小动作,其中一个我们在以前的模式已经看到: 选项​​可以覆盖全球和每个元素的集合/ 选项可以在定制每个元件通过HTML5数据属性等级(如下所示)。这有利于可以应用到元素的集合,但随即开始定制,而不需要实例化每个元件具有不同的默认值的插件行为。 我们没有看到在野外选择后者过于频繁,但它可以是一个显著清洁的解决方案(只要我们不介意内联的方式)。如果想知道这可能是有用的,想象编写插件拖动一大组元素。我们可以去定制自己的选项,如下所示: ? 1 2 3 4 $( ".item-a" ).draggable( {"defaultPosition":"top-left"} ); $( ".item-b" ).draggable( {"defaultPosition":"bottom-right"} ); $( ".item-c" ).draggable( {"defaultPosition":"bottom-left"} ); //etc 但使用我们的在线模式的做法,以下是可能的: ? 1 $( ".items" ).draggable(); ? 1 2 3 html <li class="item" data-plugin-options="{"defaultPosition":"top-left"}"></div> <li class="item" data-plugin-options="{"defaultPosition":"bottom-left"}"></div> 等等。我们很可能具有用于这些方法中的一个的偏好,但它仅仅是另一种变化值得意识到的。 ? 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 /* * "Highly configurable" mutable plugin boilerplate * Author: @markdalgleish * Further changes, comments: @addyosmani * Licensed under the MIT license */ // Note that with this pattern, as per Alex Sexton's, the plugin logic // hasn't been nested in a jQuery plugin. Instead, we just use // jQuery for its instantiation. ;(function( $, window, document, undefined ){ // our plugin constructor var Plugin = function( elem, options ){ this.elem = elem; this.$elem = $(elem); this.options = options; // This next line takes advantage of HTML5 data attributes // to support customization of the plugin on a per-element // basis. For example, // <div class="item" data-plugin-options="{'message':'Goodbye World!'}"></div> this.metadata = this.$elem.data( "plugin-options" ); }; // the plugin prototype Plugin.prototype = { defaults: { message: "Hello world!" }, init: function() { // Introduce defaults that can be extended either // globally or using an object literal. this.config = $.extend( {}, this.defaults, this.options, this.metadata ); // Sample usage: // Set the message per instance: // $( "#elem" ).plugin( { message: "Goodbye World!"} ); // or // var p = new Plugin( document.getElementById( "elem" ), // { message: "Goodbye World!"}).init() // or, set the global default message: // Plugin.defaults.message = "Goodbye World!" this.sampleMethod(); return this; }, sampleMethod: function() { // e.g. show the currently configured message // console.log(this.config.message); } } Plugin.defaults = Plugin.prototype.defaults; $.fn.plugin = function( options ) { return this.each(function() { new Plugin( this, options ).init(); }); }; // optional: window.Plugin = Plugin; })( jQuery, window, document ); 用法: ? 1 2 3 $("#elem").plugin({ message: "foobar" }); 深入阅读 “ 创建高度可配置的jQuery插件,”马克·达格利什 “ 写作高度可配置的jQuery插件,第2部分,”马克·达格利什 怎样做一个好插件超越模式? 在一天结束的时候,设计模式只是一个方面编写维护的jQuery插件。有考虑一些价值等因素,我想和大家分享我自己的标准来选择第三方插件来解决一些其他问题。我希望这有助于提高你的插件项目的整体质量: 质量 坚持这两方面中的JavaScript和jQuery,你写的最佳实践。正在作出努力,通过皮棉无论jsHint或JSLint的插件代码?是插件最佳写的吗? 代码样式 是否插件遵循一致的代码风格指南,如jQuery的核心风格指南?如果没有,你的代码至少比较干净,可读性? 兼容性 哪个版本的jQuery插件兼容?它已经过测试,最新的jQuery-git的构建或最新的稳定?如果插件是jQuery的1.6之前写的,那么它可能有属性和特性的问题,因为他们接触的方式在该版本中改变。 jQuery的提供改进和机会,为项目的jQuery的新版本,提高对核心函数库提供什么。有了这个来自偶尔破损(主要是在主要版本),因为我们对事情做一个更好的方式移动。我希望看到在必要时或插件作者更新他们的代码,至少,在新版本测试自己的插件,以确保一切正常。 可靠性 该插件都应该有它自己的一套单元测试。不仅这些证明它实际上起到预期的,但它们也可以提高设计而不破坏它的最终用户。我认为单元测试任何严重的jQuery插件,是为生产环境中必不可少的,而且他们并不难写。对于一个优秀的引导与QUnit自动JavaScript的测试,你可能有兴趣在“ 自动化的JavaScript测试使用QUnit,”由乔恩Zaefferer。 性能 如果插件需要执行需要大量处理或DOM的大量操作任务,应该遵循最佳做法的基准,以帮助减少这一点。使用jsPerf.com测试代码到一个段),以及它如何执行在不同的浏览器和b)发现什么,如果有的话,可能会进一步优化。 文档 如果强度是其他开发者使用插件,确保它的有据可查的。文件的API和插件是如何被使用。有什么方法和选项做插件的支持?是否有用户需要知道的任何陷阱?如果用户无法弄清楚如何使用插件,他们很可能会寻找一个替代。这是有很大帮助的评论你的插件代码也。这是迄今为止最好的礼物,你可以提供其他的开发者。如果有人认为他们可以浏览您的代码基础不够好,使用或改进它,那么你已经做得很好。 维修的可能性 当发布一个插件,估计多少时间,可能需要维护和支持。我们都喜欢与社区共享我们的插件,但是需要设置的人来回答问题,地址问题和持续改进能力的预期。这可以简单地通过说明该项目的意图维护支持,在前期完成README文件。 结论 在本章中,我们探索了一些节省时间的设计模式,并可以用来改善jQuery插件如何编写最佳实践。一些更适合某些使用情况比别人,但我希望整个这些模式是非常有用的。 记住,在选择的模式时,它是实际的是重要的。不要使用插件模式只是为了它的缘故,相反,投资时间在了解的基础结构,并建立它如何妥善解决您的问题或适合你试图建立的组件。 命名空间模式 在本节中,我们将探讨的模式在JavaScript的命名空间。命名空间可被认为是代码单元下的唯一标识符的逻辑分组。所述标识符可以以多种名称空间中引用和每个标识符本身可以包含其自身的嵌套(或子)的命名空间的层次结构。 在应用程序的开发,我们聘请了一些重要的原因命名空间。在JavaScript中,他们帮助我们避免碰撞与全局命名空间中的其他对象或变量。它们还用于帮助组织的功能块中的码基,以便它可以更容易地引用和使用是非常有用的。 命名空间任何严重的脚本或应用程序是至关重要的,因为它可从另一个脚本使用页面上的事件打破维护我们的代码是很重要的同一变量或方法的名称我们。随着数量的第三方定期注入的页面,这些天的标记,这可能是一个普遍的问题,我们都需要在我们的事业某一点来解决。作为全球命名空间的乖巧的“公民”,这也是我们必须尽力同样不能阻止其他开发人员的脚本同样的问题,由于执行。 虽然JavaScript不真的有内置的对像其他语言的名称空间的支持,它确实有它可用来达到类似的效果对象和关闭。 命名空间基础 命名空间可以在几乎任何严重的JavaScript应用程序被发现。除非我们用一个简单的代码片段工作,这是我们必须竭尽所能,确保我们正在实施正确的命名空间,因为它不只是简单的回升,这也将避免第三方代码重挫我们自己。我们将在这一节被检查模式是: 单全局变量 前缀命名空间 对象的文字符号 嵌套命名空间 立即调用的函数表达式 命名空间注入 1.单全局变量 在JavaScript的命名空间的一个流行的模式是选择了一个全局变量作为我们的主要参考对象。这方面的一个框架的实施,我们返回具有的功能和属性的对象可以发现如下: ? 1 2 3 4 五 6 7 8 var myApplication = (function () { function(){ //... }, return{ //... } })(); 虽然这适用于某些情况下,跟单全局变量模式的最大挑战是确保没有其他人使用相同的全局变量名,因为我们有在页面中。 2.前缀命名空间 一种解决上述问题,如由提到彼得米修,是使用前缀命名空间。它是在心脏一个简单的概念,但这个想法是我们选择一个唯一的前缀的命名空间,我们希望使用(在本例中,myApplication_),然后定义任何方法,变量或其他物体前缀后如下: ? 1 2 3 4 五 var myApplication_propertyA = {}; var myApplication_propertyB = {}; function myApplication_myMethod(){ //... } 这是从减少存在于全局范围的特定变量的可能性的角度有效,但要记住,一个唯一命名的对象可以有同样的效果。 此之外,与图案的最大的问题是,它可能会导致大量的全局对象的一次我们的应用程序开始增长。还有我们的前缀没有全局命名空间中使用的任何其他开发商相当严重依赖,所以要小心,如果选择使用此。 欲了解更多关于彼得对单一的全局变量格局的看法,看他们的出色岗位http://michaux.ca/articles/javascript-namespacing。 3.对象的文字符号 对象的文字符号(我们也涵盖了本书的模块模式部分)可以被认为包含关键的集合对象:值对用冒号分隔每个对键和值,其中键也可以代表新的名称空间。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 var myApplication = { // As we've seen, we can easily define functionality for // this object literal.. getInfo:function(){ //... }, // but we can also populate it to support // further object namespaces containing anything // anything we wish: models: {}, views: { pages: {} }, collections: {} }; 你也可以选择直接添加属性命名空间: ? 1 2 3 4 五 6 7 8 9 10 11 12 myApplication.foo = function(){ return "bar"; } myApplication.utils = { toString:function(){ //... }, export: function(){ //... } } 对象文本没有污染全局命名空间的优势,但协助组织代码和参数逻辑。他们是真正有益的,如果我们希望创建可扩展,以支持深度嵌套轻松可读的结构。与简单的全局变量,对象文本往往也考虑到测试一个变量是否存在由同一名称,因此发生碰撞的几率是显著减少。 在接下来的示例中,我们展示了许多不同的方式,使我们可以检查是否一个变量(对象或插件命名空间)已经存在,它定义,如果它不。 ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 // This doesn't check for existence of "myApplication" in // the global namespace. Bad practice as we can easily // clobber an existing variable/namespace with the same name var myApplication = {}; // The following options *do* check for variable/namespace existence. // If already defined, we use that instance, otherwise we assign a new // object literal to myApplication. // // Option 1: var myApplication = myApplication || {}; // Option 2: if( !MyApplication ){ MyApplication = {} }; // Option 3: window.myApplication || ( window.myApplication = {} ); // Option 4: var myApplication = $.fn.myApplication = function() {}; // Option 5: var myApplication = myApplication === undefined ? {} : myApplication; 我们会经常看到开发商选择了选项1或选项2 - 他们都是直截了当的理解和在他们的归宿方面等价的。 方案3假设我们在全局命名空间正在努力,但它也可以写为: ? 1 myApplication || (myApplication = {}); 这种变化假定myApplication已经初始化,所以它只是一个参数/参数场景在下面的示例中非常有用: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function foo() { myApplication || ( myApplication = {} ); } // myApplication hasn't been initialized, // so foo() throws a ReferenceError foo(); // However accepting myApplication as an // argument function foo( myApplication ) { myApplication || ( myApplication = {} ); } foo(); // Even if myApplication === undefined, there is no error // and myApplication gets set to {} correctly 选项​​4可以写的jQuery插件,其中有用: ? 1 2 3 4 五 6 7 8 // If we were to define a new plugin.. var myPlugin = $.fn.myPlugin = function() { ... }; // Then later rather than having to type: $.fn.myPlugin.defaults = {}; // We can do: myPlugin.defaults = {}; 这将导致更好的压缩(缩小),并可以节省范围查找。 选项5有点类似于方案4,但它是一个长期的形式,评估是否myApplication是undefined内联这样的,它的定义为对象如果不是,否则设置为现有的值myApplication如果是这样。 它示出只是为了被透彻,但在大多数的情况下,选项1-4将以上满足大多数需要着想。 当然还有方差的大量对象文本如何以及在何处使用的组织和结构的代码。对于较小的应用希望暴露特定自我封闭模块嵌套的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 var namespace = (function () { // defined within the local scope var privateMethod1 = function () { /* ... */ }, privateMethod2 = function () { /* ... */ } privateProperty1 = "foobar"; return { // the object literal returned here can have as many // nested depths as we wish, however as mentioned, // this way of doing things works best for smaller, // limited-scope applications in my personal opinion publicMethod1: privateMethod1, // nested namespace with public properties properties:{ publicProperty1: privateProperty1 }, // another tested namespace utils:{ publicMethod2: privateMethod2 } ... } })(); 对象文本的好处是,他们为我们提供了一个非常优雅的键/值语法工作; 之一,我们能够很容易地封装任何明显的逻辑或功能为我们清晰地从他人分开,并为扩展我们的代码了坚实的基础的方式应用。 然而,一个可能的缺点是,对象文本具有成长为长语法结构的潜力。选择加入到利用嵌套命名图案(也使用相同的图案作为其基底) 这种模式有许多其他有用的应用了。除了命名空间,它是经常利益脱钩我们的应用程序的默认配置为可无需很容易地修改在我们的整个代码库搜索只是改变他们一个区域 - 对象文本为此工作的伟大。下面是一个假想的对象字面配置的例子: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var myConfig = { language: "english", defaults: { enableGeolocation: true, enableSharing: false, maxPhotos: 20 }, theme: { skin: "a", toolbars: { index: "ui-navigation-toolbar", pages: "ui-custom-toolbar" } } } 需要注意的是JSON对象是文字符号的一个子集,有它和上面之间真的只有轻微的语法差异(比如JSON键必须是字符串)。如果由于任何原因一个人希望使用JSON存储配置数据,而不是(发送到后端时,例如用于简单存储),随意。欲了解更多关于对象文本模式,我建议你阅读丽贝卡墨菲的优秀文章的话题,因为她介绍我们没有在摸几方面。 4.嵌套命名空间 对象字面的模式的延伸是嵌套的命名空间。这是用另一种常见的模式,由于这样的事实,即使命名空间已经存在,它不可能在同一嵌套的孩子做提供了碰撞的风险较低。 这看起来很熟悉? ? 1 YAHOO.util.Dom.getElementsByClassName("test"); 雅虎YUI库的旧版本经常使用嵌套对象命名空间格局。在我的时间作为AOL的工程师,我们也在我们的许多较大的应用程序使用这种模式。嵌套命名空间的样本实现可能是这样的: ? 1 2 3 4 五 6 7 8 9 10 11 12 var myApp = myApp || {}; // perform a similar existence check when defining nested // children myApp.routers = myApp.routers || {}; myApp.model = myApp.model || {}; myApp.model.special = myApp.model.special || {}; // nested namespaces can be as complex as required: // myApp.utilities.charting.html5.plotGraph(/*..*/); // myApp.modules.financePlanner.getSummary(); // myApp.services.social.facebook.realtimeStream.getLatest(); 注意:为模块那里使用沙盒API主机对象,远不及远较浅命名空间YUI3如何处理名字空间上述不同。 我们也可以选择申报新的嵌套的命名空间/属性索引属性如下: ? 1 2 3 myApp["routers"] = myApp["routers"] || {}; myApp["models"] = myApp["models"] || {}; myApp["controllers"] = myApp["controllers"] || {}; 这两个选项是可读的,有组织的和提供命名空间提供了以类似的方式应用到我们可以在其他语言被用来相对安全的方式。然而,唯一真正需要注意的是,它需要我们的浏览器的JavaScript引擎首次定位对myApp对象,然后挖下去,直到获得我们真正想要使用的功能。 这可能意味着执行查找工作的量增加,但开发者如Juriy Zaytsev先前已经测试并发现相较于“嵌套”的方式单一对象命名空间之间的性能差异是相当微不足道的。 5.立即调用的函数表达式(IIFE)■ 在本书的前面,我们简要介绍一个概念IIFE(立即调用的函数表达式),它实际上是一个匿名函数,它被定义后立即调用。如果这听起来熟悉,那是因为你可能有以前遇到过被称为自动执行(或自调用)匿名函数,但是我个人觉得本Alman的IIFE命名更准确。在JavaScript中,因为这两个变量,这样的背景下明确定义的函数可能只在它的内部访问,函数调用提供了一种简单的方法实现隐私。 IIFEs是一种流行的方式来封装应用程序逻辑来保护它从全局命名空间,但也有自己的命名空间的世界中使用。 IIFEs的例子可以发现如下: ? 1 2 3 4 五 // an (anonymous) immediately-invoked function expression (function () { /*...*/ })(); // a named immediately-invoked function expression (function foobar () { /*..*/ })(); 的自动执行功能,这比IIFEs相当不同的例子,可以在下面找到: ? 1 2 3 4 五 // named self-executing function function foobar () { foobar(); } // anonymous self-executing function var foobar = function () { arguments.callee(); } 回到IIFEs,第一IIFE例子稍微扩展版可能看起来像: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 var namespace = namespace || {}; // here a namespace object is passed as a function // parameter, where we assign public methods and // properties to it (function( o ){ o.foo = "foo"; o.bar = function(){ return "bar"; }; })( namespace ); console.log( namespace ); 虽然可读,这个例子可以显著扩大了解决共同发展问题,如隐私(公共/私有函数和变量),以及方便的命名空间扩展定义的级别。让我们通过一些更多的代码: ? 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 // namespace (our namespace name) and undefined are passed here // to ensure 1. namespace can be modified locally and isn't // overwritten outside of our function context // 2. the value of undefined is guaranteed as being truly // undefined. This is to avoid issues with undefined being // mutable pre-ES5. ;(function ( namespace, undefined ) { // private properties var foo = "foo", bar = "bar"; // public methods and properties namespace.foobar = "foobar"; namespace.say = function ( msg ) { speak( msg ); }; namespace.sayHello = function () { namespace.say( "hello world" ); }; // private method function speak(msg) { console.log( "You said: " + msg ); }; // check to evaluate whether "namespace" exists in the // global namespace - if not, assign window.namespace an // object literal })( window.namespace = window.namespace || {} ); // we can then test our properties and methods as follows // public // Outputs: foobar console.log( namespace.foobar ); // Outputs: You said: hello world namespace.sayHello(); // assigning new properties namespace.foobar2 = "foobar"; // Outputs: foobar console.log( namespace.foobar2 ); 扩展当然是关键的,以任何可伸缩命名空间图案和IIFEs可以用来实现这一很容易。在下面的例子中,我们的“空间”再度作为参数传递给我们的匿名函数传递,然后延伸(或饰)与进一步的功能: ? 1 2 3 4 五 6 7 8 9 10 11 // let's extend the namespace with new functionality (function( namespace, undefined ){ // public method namespace.sayGoodbye = function () { namespace.say( "goodbye" ); } })( window.namespace = window.namespace || {}); // Outputs: goodbye namespace.sayGoodbye(); 如果您想了解更多关于这个模式,我建议你阅读本的IIFE后获取更多信息。 6.命名空间注入 命名空间注入上IIFE,我们使用一个函数包装内“注入”为特定名字空间的方法和属性另一变型这个作为命名空间代理。这种模式提供的好处是功能性的行为多个对象或命名空间便于应用和应用一组的基础方法时,应以后建立在(例如getter和setter)可以派上用场。 这个模式的缺点是,有可能更容易或更优的方法来实现这一目标(例如,深对象扩展/合并),我先前在部分覆盖。 下面我们可以看到这种模式在行动,在这里我们用它来 ​​填充行为两个命名空间的一个例子:最初定义(utils的)一个又一个,我们动态地创建为utils的功能分配的一部分(被称为新的命名空间工具)。 ? 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 var myApp = myApp || {}; myApp.utils = {}; (function () { var val = 5; this.getValue = function () { return val; }; this.setValue = function( newVal ) { val = newVal; } // also introduce a new sub-namespace this.tools = {}; }).apply( myApp.utils ); // inject new behaviour into the tools namespace // which we defined via the utilities module (function () { this.diagnose = function(){ return "diagnosis"; } }).apply( myApp.utils.tools ); // note, this same approach to extension could be applied // to a regular IIFE, by just passing in the context as // an argument and modifying the context rather than just // "this" // Usage: // Outputs our populated namespace console.log( myApp ); // Outputs: 5 console.log( myApp.utils.getValue() ); // Sets the value of `val` and returns it myApp.utils.setValue( 25 ); console.log( myApp.utils.getValue() ); // Testing another level down console.log( myApp.utils.tools.diagnose() ); 安格斯·克罗尔还先前建议使用呼叫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 // define a namespace we can use later var ns = ns || {}, ns2 = ns2 || {}; // the module/namespace creator var creator = function( val ){ var val = val || 0; this.next = function () { return val++ }; this.reset = function () { val = 0; } } creator.call( ns ); // ns.next, ns.reset now exist creator.call( ns2, 5000 ); // ns2 contains the same methods // but has an overridden value for val // of 5000 如所提到的,这种类型的图案的是用于分配一个类似碱设置的功能到多个模块或​​命名空间是有用的。我不过才真正建议使用它,其中明确声明对象/关闭直接访问内部功能没有意义。 先进的命名空间模式 现在我们来探讨一些先进的模式和事业上更大的应用程序时,我发现宝贵的-其中有一些需要重新思考传统方法的应用程序命名空间。我会注意,我不是在提倡以下任何作为的方式来命名,而是我在实践中发现的工作方式。 自动化嵌套命名空间 正如我们已经回顾,嵌套的命名空间可以为代码单元结构提供一个有组织的层次结构。这样的空间的一个例子可以是以下:application.utilities.drawing.canvas.2d。这也可以使用对象字面量模式进行扩展: ? 1 2 3 4 五 6 7 8 9 10 11 var application = { utilities:{ drawing:{ canvas:{ 2d:{ //... } } } } }; 一个与此模式的明显挑战是,我们要创建的每个附加层需要定义在我们的顶级命​​名空间有些家长的孩子还有一个目的。当多个深度需要在复杂性我们的应用程序的增加这变得特别费劲。 如何解决这个问题得到更好的解决呢?在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 // top-level namespace being assigned an object literal var myApp = myApp || {}; // a convenience function for parsing string namespaces and // automatically generating nested namespaces function extend( ns, ns_string ) { var parts = ns_string.split("."), parent = ns, pl; pl = parts.length; for ( var i = 0; i < pl; i++ ) { // create a property if it doesn't exist if ( typeof parent[parts[i]] === "undefined" ) { parent[parts[i]] = {}; } parent = parent[parts[i]]; } return parent; } // Usage: // extend myApp with a deeply nested namespace var mod = extend(myApp, "modules.module2"); // the correct object with nested depths is output console.log(mod); // minor test to check the instance of mod can also // be used outside of the myApp namesapce as a clone // that includes the extensions // Outputs: true console.log(mod == myApp.modules.module2); // further demonstration of easier nested namespace // assignment using extend extend(myApp, "moduleA.moduleB.moduleC.moduleD"); extend(myApp, "longer.version.looks.like.this"); console.log(myApp); Web检查的输出: 其中一个会以前有显式声明的各种巢为他们命名为对象,这个现在可以很容易地使用一个单一的代码,更清洁的线来实现的。 依赖声明模式 现在我们要探索未成年人增强到我们称之为依赖宣言模式嵌套命名空间格局。我们都知道,对对象的局部引用可以减少总体查找时间,但让我们这适用于命名空间,看看它是如何可能看起来在实践中: ? 1 2 3 4 五 6 7 8 9 10 11 12 13 14 15 16 17 18 // common approach to accessing nested namespaces myApp.utilities.math.fibonacci( 25 ); myApp.utilities.math.sin( 56 ); myApp.utilities.drawing.plot( 98,50,60 ); // with local/cached references var utils = myApp.utilities, maths = utils.math, drawing = utils.drawing; // easier to access the namespace maths.fibonacci( 25 ); maths.sin( 56 ); drawing.plot( 98, 50,60 ); // note that the above is particularly performant when // compared to hundreds or thousands of calls to nested // namespaces vs. a local reference to the namespace 与这里的局部变量的工作几乎总是比具有顶层全局(egmyApp)工作速度更快。它也比都访问每一个后续行嵌套属性/子命名空间更方便,更高性能和能提高在更复杂的应用程序的可读性。 斯托扬建议,宣布我们的函数范围的顶部(采用单变量模式)函数或模块所需的本地化命名空间,这就要求一个依赖声明的模式。一本提供的好处是依赖定位和解决他们的下降,我们应该有需要时动态加载模块到我们的命名空间中的可扩展架构。 在我看来,这种模式在模块级工作时,定位一个命名空间由一组的方法使用效果最好。在每个功能级别本地化的命名空间,特别是在有空间的依赖之间的重叠显著将是我会建议避免在可能的情况。取而代之的是,它进一步向上,只是让他们都访问相同的参考。 深对象扩展 另一种方法来自动命名空间是深刻的对象扩展。使用对象的文字符号定义的命名空间可以很容易地扩展(或合并)与其他物体(或命名空间),这样两个命名空间的属性和功能,可在同一个命名空间后合并下访问。 这东西是已经取得相当容易与现代的JavaScript框架来实现(例如见jQuery的$ .extend),但是,如果用看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 // extend.js // Written by Andrew Dupont, optimized by Addy Osmani function extend( destination, source ) { var toString = Object.prototype.toString, objTest = toString.call({}); for ( var property in source ) { if ( source[property] && objTest === toString.call(source[property]) ) { destination[property] = destination[property] || {}; extend(destination[property], source[property]); } else { destination[property] = source[property]; } } return destination; }; console.group( "objExtend namespacing tests" ); // define a top-level namespace for usage var myNS = myNS || {}; // 1. extend namespace with a "utils" object extend(myNS, { utils:{ } }); console.log( "test 1", myNS); // myNS.utils now exists // 2. extend with multiple depths (namespace.hello.world.wave) extend(myNS, { hello:{ world:{ wave:{ test: function(){ //... } } } } }); // test direct assignment works as expected myNS.hello.test1 = "this is a test"; myNS.hello.world.test2 = "this is another test"; console.log( "test 2", myNS ); // 3. what if myNS already contains the namespace being added // (e.g. "library")? we want to ensure no namespaces are being // overwritten during extension myNS.library = { foo:function () {} }; extend( myNS, { library:{ bar:function(){ //... } } }); // confirmed that extend is operating safely (as expected) // myNS now also contains library.foo, library.bar console.log( "test 3", myNS ); // 4. what if we wanted easier access to a specific namespace without having // to type the whole namespace out each time? var shorterAccess1 = myNS.hello.world; shorterAccess1.test3 = "hello again"; console.log( "test 4", myNS); //success, myApp.hello.world.test3 is now "hello again" console.groupEnd(); 注:以上实现不跨浏览器的所有对象兼容,只能证明的概念来考虑。人们可以发现Underscore.js extend()方法更简单,更跨浏览器的实现开始与http://documentcloud.github.com/underscore/docs/underscore.html#section-67。或者,从核心中提取jQuery的$ .extend()方法的版本可以在这里找到:https://github.com/addyosmani/jquery.parts。 对于将要在其应用程序使用jQuery开发,可以实现具有完全相同的对象的名称空间扩展$.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 // top-level namespace var myApp = myApp || {}; // directly assign a nested namespace myApp.library = { foo:function(){ //... } }; // deep extend/merge this namespace with another // to make things interesting, let's say it's a namespace // with the same name but with a different function // signature: $.extend( deep, target, object1, object2 ) $.extend( true, myApp, { library:{ bar:function(){ //... } } }); console.log("test", myApp); // myApp now contains both library.foo() and library.bar() methods // nothing has been overwritten which is what we're hoping for. 为了彻底性的着想,请参阅这里的jQuery $ .extend等同于本节中的命名空间实验的其余部分。 建议 回顾我们在本节探讨了命名空间的模式​​,我会亲自使用对于大多数大型应用程序的选项是嵌套对象与对象面值模式命名空间。如果可能的话,我会实现这个使用自动嵌套的命名空间,但是这仅仅是一个个人喜好。 IIFEs和单全局变量可以正常工作在中小型范围的应用,但是,更大的代码库需要两个命名空间和深亚命名空间的要求,促进可读性和鳞片一个简洁的解决方案。我觉得这种模式实现了所有这些目标很好。 我也建议尝试一些建议先进的实用方法的命名空间扩展,因为他们确实可以节省时间我们从长远来看。 结论 以上就是本介绍冒险进入在JavaScript中和jQuery设计模式的世界 - 我希望你发现它是有益的。 设计模式很容易让我们建立在谁拥有超过几十年来定义的解决方案,以具有挑战性的问题和架构开发者的肩上。这本书的内容应该有希望提供足够的信息来使用我们覆盖在自己的脚本,插件和Web应用程序的模式上手。 我们要知道这些模式,但它也必须了解如何以及何时使用它们是很重要的。雇用他们之前研究每个模式的利弊。取出来的时候有图案进行实验,以充分理解他们所提供的,并基于模式的真正价值你的应用程序的使用情况判断。 如果我鼓励你在这一领域的兴趣,并进一步您想了解更多关于设计模式,则对这一领域提供通用软件的开发和当然,JavaScript的一批优秀的标题。 我很高兴地建议: “ 格局对企业应用架构 ”由Martin Fowler “ JavaScript的模式 ”的斯托扬斯特凡 感谢您阅读学习JavaScript的设计模式。有关学习JavaScript更多的教育材料,请随时读取我更多关于我的博客http://addyosmani.com或在Twitter上@addyosmani。 直到下一次,最好的运气在JavaScript中的冒险之旅! 参考 设计原则和设计模式-罗伯特·马丁Ç http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf 拉尔夫·约翰逊- ACM的特刊模式和模式语言- http://www.cs.wustl.edu/~schmidt/CACM-editorial.html 山坡工程设计模式库- http://hillside.net/patterns/ 临JavaScript的设计模式-罗斯Harmes和达斯汀·迪亚兹http://jsdesignpatterns.com/ 设计模式定义- http://en.wikipedia.org/wiki/Design_Patterns 模式和软件术语http://www.cmcrossroads.com/bradapp/docs/patterns-intro.html 粒设计模式的好处-杰夫Juday http://articles.techrepublic.com.com/5100-10878_11-5173591.html JavaScript的设计模式- Subramanyan古汉http://www.slideshare.net/rmsguhan/javascript-design-patterns 什么是设计模式,做我需要他们?-詹姆斯Moaoriello http://www.developer.com/design/article.php/1474561 软件设计模式-亚历巴内特http://alexbarnett.net/blog/archive/2007/07/20/software-design-patterns.aspx 评估软件设计模式-罗德Gunni http://www.rode.dk/thesis/ SourceMaking设计模式http://sourcemaking.com/design_patterns 辛格尔顿- Prototyp.ical http://prototyp.ical.ly/index.php/2007/03/01/javascript-design-patterns-1-the-singleton/ JavaScript的模式-斯托扬Stevanov - http://www.slideshare.net/stoyan/javascript-patterns 堆栈溢出-在JavaScript中(讨论)设计模式实现http://stackoverflow.com/questions/24642/what-are-some-examples-of-design-pattern-implementations-using-javascript 设计模式的元素-贾里德阀芯http://www.uie.com/articles/elements_of_a_design_pattern/ 堆栈溢出-实用JS设计模式的例子(讨论)http://stackoverflow.com/questions/3722820/examples-of-practical-javascript-object-oriented-design-patterns 尼古拉斯Zakas -在JavaScript中第1部分设计模式http://www.webreference.com/programming/javascript/ncz/column5/ 堆栈溢出-在jQuery的设计模式http://stackoverflow.com/questions/3631039/design-patterns-used-in-the-jquery-library 分类设计模式通过AntiClue -伊利斯·尼尔森http://www.anticlue.net/archives/000198.htm 设计模式,模式语言和框架-道格拉斯施密特http://www.cs.wustl.edu/~schmidt/patterns.html 献爱心模块模式-基督教海尔曼http://www.wait-till-i.com/2007/07/24/show-love-to-the-module-pattern/ 软件设计变得简单- Anoop Mashudanan http://www.scribd.com/doc/16352479/Software-Design-Patterns-Made-Simple JavaScript的设计模式-克劳斯·柯门达http://www.klauskomenda.com/code/javascript-programming-patterns/ 简介的JavaScript模块模式https://www.unleashed-technologies.com/blog/2010/12/09/introduction-javascript-module-design-pattern 设计模式解释- http://c2.com/cgi/wiki?DesignPatterns 混入解释http://en.wikipedia.org/wiki/Mixin 与四人帮的设计模式工作在JavaScript http://aspalliance.com/1782_Working_with_GoFs_Design_Patterns_in_JavaScript_Programming.all 使用的Object.create http://stackoverflow.com/questions/2709612/using-object-create-instead-of-new t3knomanster的JavaScript设计模式- http://t3knomanser.livejournal.com/922171.html JavaScript的优势-对象文本http://stackoverflow.com/questions/1600130/javascript-advantages-of-object-literal JavaScript类模式-利亚姆麦克伦南http://geekswithblogs.net/liammclennan/archive/2011/02/06/143842.aspx 了解代理jQuery中- http://stackoverflow.com/questions/4986329/understanding-proxy-in-jquery Observer模式使用JavaScript - http://www.codeproject.com/Articles/13914/Observer-Design-Pattern-Using-JavaScript 在谈到Observer模式- http://www.javaworld.com/javaworld/javaqa/2001-05/04-qa-0525-observer.html