我们首先简单地概述一下,自从三年前Eric Miraglia(YUI的开发者)第一次发表博客描述模块化模式以来的一些模块化模式。如果你已经对于这些模块化模式非常熟悉了,大可以直接跳过本节,从“进阶模式”开始阅读。
匿名闭包
这是一种让一切变为可能的基本结构,同时它也是Javascript最棒的特性。我们将简单地创建一个匿名函数并立即执行它。所有的代码将跑在这个函数内,生存在一个提供私有化的闭包中,它足以使得这些闭包中的变量能够贯穿我们的应用的整个生命周期。
复制代码 代码如下:
(function () {
// ... all vars and functions are in this scope only
// still maintains access to all globals
}())
注意这对包裹匿名函数的最外层括号。因为Javascript的语言特性,这对括号是必须的。在js中由关键词function开头的语句总是会被认为是函数声明式。把这段代码包裹在括号中就可以让解释器知道这是个函数表达式。
全局变量导入
Javascript有一个特性叫做隐式全局变量。无论一个变量名在哪儿被用到了,解释器会根据作用域链来反向找到这个变量的var声明语句。如果没有找到var声明语句,那么这个变量就会被视为全局变量。如果这个变量用在一句赋值语句中,同时这个变量又不存在时,就会创建出一个全局变量。这意味着在匿名闭包中使用或创建全局变量是很容易的。不幸的是,这会导致写出的代码极难维护,因为对于人的直观感受来说,一眼根本分不清那些是全局的变量。
幸运的是,我们的匿名函数提供了简单的变通方法。只要将全局变量作为参数传递到我们的匿名函数中,就可以得到比隐式全局变量更清晰又快速的代码了。下面是示例:
复制代码 代码如下:
(function ($, YAHOO) {
// now have access to globals jQuery (as $) and YAHOO in this code
}(jQuery, YAHOO))
模块导出
有时你不仅想要使用全局变量,你还想要声明它们,以供反复使用。我们可以很容易地通过导出它们来做到这一点——通过匿名函数的返回值。这样做将会完成一个基本的模块化模式雏形,接下来会是一个完整的例子:
复制代码 代码如下:
var MODULE = (function () {
var my = {},
privateVariable = 1
function privateMethod() {
// ...
}
my.moduleProperty = 1
my.moduleMethod = function () {
// ...
}
return my
}())
注意我们已经声明了一个叫做MODULE的全局模块,它拥有2个公有的属性:一个叫做MODULE.moduleMethod的方法和一个叫做MODULE.moduleProperty的变量。另外,它还维护了一个利用匿名函数闭包的、私有的内置状态。同时,我们可以很容易地导入需要的全局变量,并像之前我们所学到的那样来使用这个模块化模式。
进阶模式
上面一节所描述的基础已经足以应对许多情况,现在我们可以将这个模块化模式进一步的发展,创建更多强大的、可扩展的结构。让我们从MODULE模块开始,一一介绍这些进阶模式。
放大模式
整个模块必须在一个文件中是模块化模式的一个限制。任何一个参与大型项目的人都会明白将js拆分多个文件的价值。幸运的是,我们拥有一个很棒的实现来放大模块。首先,我们导入一个模块,并为它添加属性,最后再导出它。下面是一个例子——从原本的MODULE中放大它:
复制代码 代码如下:
var MODULE = (function (my) {
my.anotherMethod = function () {
// added method...
}
return my
}(MODULE))
我们用var关键词来保证一致性,虽然它在此处不是必须的。在这段代码执行完之后,我们的模块就已经拥有了一个新的、叫做MODULE.anotherMethod的公有方法。这个放大文件也会维护它自己的私有内置状态和导入的对象。
宽放大模式
我们的上面例子需要我们的初始化模块最先被执行,然后放大模块才能执行,当然有时这可能也不一定是必需的。Javascript应用可以做到的、用来提升性能的、最棒的事之一就是异步执行脚本。我们可以创建灵活的多部分模块并通过宽放大模式使它们可以以任意顺序加载。每一个文件都需要按下面的结构组织:
复制代码 代码如下:
var MODULE = (function (my) {
// add capabilities...
return my
}(MODULE || {}))
在这个模式中,var表达式使必需的。注意如果MODULE还未初始化过,这句导入语句会创建MODULE。这意味着你可以用一个像LABjs的工具来并行加载你所有的模块文件,而不会被阻塞。
紧放大模式
宽放大模式非常不错,但它也会给你的模块带来一些限制。最重要的是,你不能安全地覆盖模块的属性。你也无法在初始化的时候,使用其他文件中的属性(但你可以在运行的时候用)。紧放大模式包含了一个加载的顺序序列,并且允许覆盖属性。这儿是一个简单的例子(放大我们的原始MODULE):
复制代码 代码如下:
var MODULE = (function (my) {
var old_moduleMethod = my.moduleMethod
my.moduleMethod = function () {
// method override, has access to old through old_moduleMethod...
}
return my
}(MODULE))
我们在上面的例子中覆盖了MODULE.moduleMethod的实现,但在需要的时候,可以维护一个对原来方法的引用。
克隆与继承
复制代码 代码如下:
var MODULE_TWO = (function (old) {
var my = {},
key
for (key in old) {
if (old.hasOwnProperty(key)) {
my[key] = old[key]
}
}
var super_moduleMethod = old.moduleMethod
my.moduleMethod = function () {
// override method on the clone, access to super through super_moduleMethod
}
return my
}(MODULE))
这个模式可能是最缺乏灵活性的一种选择了。它确实使得代码显得很整洁,但那是用灵活性的代价换来的。正如我上面写的这段代码,如果某个属性是对象或者函数,它将不会被复制,而是会成为这个对象或函数的第二个引用。修改了其中的某一个就会同时修改另一个(译者注:因为它们根本就是一个啊!)。这可以通过递归克隆过程来解决这个对象克隆问题,但函数克隆可能无法解决,也许用eval可以解决吧。因此,我在这篇文章中讲述这个方法仅仅是考虑到文章的完整性。
跨文件私有变量
把一个模块分到多个文件中有一个重大的限制:每一个文件都维护了各自的私有变量,并且无法访问到其他文件的私有变量。但这个问题是可以解决的。这里有一个维护跨文件私有变量的、宽放大模块的例子:
复制代码 代码如下:
var MODULE = (function (my) {
var _private = my._private = my._private || {},
_seal = my._seal = my._seal || function () {
delete my._private
delete my._seal
delete my._unseal
},
_unseal = my._unseal = my._unseal || function () {
my._private = _private
my._seal = _seal
my._unseal = _unseal
}
// permanent access to _private, _seal, and _unseal
return my
}(MODULE || {}))
所有文件可以在它们各自的_private变量上设置属性,并且它理解可以被其他文件访问。一旦这个模块加载完成,应用程序可以调用MODULE._seal()来防止外部对内部_private的调用。如果这个模块需要被重新放大,在任何一个文件中的内部方法可以在加载新的文件前调用_unseal(),并在新文件执行好以后再次调用_seal()。我如今在工作中使用这种模式,而且我在其他地方还没有见过这种方法。我觉得这是一种非常有用的模式,很值得就这个模式本身写一篇文章。
子模块
我们的最后一种进阶模式是显而易见最简单的。创建子模块有许多优秀的实例。这就像是创建一般的模块一样:
复制代码 代码如下:
MODULE.sub = (function () {
var my = {}
// ...
return my
}())
虽然这看上去很简单,但我觉得还是值得在这里提一提。子模块拥有一切一般模块的进阶优势,包括了放大模式和私有化状态。
所以:只需要将需要暴露给外部的变量或者方法 设置为exports的属性 就行,
可以把exports看做一个全局对象,把所有暴露出来的函数和变量都存放在里面
1.先写个6.js文件
CommonJS规范规定,每个模块内部,module变量代表当前模板,这个变量是一个对象,他的 exports 属性(相当于 module.exports )是对外的接口。 这里详情请看我的另一篇文章: module、exports 和 require的关系
加载某个模块,其实是加载该模块的module.exports属性。require方法用于加载模块
ES6模块化的使用方法:(注!因为CommonJS类库众多,以及 CommonJS 和 ES6 之间的差异,所以无法直接兼容es6。)
直接/按需导出:可使用多个 用变量/常量的方式
导入:需要用按需导入 {解构} 的方式获取
默认导出:只能使用一个 (default属性只有es6才有)可以用引入对象定义多个属性,但这样在引入后调用的时候,更麻烦。
导入:优点:可以直接使用文件做接收参数且不用结构。
重命名export和import
如果导入的多个文件中,变量名字相同,即会产生命名冲突的问题,为了解决该问题,ES6为提供了重命名的方法,当你在导入名称时可以这样做:
如果想看CommonJS与ES6模块化的原理 可以去看我另一篇文章
[秦圆圆]大佬写的 require和import的区别
[大孩子气]大佬写的 require/exports、import/export 的区别
[七分sunshine!]大佬写的# 前端模块化工具 requireJs的使用
一.commonjs 是什么
是作用于服务端应用程序,让js程序具有模块化功能的一种语法规范,执行方式是同步且运行时加载,
如何使用:
1.module.exports 导出
定义一个匿名对象,将需要导出的成员 赋值到这个匿名对象上,然后再赋值到module.exports 导出
2.exports 导出
exports在每个模块中扮演着一个对象,如同每次在模块中默认执行了let exports = module.exports,那么exports如何使用导出功能呢,只要在exports对象上声明一个要导出去的属性名,将要导出的值赋值进去,
但不能直接赋值给exports对象,否则就和module.exports没有关系,例如:exports=function fn() {},
导入时拿到的是一个undefind
3.require 导入
导入得到是一个对象,对象里面是导出模块所导出的成员,也可以同结构的方式按需导入
二.ES6模块化
es6模块化也是和commonjs一样是具有将js模块化功能的语法规范,不过只能用于在能识别es6语法浏览器环境,es6模块由两个命令构成:export, importexport 是向外提供输出对外接口,import是使用其他模块向内提供输入接口
1.export
使用export 导出想要导出的值,可以是对象,变量,函数
声明 的同时 且导出
将要导出的值赋值给对象的方式导出
那么以exports导出的模块该如何 导入到其他js文件呢?
1.2 导入模块 语法 import
import 是提供接收对外导入的接口
格式: import 以结构的额方式使用大括号包起来,里面填写按需导入的成员,必须和必须和对应模块导出的成员名一致,如果需要更改成员名,需要使用as关键词, from 对接的模块路径
如果需要把一整个模块的所有对象都导入成一个对象 使用 as 关键词,自定义一个对象名
2.0 export default
同样是和export一样暴露指定的变量或者对象函数
但是导入到 其他模块会稍微方便一些,可以不用像export 需要使用{}接收,直接使用一个自定义的对象名接收即可
commonjs与es6模块化直接的差别
commonjs导出是值的拷贝,es6模块化是导出的值的引用
commonjs加载是运行时加载,而es6模块化是编译时加载会比commonjs更加高效