JavaScript 的有趣之处在于它不会强加任何特定的结构。如果你愿意,可以称之为“自带组织”。随着我在 Web 应用网站中编写越来越多的 JavaScript 代码,这一点变得越来越有趣。 如何构建你的 JavaScript 代码非常重要,因为
- 如果做得好,它可以让其他人和重新查看自己代码的你更容易理解代码。
- 拥有一个既定的结构有助于保持未来代码的整洁,并鼓励你遵循已声明的最佳实践。
- 它使你的代码可测试。
在继续之前,我想说明一点,我远不是 JavaScript 大师。JavaScript 世界里有很多聪明人,他们比我早得多就开始做这件事,并讨论这件事,那时我甚至还不知道 div 是什么。
最近我一直在思考这个问题,因为我为 CodePen 编写了大量的 JavaScript 代码,CodePen 非常依赖 JavaScript。从一开始我们就一直在使用“模块模式”,我认为它一直为我们提供了很好的服务。
然后我发布了这篇关于 Treehouse 广告制作过程 的文章,在文章中我也使用了模块模式来编写 JavaScript 代码。Louis Lazaris 评论说,最近他也喜欢这样编写 JavaScript 代码,并 引发了一些讨论。Louis 随后发表了一篇 文章,解释了他喜欢的结构模式。
概念
如果有一个可以从中构建的示例,则更容易理解这样的概念。假设我们正在构建一个新闻小部件。它列出了一些新闻文章,并且有一个按钮可以加载更多新闻文章。
模块
谦逊的开端
var NewsWidget = {
};
只是一个对象。我喜欢我的 JavaScript 变量以小写字母开头,但模块的首字母大写。这只是一个有助于代码可读性的约定。
设置
新闻小部件可能与一些重要的数字相关联(例如,其中包含的文章数量)。此外,还有一些需要定期访问的重要元素(DOM 节点)。
此模块将有多个执行小型特定操作的子函数。其中许多可能需要访问这些设置和“缓存”的元素。因此,我们将使每个子函数都可以访问这些设置。
var s,
NewsWidget = {
settings: {
numArticles: 5,
articleList: $("#article-list")
}
};
我们将在稍后讨论子函数访问。
初始化函数
为了“启动”,我们将只调用**一个**函数。这在所有模块中都将保持一致,因此当您开始阅读代码并弄清楚发生了什么时,您会确切地知道在哪里查找。
var s,
NewsWidget = {
settings: {
numArticles: 5,
articleList: $("#article-list"),
moreButton: $("#more-button")
},
init: function() {
// kick things off
s = this.settings;
}
};
init
函数首先要做的事情是将变量 s
(我们在与模块相同级别声明了该变量)设置为指向设置的指针。由于 s
的声明位置,这意味着模块的所有子函数都可以访问设置。很好。
绑定 UI 操作
Alex Vasquez 为我们在 CodePen 建立了一个约定,即我们会有一个专门用于绑定 UI 事件的函数。在绑定时,您永远不会编写任何“执行操作”的代码,您只需进行绑定,然后调用另一个名称合适的子函数。
var s,
NewsWidget = {
settings: {
numArticles: 5,
articleList: $("#article-list"),
moreButton: $("#more-button")
},
init: function() {
s = this.settings;
this.bindUIActions();
},
bindUIActions: function() {
s.moreButton.on("click", function() {
NewsWidget.getMoreArticles(s.numArticles);
});
},
getMoreArticles: function(numToGet) {
// $.ajax or something
// using numToGet as param
}
};
合并文件
新闻小部件可能不是页面上唯一的 JavaScript 代码。您需要加载许多模块。也许新闻小部件位于您网站的每个页面上,因此属于 global.js 文件。此 global.js 文件最终将是一堆连接在一起的模块,然后是一个大型启动派对。
处理此连接操作有很多方法。它可能是像 CodeKit 这样的开发工具,以及它进行 附加/前置 的能力。它也可能是某些花哨的 Grunt.js 构建脚本。
在 CodePen,我们使用 Ruby on Rails 及其 资产管道。因此,对我们来说,global.js 文件将类似于
//= require common/library.js
//= require module/news-widget.js
//= require module/some-other-widget.js
(function() {
NewsWidget.init();
SomeOtherModule.init();
})();
就是这样
我认为这是一种非常令人满意的编写 JavaScript 代码的方式。它满足了我之前提到的关于结构重要性的三个要点。
关于测试点,我知道没有过多讨论,但我相信您可以想象,在负责特定任务的较小的特定函数中,编写断言会更容易。大多数 JavaScript 测试都是这样完成的(例如 Jasmine)。例如,在某种形式的代码中:“我断言,当此函数获取此值时,另一件事会发生,并且等于某个其他值。”
这只是冰山一角。我刚刚开始接触它,但 Addy Osmani 编写的 学习 JavaScript 设计模式(可在网上免费阅读!)看起来会深入探讨这个问题。
您可能应该从每个代码示例的第二行删除“function()”(第一个和最后一个示例除外)。 :-)
哎呀!已修复。
很棒的解释,伙计……
太棒了。与我一直在做的事情非常相似,但更简洁。我真的很喜欢如何进行设置。谢谢
我建议您了解一下 Marionette (http://marionettejs.com/)。即使我不使用 Backbone 与 REST 后端交互,我也喜欢使用它们的模块/视图/路由器/控制器。Marionette 为我提供了一种合理的方式来构建代码。它还可以处理一些样板代码和内存管理问题。
开发人员 Derick Bailey 还提供了一个不错的 JSFiddle 供您入门 (http://jsfiddle.net/derickbailey/M5J8Q/)。它非常简化,但如果您想学习/尝试,这是一个很好的起点。
如果您对每个模块使用相同的“s”变量作为设置,然后将它们全部导入到同一个 global.js 中,它们会不会发生冲突?
我认为这是一个很好的发现,他们最好使用一些模块加载器(require.js 或等效项)或使用自动执行函数(
来封装局部变量。
他们宁愿使用现有的模块模式(再次,AMD 样式),而不是重新编码。
不会,因为模块是按顺序初始化的。但我将在 's' 的位置使用 'this.settings'(或局部快捷方式 var,如果我需要多次使用 'this.settings')。
@atmin 这也是让我困惑的地方……为什么为了一个快捷方式污染全局命名空间?为什么不将 s 变量设置为局部变量并用作快捷方式,或者干脆跳过语义直接使用 s 作为 settings 对象?
好的,它们是按顺序初始化的。但我认为
s
在模块外部声明而不是在init
函数内部作为局部变量声明的原因是为了让同一模块中的其他方法将来能够更轻松地访问设置。如果NewsWidget.getMoreArticles
引用s
,它将查看错误的设置。实际上,我认为在示例代码中,在事件处理程序内部,这可能会抛出一个错误,因为它引用了
s.numArticles
,并且会在其他模块初始化后触发。这是一个我正在谈论的示例:http://codepen.io/jetpacmonkey/pen/gkmei
总体上同意 Michael 的观点。我过去也像文章中演示的那样编写模块,并很快遇到了名称冲突问题。我现在使用自执行函数模式编写模块,从而为模块提供私有变量/方法等的“封装”。尝试类似以下内容
顺便说一句,缩进似乎没有正常工作……或者我错过了关于如何在 <pre> 中格式化代码的一些内容。
Brendan 说得对 :)
缩进没问题。预览似乎坏了。
全局变量是任何编程语言中最糟糕的反模式之一。这几乎总是你在任何大学的 CS-101 课程中学习的第一件事。JavaScript 使使用这种反模式变得容易,几乎会诱使你使用它。为什么?因为你经常发现自己处于闭包作用域的中间,无法访问所需的变量,所以你在外部作用域中创建了一个变量。Chris 自己也说过,“我们将使设置对它们 [子函数] 中的每一个都可用。”你能对你全局范围的变量做的最好的事情就是将它们声明为“const”并使它们不可变。如果 JS 开始对你关于非法访问发出警告,你就知道你使用它不正确了。其他解决方案在其他一些评论中列出。使用命名空间。
你的模块模式似乎有些奇怪:你声明了 settings 对象,并将 articleList 赋值给一个 jQuery 对象。这意味着你的 DOM 必须在包含此 JavaScript 文件时准备好。如果这是真的,那么绑定事件到 DOM 的 init 函数有什么作用?
在底部加载所有脚本,当你调用脚本时,DOM 就准备好了。
如果它不是为文章列表使用固定的 ID,而是可以作为参数传递给 init(),可能更灵活。
同意。这正是我所做的,但我喜欢有一个可以覆盖的默认值。
Jeremy:我相信这种情况不会发生。“s”也引用了“this”,我相信,由于函数的词法作用域,这可以防止它们相互覆盖。当然,如果我错了,我愿意接受更正。
this
在等号的右侧。实际被赋值的s
变量具有全局作用域(这正是它在模块外部声明的原因),这意味着,除非 Ruby on Rails 做了一些特殊的操作,否则第二个模块的init
将覆盖第一个模块的设置,并且对第一个模块中使用s
的任何函数的后续调用都将引用第二个模块的设置。这也可以这样写吗?`
更面向对象一些?
我认为是的,可以这样写。Chris 的示例侧重于更严格的命名空间。“NewsWidget”是一个主类,其子类附加在其上:“settings”、“init”等。
实际上,Chris 使用的方法符合单例设计模式,并且在面向对象语言中非常常见。
你可以在网上查找应该在什么情况下使用它。
感谢你提到我的帖子,Chris。
对于感兴趣的人,我强烈推荐我帖子中的一些评论,这些评论提供了一些很好的建议。我特别喜欢 Maciej Baron 的评论
http://www.impressivewebs.com/my-current-javascript-design-pattern/#comment-32835
怎么样 -> http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth
这不是模块模式,这是一个简单的对象字面量,模块模式比这更复杂一些,并且 bind 函数并不是真正好的开发方式,类似这样:https://gist.github.com/4504091
我做了一个模块模式引导程序,看看这个:https://github.com/guilhermepontes/module-pattern-bootstrap
有很多方法可以实现模块模式,对象字面量就是其中之一。此处链接到你的文本…参考此内容
此 gist 表示一个模块应用程序:https://gist.github.com/4504159
我使用类似的方法,但使用函数(由于自动初始化)。它可以轻松调整以用作构造函数。
事实上,你在 _somePrivateMethod 前面没有使用
var
关键字意味着它会被放到全局作用域中。你的示例非常好,但是为了使你的方法真正成为私有的,你需要var
来保持作用域局部。var _somePrivateMethod = function() { ... }
捕捉得很好,谢谢。
嗨,看看更新后的示例 - 在之前的示例中,我有点失误,在单个实例(没有“new”)中,“this”指向“window”对象。以下是更新后的示例
你可以在 jsFiddle 上看到它
* 标准示例(单个,自动初始化) - http://jsfiddle.net/nightman/jxZ5n/1/
* 带有多个实例的示例,无自动初始化 - http://jsfiddle.net/nightman/B8ECk/1/
此致
Karol - 我最喜欢你的实现。
这是你多个实例版本的示例,但使用了自动初始化。
http://jsfiddle.net/B8ECk/2/
看起来非常棒!谢谢。
嗨,Cooper,看看更新后的示例 - 在之前的示例中,我有点失误,在单个实例(没有“new”)中,“this”指向“window”对象。以下是更新后的示例
此致
感谢分享,我使用相同的方法,但作为单例,看看这个
http://robdodson.me/blog/2012/08/08/javascript-design-patterns-singleton/
我离成为 JS 专家还差得很远。大约 6 个月前,我和一些同事为一个大客户工作时了解了这种模式。从那时起,这就是我构建所有 JS 的方式。它有很多好处:1)你可以将每个模块分别存储在你的 CMS 中,并在构建时连接。2)如果你已经编写了模块化的 HTML 和 CSS,你可以将功能模块与表示模块配对。3)当你一致地使用这种模式时,用它创建 JSON 很容易。
我学习到的“高级”方法是,你可以创建一个包含所有模块的对象并将其分配给窗口,然后创建一个“主”初始化函数,在文档加载完成后运行你的模块。
window.paceaux = {
this.init = function(){
NewsWidget.init();
ArticleWidget.init();
},
NewsWidget ={
//代码片段
this.init= function (){
}
},
ArticleWidget= {
this.init = function (){
}
}
};
//———–
//文档加载完成
//
$(function () {
window.paceaux.init();
});
这种方法的主要问题在于它非常单一,因为它对DOM具有硬编码的依赖关系,它是一个单例,因此你无法在一个页面上拥有多个小部件,并且难以测试。
我个人偏好的风格是你在bootstrap插件中看到的风格。也就是说
你将所有内容都包装在一个自执行函数中
在其中包含一个可构造的对象,该对象包含所有小部件的逻辑。
依赖项传递给构造函数,这意味着单元测试变得更容易,并且你可以在每个页面上拥有多个小部件
你还可以非常简单地从这里创建一个jQuery插件和声明性data-* API,因为所有逻辑都封装在一个简洁的对象中。它基本上是样板代码,并且使事情变得非常灵活。
我准备了一个完全可用的示例小部件,它在codepen上准确地展示了我的意思。JS中的注释非常详细,所以每个人都应该能够理解。它还演示了在一个页面上使用多个小部件 :)
太棒了,我希望会有更多关于这方面的讨论。。。
虽然有很多JS模式可以使用,但有时我们应该知道使用某种模式是否有意义。
你的结构是正确的,但在它真正成为一个模块之前还需要一些东西,例如封装私有成员、公开公共API和命名空间。我强烈建议你阅读Douglas Crockford关于模块模式的原始文章。
本质上,你将拥有一个带有私有成员的自执行函数
…它通过公共API公开
…它可以在DOM加载时启用,如下所示
或者如果
_init
接受参数,则如下所示这似乎是一个简单的方法,可以开始理解如何编写模块化代码。
另外,我还没有看过Addy的书,感谢你的推荐!
小错误
你忘记了Addy姓氏中的“S”。
我建议你看一下BEM方法论。
BEM是一种组织代码的方式,可以简化开发和未来维护的过程。
SmashingMagazine上也有一系列关于BEM的文章。并且在第3部分中描述了项目结构。
这看起来是一个很好的编码方式。
{ spam: eggs }
在我看来像一个字典。这适用于纯JS吗?
太棒了!一如既往。。。
我有一些类似的东西,但目前我是这样做的,如果不好,有更好的方法等等,请随时建议。
Chris,我给你一个“A”表示努力,一个“B-”表示执行。
正如其他人指出的那样,变量“s”将是全局的,因此存在被其他模块覆盖的风险。它也不是真正的模块模式。假设你希望它成为一个单例,你可以稍微修改你的示例(这类似于评论中的一些其他帖子)
我认为只要他将整个内容包装在一个自执行匿名函数中,那么就不必担心全局“s”。这将类似于你上面的提议(顺便说一句,我喜欢这个提议)。
太棒了,Peter。简洁明了,效果很好。我以后会自己使用这种模式!
如果整个内容都包装在一个自执行匿名函数中,那么“s”将不是全局的,但NewsWidget也不会是全局的,这意味着他无法在自执行函数之外调用NewsWidget.init()。解决方法是在不使用“var”的情况下声明NewsWidget,这会隐式地将其创建为全局变量,但我通常不建议这样做。
我没有将整个内容包装在一个自执行匿名函数中,而是保留了匿名函数,但让它返回一个对象(然后我将其分配给NewsWidget)。这很相似,但这种细微的变化产生了很大的不同。
我想我所考虑的是,这将是整个单个站点/应用程序JS的结构。因此,全局“s”或主对象在函数外部不可访问并不重要。尤其是在一个小应用程序中,它不会有任何潜在的冲突等等。
我喜欢这个,因为它修复了设置变量是全局变量的问题,但是如何在使用模块模式时使用闭包使函数私有/公共(封装)。如果每个函数都在返回中,它将是公共的。如果你将函数移出返回,你将使它们成为私有的,但会失去对设置变量的访问权限。
@Jerry,如果你将函数移出返回,你将使它们成为私有的。你仍然可以通过私有变量“s”访问设置。例如
在这个例子中,我添加了一个私有方法“somePrivateFunction”,它可以通过私有变量“s”访问设置。或者,我可以做一些事情,例如将对“this”的引用作为参数之一传递
然后,在我调用私有函数的地方
非常棒,Peter。我目前也在尝试这种模块模式!
另一种实现方法是。
很棒的文章!我使用过遵循这些模式的代码,但我有一些疑问。谢谢。
很棒的解释,伙计。很高兴读到它 :)
基于模式编写脚本的绝佳解释。非常需要的指导方针,以避免在编写脚本时出现任何错误。
嗨,我想你是指Addy Osmani!
很棒的文章!
干杯
好读,我也同意Peter Foti上面提出的评论。我认为如果你看一下Sparky.js,你一定会爱上它的——http://sparkyjs.com/
我不是JS专家,但我的javascript“顿悟”时刻来自于揭示模块模式
http://addyosmani.com/resources/essentialjsdesignpatterns/book/#revealingmodulepatternjavascript
我同意作者的观点,这是一个改进。我曾在PHP中做过一些面向对象编程(就其本身而言),所以拥有一个具有公共和私有方法的对象对我来说很有意义。
关于模块模式的一篇很棒的小文章!我最近一直在深入研究它,使用Rails资产管道并尝试使用require.js组织我们工作中混乱的JavaScript。在我看来,它使一切变得更加整洁,我喜欢拥有只有几百行JS的文件而不是几千行。
谢谢Chris。。。
老实说,你只是触及了闭包逻辑的表面。我花了一些时间才理解这些秘密,但一旦理解了整个闭包主题,就会揭示JS编程的优点。
扩展“模块”一词,并从“框架”的角度思考。构建你自己的库并一致地使用它们。
举个例子
你在模块外部使用一个变量。如果能够利用纯“私有”变量,只从模块内部访问,那将是多么好的一件事!
主要的技巧是在模块内部保留一个指向自身在任何上下文中自身的引用。不,“this”不起作用,因为当从事件处理程序或计时器调用模块函数时,它会指向其他对象。
周围有很多文档和示例,我只能鼓励每个人增加他们的知识——这值得,它会将你的ACME Javascript变成一门通用的编程语言。
根据我对模式的理解,这不是模块模式,而是一个简单的单例模式。单例模式通过对象字面量语法定义其简单的构造。它们通常缺乏封装,因为所有内容(属性和方法)通常都是公开的且可访问的。
由于这种“所有内容都公开”的特点,它使对象容易受到变异。在多个开发环境或公共 API 中,这通常不是好的做法,封装更可取。
封装,单例通常缺乏的东西,是提供受控 API 的手段,在该 API 中,您只公开具有访问私有变量权限的特权方法。这可以防止意外覆盖和变异。
另一方面,模块的构建目的是提供封装。基本的模块是一个函数,它返回一个特权函数或一个具有特权方法的对象,这些方法可以访问私有变量,但需要注意的是,包含数据的这些变量不是返回对象的一部分,因此它们不会公开暴露在模块函数之外。
如果您在想,“这听起来像闭包”,那它确实是!这个闭包允许开发人员控制数据如何以及如何被访问,例如封装。这对于公共 API 和多个开发团队来说是最佳实践。
TL;DR
单例模式由对象字面量语法定义,通常没有包含私有和特权实体的封装。
另一方面,模块由一个函数定义,该函数返回一个具有特权方法的对象,这些方法可以访问私有变量。
至少,这就是我理解 JavaScript 中模式的方式。如果我在这里误解了什么,请告诉我。
我本来想插话提一下,这其实不是模块模式,而是单例模式,但 Justin Lowry 比我先说了。我不喜欢在模式和编程技巧上过于教条,因为很多时候这仅仅是语义或背景差异。可以有单例模块、可实例化模块、内部包含工厂的模块等等。
在本例中,代码示例绝对是单例。单例本身没有问题,但对于那些没有注意正在发生的事情的人来说……如果不对对象正在引用的内容进行手动管理,可能会导致内存泄漏。
直到评论列表的中间,我都很担心没有人会提到 AMD(异步模块定义)。
当然,文章中所示的单例模式通常可以满足大多数开发人员的需求。但是,由许多具有各种依赖关系的子模块组成的稍微复杂一点的 Web 应用程序会让人头疼……
我从 PHP 世界进入 JavaScript 驱动的应用程序,有些事情变得很奇怪。但是,当我开始学习 AMD 方法时,由于整个过程,开发应用程序开始让我感到愉快。
有人之前提到过私有变量。嗯,确实有一些问题。我使用构建脚本解决了这种情况,这些脚本分析我的源代码内容,例如 this._somePrivateStuff() 将被重写为简单的 var _somePrivateStuff() 并进一步解析。为什么这么奇怪?嗯,我一直在谷歌搜索和阅读许多关于 JS 中 TDD 的文档,有一件事很遗憾——根本没有反射机制。因此,在开发版本中,您应该将您的私有/受保护变量……公开,但在发布之前,用适当的修饰符替换声明。
如果有人在设计此类模块方面有更好的经验,涉及到 TDD 方法,请随时回复此评论。
我不确定您对 JS 的了解有多深,但是隐私只能通过使用函数作用域来闭包变量(即闭包)来创建,从而将它们隐藏在外部作用域之外。用下划线作为变量前缀在功能上没有任何作用。
如果您正在开发一个复杂的 JS 应用程序,并且想要对其进行测试,最好的方法是使用实际的模块或工厂模式,并让它返回您需要的函数,而无需返回私有变量。然后,您可以让测试框架测试 API 并确保它返回预期值。
AngularJS 是一个拥有华丽的内置单元测试的最佳 JS 框架之一。他们构建整个框架时就考虑到了测试。我强烈建议您研究一下,并了解他们是如何解决问题的。
他们甚至创建了依赖注入 (DI),这使得测试几乎毫不费力。DI 很少见,但它是一种非常强大的处理依赖项的方法。
无论如何,如果您需要真正的隐私,您不能使用您对其他“经典”语言的了解,您必须真正理解 JS 的原型特性及其词法作用域。如果您试图将更经典的风格强加于 JS,最终您将自食其果。
嗯,我错了——当然,这些变量是由闭包封闭的。我的意思是,在测试期间访问内部的私有字段,因为——据我所知——没有 JS VM 具有类似反射的功能。
我会看看 AngularJS,谢谢你的反馈。
正如其他人所说,这实际上 *不是* 模块模式,而是单例(对象字面量模式)。Rebecca Murphey 有一篇关于此模式的非常棒的帖子,如果有人想了解更多信息,可以参考:http://rmurphey.com/blog/2009/10/15/using-objects-to-organize-your-code/
很棒的内容,谢谢 Chris。
关于模块模式设计的精彩解释,现在如果我们有一本 20 页的书籍专注于模块设计模式和不同的方法就好了。Andy Osami 的书很棒,但他解释了所有模式,而模块模式是最流行的。
我想问一下,如果我订阅了 lodge,我能否学习服务器端技术,比如像这个网站的首页一样自动添加新的 feed,以及如何处理论坛等等?我对所有这些东西都是新手(很困惑)。
喜欢这个模式,并且已经使用它的一种变体一段时间了。
快速提示:在 bind_events 方法中,.on() 能够传递 event.data。这意味着您不必将函数调用包装在另一个函数中才能传递参数。
我其中一个 bind_events 方法的代码片段。
注意这些绑定中的一些 JSON 对象参数。{ index: 1 } 稍后可以通过类似... 的方式在所述函数内部访问。
更好吗?不一定。只是工具箱里的另一个工具。
event.data 提示非常酷。我经常使用 .on(),但从未知道这个技巧。非常酷。谢谢分享。
这是一个很酷的模式,但我只会将其用于传递动态生成的對象。如果您只是想在对象上执行一个方法作为回调,我个人更喜欢使用
$.proxy
。例如,使用 Chris 上面的代码
为我们节省了额外的
s
变量。嗨,我在之前的代码示例中犯了一个小错误——在我的单实例示例中,“this”指向“window”对象。这是更新后的版本
jsFiddle 代码
* http://jsfiddle.net/nightman/ynUZf/ – 单实例,基本示例
* http://jsfiddle.net/nightman/3kzhj/ – 单实例,带有其他选项的示例
* http://jsfiddle.net/nightman/TBKvB/1/ – 多个实例(构造函数示例)
* http://jsfiddle.net/nightman/prpU2/2/ – 多个实例(构造函数示例)以及自动初始化
此致。
小心。很多人写关于 JavaScript 的文章,但他们不是专家(不是说你,而是我们从谁那里获取信息)。我敢打赌,编写 JavaScript 的新手程序员比任何其他语言都要多,导致了大量 *即插即用* 的部件和小部件库的激增。
编程不仅仅是学习一门语言的语法并勇往直前,那是在进行实验(这没什么错,只是不要将其作为最佳做法的示例)。编程也不仅仅是与计算机通信,或学习算法和模式。尤其是在当今时代,同样重要的是它与人之间的交流,以及向其他开发人员展示你的程序的功能(甚至是你自己,尤其是在你的记忆失去了你程序的心智模型之后)。
许多 JavaScript“最佳实践”文章和编码标准都侧重于为编译器编写代码,而不是为作者编写代码。编程本身(从抽象意义上讲)是一项很难传授给他人的技能,学习语法、算法和模式是简单部分。
Bret Victor 在他精彩的论文中谈到了这一点,以及更多内容:http://worrydream.com/#!/LearnableProgramming,如果您有时间阅读它,它相当长,但绝对是一种享受。
一个好的模块不允许从模块外部直接操作
settings
对象,它们应该是模块私有的,并且只有在存在允许其操作的公共函数的情况下才能从外部修改。这样,它可以被验证,如果有人执行s.numArticles = document.location.search.substring(1)
会发生什么?一个 *很棒* 的模块不仅可以通过编程方式访问设置,还应该有一种方法可以从文档中检索它需要的所有参数。您不应该为 JavaScript 程序员编写 JavaScript 模块,而应该为 HTML 作者编写,尤其是在您希望更多人使用它们的情况下,他们只想编写 HTML 来包含您的脚本,并且希望事情就此结束。
最重要的是,一个 *很棒* 的模块不会在任何地方硬编码任何 HTML ID。它会检查文档并发现自己是否需要,并相应地采取行动,这与 polyfill 的工作方式非常相似,使用元素、类、微数据,任何东西,除了 HTML ID。
你的“模块”仅仅是使用对象字面量语法创建的对象。你需要导出对
settings
的引用的原因是,你实际上并没有使用那本书中的模块模式:http://addyosmani.com/resources/essentialjsdesignpatterns/book/#modulepatternjavascript——这本书首先通过将对象字面量语法描述为先决条件来开始介绍模块模式,然后继续描述模块模式本身。对象字面量是一种在不使用new Object()
的情况下创建对象的方式,就像字符串字面量在不使用new String()
的情况下创建字符串一样。那本书看起来不错。至少它知道对立即调用函数使用了正确的术语,不像之前的一些评论,他们仍然称之为自执行函数。这个函数是自执行的
自执行函数属于关于递归的讨论。此函数立即被调用
虽然我个人更喜欢使用
new
关键字不需要尾随括号,并且你不需要
return
模块的接口,只需使用新对象(this
)来定义它。为什么你在立即调用函数表达式中包含
NewsWidget.init(); SomeOtherModule.init();
,我永远不会知道。因为你那里没有任何需要保护或设为私有的东西。尽管如此,如果模块本身是立即调用的,那么你根本不需要在它们上调用init
。很好的总结,非常有启发性的主题。谢谢!
@Lee,很棒的回复,谢谢。我在阅读这篇文章和评论时开始失去信心。+1,因为你知道自己在做什么。
这太棒了。感谢分享你的专业知识。
我借鉴了这个并用选项扩展了它。感谢@karol提供的示例。
CodePen:带有选项对象的模块模式
s.moreButton.on click事件从未触发,有人能告诉我为什么吗?
如果我使用$(“#more-button”)代替,它就会起作用
我最近开始使用这种模式的修改版本。我喜欢这样使用对象,但我不喜欢随着代码变得越来越复杂,子对象变得难以理解。我个人也相当坚持在任何可能的情况下使用完整的单词作为变量名。我发现它更具有自文档性。我的代码最终看起来更像这样
我想我也可以开始将“settings”部分用作默认设置,然后将“settings”对象传递给初始化函数以覆盖选定的默认值。我相信还有很多其他可以改进的地方,但这就是我当前的模式。我喜欢即使我正在使用对象,我也不必过多地考虑子对象在哪里结束,以及它们是否被正确注释。
我非常喜欢你示例中的这种编码风格。对我来说,它更简洁。但是查看评论中的其他风格让我查看了几天前编写的一个小全屏模块,我意识到模块中的每个方法都是公开的。如果可以使方法私有,我希望能坚持这种风格。
所以我认为其他人的做法更好。不过我有一个疑问。在这个示例中,在自调用函数中传入window是必要的还是好的做法?我知道jQuery插件在“$”被另一个脚本覆盖的情况下会这样做,但是“window”会被覆盖吗?
在阅读了更多评论并找到了Oriely的这个链接后,我对此有了更多了解。关于带构造函数的模块的部分也回答了我之前关于如何将选项传递给模块的另一个问题。
如何使用模块模式
不错的文章,只有一件事我想改变……
var s;
你应该在init的私有作用域内声明“s”变量,因为你创建了一个值为“undefined”的全局s变量。始终尽量减少全局变量的数量。
不错的文章!
我一直在使用Christian Heilmann在他的文章在我职业生涯早期我希望知道的七件事中谈到的闭包,称为“揭示模块模式”,发现它运行良好。实际上,我在阅读本文的第一段代码时就在考虑闭包,并且我还没有尝试使用对象进行闭包。我想每个人都有自己的风格,但哪种方式通常是最好的?
哇,谢谢那个链接 :)
你应该看看
Terrific JS
不错的文章。我编写 Javascript 应用程序已经有一段时间了,并且一直对构建应用程序的结构感到沮丧。我最终制作了自己的 Javascript 应用程序构建程序,称为 structureJS https://github.com/ShamariFeaster/structureJS
更多模块方法是您使用干净的 JS
}
;(function(){
})();
这似乎不是模块模式设计的实际示例?您只是将对象字面量分配给一个变量……模块模式必须使用一个内联执行的匿名函数来在您正在创建的命名空间上创建闭包。这允许公有/私有成员,从而模拟 OOP