在看了一下午的文章之后,我大概对js模块化有了一定的了解。现在来回顾和记录一下javascrip的模块化发展史,主要是commonJS、AMD和应用AMD的requireJS、CMD和应用CMD的seaJS(不过现在seaJS貌似用的人挺少的,就不详细说明了)
js在发展过程
有几个阶段:
过程式的JavaScript→面向对象的javascript→面相模块的javascript
- 原始写法:
模块就是实现特定功能的一组写法:
|
|
上面的m1和m2构成了一个模块,使用的时候直接调用就可以了。
这种做法的缺点是:“污染”了全局变量,无法保证不与其他的模块中的变量发生冲突,并且模块之间看不出直接的关系
- 对象写法
这时已经发展到了第二个阶段了,面向对象的javascript,这时就就是我们所熟悉的面向对象的编程思想了,继承、原型、闭包等都是面向对象的内容
|
|
上面的函数m1和m2都封装在module1对象中,使用的时候,直接调用这个对象的属性就可以了module1.m1(),
但是这样写会暴露所有的模块成员,内部状态可以被外部改变。比如
|
|
- 立即执行函数写法
这种写法可以达到不暴露私有成员的目的
|
|
这里只能通过return的函数名来调用函数,达到了包装函数的作用
这里还运用了闭包的思想,在函数module1的外部调用了函数内的变量_count
闭包
闭包就是能够读取其他函数内部变量的函数,在这个例子中,m1就是一个闭包的例子,在m1中引用了module的局部变量_count,然后在module1的return中又将m1作为返回值,这样就可以在module1的外部访问module1中的局部变量_count
- 放大模式
如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用“放大模式”(将原有的模块扩充,达到放大的目的。实则就是给模块添加新的方法)
|
|
在大型项目中我们是不能将所有的js都放在一个文件夹下的,这样就可以使用方法模式来实现。首先,我们导入一个模块,并为它添加属性,最后再导出他,这样就将原来的函数module1“放大”
在执行完这段代码之后,我们的模块就可以拥有一个新的 module1.m3的公有方法,这个放大文件也会维护它自己的私有内置状态和导入的对象
- 宽放大模式
宽放大就是为了解决放大模式的缺点的,放大模式有一个致命的缺点就是不确定加载顺序,可能加载一个不存在空对象
|
|
在放大模式中,如果没有上面定义module1模块时,是会报错的,经过改进,就算在执行这段代码的时候,module1 即使没有定义也不会报错,也会创建module1.m3()这个公有方法
在经历了这些阶段,我们现在的主流js编程方式是面相模块的javascript
为什么使用模块化的js
使用模块化的javascript主要是为了解决两个问题(个人理解,以下均是,欢迎指正(^-^)V)
- 命名冲突,是不是有点同感了!!!
闭包作用域和命名空间其实已经解决了这个问题了,但是js模块化更加优雅的解决了这个问题
通过export暴露接口,不需要命名空间,不需要全局变量 - 文件依赖
文件的加载顺序很重要啊有没有,一不小心就犯错了,还是很不好找原因啊!
js模块化的解决方式是使用require引入依赖,使依赖内置,开发者只需关心当前模块的依赖,其他事情 SeaJS都会自动处理好。对模块开发者来说,这是一种很好的 关注度分离 ,能让程序员更多地享受编码的乐趣。
commonJS
为了解决上述的两个问题,commonJS就出现了,node应该是将commonJS运用的最出名的
什么是commonJS
commonJS就是一套javascript使用模块化的规范,原本是想统一服务器端和浏览器,所以起名叫做commonJS,但是commonJS在浏览器上使用有诸多不便,所以诞生了AMD(稍后详细说明)
使用commonJS编写node
以下是node写模块化的一个示例:
- math.js
|
|
- increment.js
|
|
- main.js,该文件为入口文件
|
|
从上述代码可以看到:
- node要求一个js文件对应一个模块。
- 使用exports导出API
- 使用require加载其它模块
commonJS的缺点
因为node也可以进行服务器端的编程,那么使用commonJS写模块是不是就可以前后端兼顾呢,事与愿违,commonJS在浏览器的支持还是有很多不足的
- 服务器端的js模块就在本地,浏览器端则需要通过网络请求
- 服务器端可以很容易进行同步或异步请求模块,浏览器则问题多多
如下
|
|
event.bind(el, 'click', function(),在第一行require(“event”)之后运行,因此必须等event加载完成。也就是说,如果加载时间很长,整个应用就会停在那里等。
这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于”假死”状态。
因此,浏览器端的模块,不能采用”同步加载”(synchronous),只能采用”异步加载”(asynchronous)。这就是AMD规范诞生的背景。
AMD
基于上述两点的commcommonJS的缺点,AMD就孕育而生,
什么是AMD
AMD可以理解为异步模块定义,可以认为是浏览器中的模块规范
AMD是”Asynchronous Module Definition”的缩写,意思就是”异步模块定义”。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
为什么说AMD可以解决commonJS在浏览器上的不足呢?
|
|
math.add()与math模块加载不是同步的,浏览器不会发生假死。所以很显然,AMD比较适合浏览器环境。
AMD只有一个简洁的API,是的,只有一句API!!!并且其中的两个参数都是可选的!!!
|
|
其中:
- id: 模块标识,可以省略。
- dependencies: 所依赖的模块,可以省略。
- factory: 模块的实现,或者一个JavaScript对象。
- id遵循CommonJS Module Identifiers 。dependencies元素的顺序和factory参数一一对应。
简单的AMD模式应用
以下是使用AMD模式开发的简单的三层结构(基础库/UI层/应用层)base.js
|
|
ui.js
|
|
`page.js``
data.js
|
|
以上同时演示了define的三种用法
- 定义无依赖的模块(base.js)
- 定义有依赖的模块(ui.js,page.js)
- 定义数据对象模块(data.js)
以上的id参数都为空,这是为什么呢?
|
|
这种具名函数是不推荐的,一般由打包工具合并多个模块到一个js文件中时使用。
更多请阅读:
