为什么需要模块化
在ES6出现之前,JS语言本身并没有提供模块化能力,这为开发带来了一些问题,其中最重要的两个问题应当是全局污染和依赖管理混乱。
// file a.js var name = 'aaa'; var sayName = function() { console.log(name); };
<!-- file index.html --> <script src='xxx/xxx/a.js'></script> <script> sayName(); // 'aaa' // code... var name = 'bbb'; sayName(); // 'bbb' </script>
上面的代码中,我们两次调用a.js所提供的sayName函数输出了不同结果,很明显这是因为两个文件都对变量name进行了赋值,因此相互之间造成了影响。当然我们可以在编写代码时注意不要定义已存在的变量名,但是当一个页面引用了10几个几百行的文件时,记住所有已经定义过的变量显然不太现实。
// file a.js var name = getName(); var sayName = function() { console.log(name) };// file b.js var getName = function() { return 'timo'; };<script src='xxx/xxx/b.js'></script> <script src='xxx/xxx/a.js'></script> <script> sayName(); // 'timo' </script><script src='xxx/xxx/a.js'></script> <script src='xxx/xxx/b.js'></script> // Uncaught ReferenceError: getName is not defined上面的代码说明,多个文件有依赖关系时,我们需要确保其引入的顺序,从而保证运行某个文件时,其依赖已经提前加载,可以想象,面对越大型的项目,我们需要处理的依赖关系也就越多,这既麻烦又容易出错。
而为了解决这些,社区中出现了许多为JS语言提供模块化能力的规范,借助这些规范,能让我们的开发更加方便安全。
常见模块化方案
CommonJS
CommonJS是由社区提出的模块化方案中的一种,Node.js遵循了这套方案。
基本写法
// file a.js var obj = { sayHi: function() { console.log('I am timo'); }; }; module.exports obj;// file b.js var Obj = require('xxx/xxx/a.js'); Obj.sayHi(); // 'I am timo'上面的代码中,文件a.js是模块的提供方,文件b.js是模块调用方。
规范
每个文件都是一个模块;
在模块内提供module对象,表示当前模块;
模块使用exports对外暴露自身的函数/对象/变量等;
模块内通过require()方法导入其他模块;
CommonJS的规范,简单来说就是上面4条,可以对照基本写法中的例子理解一下,在实际实现中,Node.js虽然遵循CommonJS规范,但是仍然对其进行了一些调整。
AMD
AMD是模块化规范中的一种,RequireJS遵循了这套规范。
基本用法
// file a.js define('module', ['m', './xxx/n.js'], function() { // code... })上面的代码中,文件a.js向外导出了模块;
规范
AMD中,暴露模块使用define函数
define(moduleName, [], callback);如上面代码,define函数共有三个参数
moduleName该参数可以省略,表示该模块的名字,一般作用不大
['name1', 'name2'],第二个参数是一个数组,表示当前模块依赖的其他模块,如果没有依赖模块,该参数可以省略
callback,第三个参数是必传参数,是一个回调函数,内部是当前模块的相关代码
ADM的特点是依赖前置,这是ADM规范与接下来要介绍的CMD规范最大的不同,依赖前置是指:在运行当前加载模块回调前,会首先将所有依赖包加载完毕,也是就是define函数的第二个参数中指定的依赖包。
define(function(require, exports, module) { var a = require('./a') a.doSomething(); // code... var b = require('./b') // code... })上面代码是CMD规范导出模块的基本写法;
规范
从写法可以看出,CMD的写法和AMD非常像,其主要区别是对于依赖加载时机的不同,上面已经说过,AMD是依赖前置,而CMD规范推崇就近原则,简单说就是在模块运行前并不加载依赖,模块运行过程中,当需要某个依赖时,再去进行加载。
UMD
CommonJS、AMD、CMD并行的状态下,就需要一种方案能够兼容他们,这样我们在开发时,就不需要再去考虑依赖模块所遵循的规范了,而UMD的出现就是为了解决这个问题。
基本写法
(function (root, factory) { if (typeof define === 'function' && define.amd) { //AMD define(['jquery'], factory); } else if (typeof exports === 'object') { //Node, CommonJS之类的 module.exports = factory(require('jquery')); } else { //浏览器全局变量(root 即 window) root.returnExports = factory(root.jQuery); } }(this, function ($) { //方法 function myFunc(){}; //暴露公共方法 return myFunc; }));上面的代码是UMD的基本写法,从代码就可以看出,其能够同时支持CommonJS规范和AMD规范。
ES6 module
上面分别介绍了CommonJS、AMD、CMD和UMD,他们都是社区对于JS实现模块化的贡献,这个规范其产生的根本原因,都是JS语言自身没有模块化能力,而目前,在JS最新的语言规范ES6中,已经为JS增加了模块化能力,而JS自身的模块化方案,完全能够替代目前社区提出的各类规范,且能够做到浏览器端和Node端通用。
ES6中的模块化能力由两个命令构成:export和import,export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
export命令
ES6中一个文件就是一个模块,模块内部的变量/函数等外部是无法访问的,如果希望将内部的函数/变量等对外暴露,供其他模块进行使用,就需要通过export命令进行导出
// file a.js export let a = 1; export let b = 2; export let c = 3;// file b.js let a = 1; let b = 2; let c = 3; export {a, b, c}// file c.js export let add = (a, b) => { return a + b; };上面三个文件的代码,都是通过export命令导出模块内容的示例,其中a.js文件和b.js文件都是导出模块中的变量,作用完全一致但写法不同,一般我们更推荐b.js文件中的写法,原因是这种写法能够在文件最底部清楚地知道当前模块都导出了哪些变量。
import命令
模块通过export命令导出变量/函数等,是为了让其他模块能够导入去使用,在ES6中,文件导入其他模块是通过import命令进行的
// file d.js import {a, b, c} from './a.js';上面的代码中,我们引入了a.js文件中的变量a、b、c,import在引入其他模块内的函数/变量时,必须与原模块所暴露出来的函数名/变量名一一对应。
同时,import命令引入的值是只读的,如果尝试对其进行修改,则会报错import {a} d from './a.js'; a = 2; // Syntax Error : 'a' is read-only;export default命令
从上面import的介绍可以看到,当需要引入其他模块时,需要知道此模块暴露出的变量名/函数名才可以,这显然有些麻烦,因此ES6还提供了一个import default命令
// file a.js let add = (a, b) => { return a+b }; export default add;// file b.js import Add from './a.js'; Add(1, 2); // 3上面的代码中,a.js通过export default命令导出了add函数,在b.js文件中引入时,可以随意指定其名称
export default命令是默认导出的意思,既然是默认导出,显然只能有一个,因此每个模块只能执行一次export default命令,其本质是导出了一个名为default的变量或函数。
以上就是为什么需要模块化?js中常用模块化方案介绍的详细内容,更多请关注php中文网其它相关文章!
网站建设是一个广义的术语,涵盖了许多不同的技能和学科中所使用的生产和维护的网站。
……