前端发展到今天,已经有不少模块化的方案,比如
CommonJS(常用在服务器端,同步的,如nodejs)
AMD(常用在浏览器端,异步的,如requirejs)(Asynchronous Module Definition)
CMD(常用在浏览器端,异步的,如seajs)
UMD(AMD&& CommonJS) 这些模块化规范的核心价值都是让 JavaScript 的模块化开发变得简单和自然。
服务器端模块 在服务器端,所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。
浏览器端模块: 在浏览器端,所有的模块都放在服务器端,同步加载,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。因此,浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)。
CommonJs 是服务器端模块的规范,Node.js采用了这个规范。 CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。 像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以CommonJS规范比较适用。
CommonJS模块的特点如下: 所有代码都运行在模块作用域,不会污染全局作用域。 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。 模块加载的顺序,按照其在代码中出现的顺序。
CommonJS语法
1、定义模块
根据CommonJS规范,每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、都是私有的,对其他文件不可见。如果想在多个文件分享变量,必须定义为顶层对象的属性,如:global.warning = true;上面代码的warning变量,可以被所有文件读取。当然,这样写法是不推荐的。
2、模块的接口
CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。
3、加载模块
加载模块使用require方法,该方法读取一个文件并执行,返回文件内部module.exports对象 注:不同的实现对require时的路径有不同要求,一般情况可以省略js拓展名,可以使用相对路径,也可以使用绝对路径,甚至可以省略路径直接使用模块名(前提是该模块是系统内置模块)
AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义". RequireJS是一个工具库,主要用于客户端的模块管理。它可以让客户端的代码分成一个个模块,实现异步或动态加载,从而提高代码的性能和可维护性。它的模块管理遵守AMD规范。 RequireJS的基本思想是,通过define方法,将代码定义为模块;通过require方法,实现代码的模块加载。 首先,将require.js嵌入网页,然后就能在网页中进行模块化编程了。<script data-main="scripts/main" src="scripts/require.js"></script>上面代码的data-main属性可省略,用于指定主代码所在的脚本文件,在上例中为scripts子目录下的main.js文件。用户自定义的代码就放在这个main.js文件中。
1、定义模块
define(id?, dependencies?, factory);
id:可选参数,用来定义模块的标识,如果没有提供该参数,脚本文件名(去掉拓展名)
dependencies:是一个数组,表示当前模块的依赖
factory:工厂方法,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值
2、加载模块
require([dependencies], function(){}); 第一个参数是一个数组,表示所依赖的模块,第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块
CMD 即Common Module Definition通用模块定义 CMD规范是国内发展出来的,CMD有个浏览器的实现SeaJS 在 CMD 规范中,一个模块就是一个文件。
Sea.js 是一个成熟的开源项目,核心目标是给前端开发提供简单、极致的模块化开发体验。这里不多做介绍,有兴趣的可以查看官方文档。 使用 Sea.js,在书写文件时,需要遵守 CMD (Common Module Definition)模块定义规范。一个文件就是一个模块。 首先要在页面中引入 sea.js 文件,这一般通过页头全局把控,也方便更新维护。想在页面中使用某个组件时,只要通过 seajs.use 方法调用。
CMD语法
1、定义模块define(id?, deps?, factory)
字符串id为模块标识,数组deps为模块依赖; 参数 factory 可以是一个函数,也可以为对象或者字符串。
factory是一个函数,有三个参数: require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口:require(id) exports 是一个对象,用来向外提供模块接口 module 是一个对象,上面存储了与当前模块相关联的一些属性和方法 注: 带 id 和 deps 参数的 define 用法不属于 CMD 规范,而属于 Modules/Transport 规范。
Sea.js 带来的两大好处: 通过 exports 暴露接口。这意味着不需要命名空间了,更不需要全局变量。这是一种彻底的命名冲突解决方案。 通过 require 引入依赖。这可以让依赖内置,开发者只需关心当前模块的依赖,其他事情 Sea.js 都会自动处理好。对模块开发者来说,这是一种很好的 关注度分离,能让程序员更多地享受编码的乐趣。
1.AMD推崇依赖前置。 在定义模块的时候就要声明其依赖的模块; CMD推崇依赖就近,只有在用到某个模块的时候再去require ;
2.AMD依赖模块的执行顺序和书写顺序不一定一致;CMD模块的执行顺序和书写顺序是完全一致的
3.对于依赖的模块AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行(根据写法不同,处理方式不通过)。
UMD (Universal Module Definition)通用模块规范 是AMD和CommonJS两者的结合 这个模式中加入了当前存在哪种规范的判断,所以能够“通用”,它兼容了AMD和CommonJS,同时还支持老式的“全局”变量规范: 应用UMD规范的js文件其实就是一个立即执行函数。在执行UMD规范时,会优先判断是当前环境是否支持AMD环境,然后再检验是否支持CommonJS环境,否则认为当前环境为浏览器环境( window )。当然具体的判断顺序其实是可以调换的。
1、更好的分离
如果要加载多个就得放置多个 script 标签,如果是加载模块的话,对于 HTML 和 JavaScript 分离很有好处,在某些场景下这个分离度很重要。
2、更好的代码组织方式 如果单个文件越来越大,维护起来出问题的几率也会越来越大,如果是多人开发,不同的代码风格,超多的业务逻辑混杂在一起,不要说维护了,光想想感觉都很麻烦。模块式的开发,一个文件就是一个模块,控制了文件的粒度,每个模块可以专注于一个功能。
3、按需加载
4、避免命名冲突
5、更好的依赖处理