为什么要使用模块化?
- 网站更多转型向网络应用
- 网站规模的增加导致代码的复杂度增加
- 性能优化,需要更少的HTTP请求
模块化的作用?
- 解决命名冲突问题
- 文件之间依赖管理
- 代码间的解耦,提高复用性。
CMD、AMD、CommonJS 规范分别指什么?有哪些应用?
现代模块实现的基石:
|
以上的形式只实现了封装性,却不能保证模块之间的加载执行过程。(顺序)
CommonJS规范
由NodeJS社区的繁荣而兴起,它提供了非常简单的模块输出与引入的方式:
|
|
特点:
- 通过
exports
或者module.exports
来暴露模块对象 - 所有代码都运行在模块作用域,不会污染全局作用域。
- 模块可以多次加载,但是只会在第一次加载时运行并且缓存,以之后加载直接读取缓存结果。要想让模块再次运行,必须清除缓存。
模块加载的顺序,按照其在代码中出现的顺序。
同步/阻塞式加载
|
|
main.js
执行结果:
|
But! 这一切都是在服务端NodeJS环境下执行的,而在浏览器环境下,同步式加载而导致的长时间等待和卡顿是会让用户抓狂的,而且浏览器端代码的引入执行是通过script动态添加的方式,由于script标签的异步加载特性,无法保证各代码文件的加载顺序,从而引起报错。
AMD/CMD规范的出现解决了针对浏览器环境的模块加载问题。
Asynchronous Module Definition - AMD
见名知意,模块可以异步加载,采用回调方式进行依赖加载->执行代码,保证了模块的运行正常,
最典型的应用便是RequireJS。
策略: 预加载,预执行,返回对象作为模块。
|
|
关于AMD的执行机制,如何验证?
|
|
|
|
执行结果:(实际代码封装了render模块,console.log全部替换为render,将结果渲染到页面上)
我们可以清楚的看见,所有dependencies都进行了预加载&执行,这是与接下来要介绍的CMD规范最大的不同之处。在语法层面,与CommonJS语法互通,AMD为CommonJS留下了grammar sugar, 上面index.js
中的var math = require('math');
写法便是典型的CommonJS写法,即依赖就近(虽然依旧在头部早早引入了依赖);以返回一个对象作为模块对象。
Common Module Definition - CMD
CMD规范由来源于玉伯开发的Sea.js,一个文件便是一个模块。
策略: 按需加载
|
|
加载 & 执行机制观测:
使用同样的代码结构,换为CMD语法,执行AMD例子中的index.js, 为了观察到require和执行的顺序,我们将index.js
的代码顺序修改一下:
|
执行结果如下:
跟我们的代码顺序完全一致,而不是像AMD一样预先加载&立刻执行。
以上两个范例代码: github
性能分析
- 对于AMD,策略为一劳永逸,在规模较大,模块较多时,首屏加载性能可能稍差,但是后续的运行会非常顺畅。
- 对于CMD,策略为按需取物,即时执行,与AMD同等环境下,首屏加载性能较快,在后续运行中可能会有卡顿现象。
主流?
- AMD
- requireJS 过去式
- CMD
- seaJS 过去式
- Browserify 小而美
- Webpack 大而全
requireJS应用DEMO
- 应用requireJS,打包优化 (辣鸡uglify,毁我青春)
- TAB支持可见区域自动切换 (已做debounce处理,下次试试throttle,underscoreJS的源码真心得好好看看)
- 首屏自动轮播,支持前后按钮点击跳转,缩略导航图点击跳转。(未作animate.stop()处理,目测animate自带debounce)
- jsonp请求数据,点击按钮,可无限请求。 (小坑: github会对http请求做保守拦截,将请求地址协议改为https可行,只是会报警内容混合,视觉上无碍)
- 时光轴采用懒加载模式 (封装了LoadTrigger函数,对于事件触发引起回调在数据结构上本质属于data consumer与data producer的关系,只在乎谁push谁。所以对这种模式封装(虽然我封装的很不抽象),传入触发事件类型以及回调和参数即可)
- 大于设置scrolltop尺寸右下角浮现gotop按钮 (个人认为还是手动在html内添加元素加上对应classname更加符合解耦的原则,用过的框架大多也是这么设计,然而我还是偷懒用js添加node…)
PITFALL ALERT!
requireJS的optimizer里内置的uglifyJS不支持ES6语法,即时使用babel官网提供针对requireJS的build方案也不好使,建议大家直接写ES5吧,毕竟requireJS已经消逝了….勿踩坑。
总结
CommonJS:
- 代表: NodeJS
- 特点: 模块具有独立作用域,运行代码不会污染全局。 模块的引入会产生缓存,加载顺序遵循引入顺序,阻塞式加载(这就是为什么需要AMD,CMD的原因)。
- 语法: // In a.jsconst num = 100const decrement = (num) => num--moudle.exports = {num: a,decrement: decrement}
// In b.jsconst { num, decrement } = require('./a') //./a.jsconsole.log( decrement(num) ) // 99AMD:
- 代表: requireJS
- 特点:预加载,预执行,首屏性能较差。
- 语法: // In a.jsdefine('a', [/* nothing */], function(){const num = 100const decrement = (num) => num--return {num: num,decrement: decrement}})
// In b.jsdefine('b', ['require', 'a'], function(require){const { num, decrement } = require('a')console.log(decrement(num)) // 99})CMD:
- 代表: sea.js
- 特点: 按需加载&执行
- 语法://In a.jsdefine(function(exports, module) {const num = 100const decrement = (num) => num--// or return { num: num , decrement: decrement}module.exports = {num: num,decrement: decrement}})
// In b.jsdefine(function(require){const { num, decrement } = require('./a')// we can also use async require/*require.async('./a', function({ num, decrement }) {console.log(decrement(num)) //99})*/console.log( decrement(num) ) // 99})