🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 前端模块化 主要介绍CommonJS模块 和 ES6模块的区别,不感兴趣可以跳过本节内容。 ## 1、ES6模块 ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。 **1.1 CommonJs模块运行时加载(对象),ES6模块编译时输出接口** 引用[阮一峰老师的例子][1]来进行说明: CommonJS的输出缓存机制,使得模块被加载时,会被缓存起来,将整个模块当作一个对象输出。 ```js // CommonJS模块 let { stat, exists, readFile } = require('fs'); // 等同于 let _fs = require('fs'); let stat = _fs.stat; let exists = _fs.exists; let readfile = _fs.readfile; ``` 上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。 并且,由于缓存机制,重复加载同一模块,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。 ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。 ```js // ES6模块 import { stat, exists, readFile } from 'fs'; ``` 上面代码的实质是从fs模块加载3个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比CommonJS模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。 **1.2 CommonJs 模块输出的是一个值的复制,ES6模块编译时输出接口(动态只读引用)** CommonJs 模块: 对于基本数据类型,属于复制。即会被模块缓存。同时,在另一个模块可以对该模块输出的变量重新赋值,而不会影响其他模块。 对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。 ES6 模块: ES6 模块的运行机制与 CommonJS不一样。JS引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。 **1.3 对于循环引用的处理** CommonJS 模块: 其重要特性是加载时执行,即脚本代码在require的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。 例如: ```js // a.js console.log('a start'); var b = require('./b.js'); console.log('a doing'); console.log(b); exports.a = 'a模块的变量'; // b.js console.log('b start'); var a = require('./b'); console.log('b doing') console.log(a); exports.b = 'b模块的变量'; // 要理解CommonJS模块是同步执行 // 当执行 node a.js时,输出为: // a start // b start // b doing // {} // a doing // {b: 'b模块的变量'} ``` ES6 模块: ES6 模块是动态引用,如果使用import从一个模块加载变量,那些变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。 上面例子的ES6改写: ```js $ node –experimental-modules a.mjs // a.js console.log('a start'); import {b} from ./b.mjs'; console.log('a doing'); console.log(b); exports.a = 'a模块的变量'; // b.js console.log('b start'); import {a} from ./a.mjs'; console.log('b doing') console.log(a); exports.b = 'b模块的变量'; // 输出 // b start // b ding // ReferenceError: a is not defined ``` 分析:执行a.mjs以后,引擎发现它加载了b.mjs,因此会优先执行b.mjs,然后再执行a.mjs。接着,执行b.mjs的时候,已知它从a.mjs输入了foo接口,这时不会去执行a.mjs,而是认为这个接口已经存在了,继续往下执行。执行到第三行console.log(foo)的时候,才发现这个接口根本没定义,因此报错。 解决方法: ```js // a.js // exports.a = 'a模块的变量'; 改为 export function a () {console.log('a模块的函数')} // b.js // exports.b = 'b模块的变量'; export function b () {console.log('b模块的函数')} // 输出 // b start // b ding // [Function: a] // a start // b doing // [Funcion: b] ``` 分析:function a(){...}具有提升作用。因此,在import之前,已经存在函数a。 下面简单介绍前端的其它模块化规范。 ## 2、CommonJS规范 1、核心思想: > 把一个文件当做一个模块,通过require方法同步加载模块。 2、应用场景 > Node.js的服务端编程加载的模块主要存在于服务器磁盘,所以加载速度较快,对node这种单线程,影响较小。因此,CommonJS同步加载模块方案主要应用于**node**服务端。 3、不适用于浏览器环境 > 浏览器加载方式与服务端完全不同。如果浏览器请求服务器资源时,会由于某个请求加载的时间过长导致浏览器阻塞,不会往下执行,所以就会出现网页打开缓慢的现象。并且,浏览器端是以插入<script>标签的形式来加载资源(ajax方式不行,有跨域问题),没办法让代码同步执行,使用commonJS同步写法会直接报错。 4、示例 ```js // 1.js module.export = function() { say: function() { alert("你好"); } } // main.js var a = require('./a.js'); a.say(); ``` 浏览器环境下必须采用异步模式。所以就有了ES6模块、 AMD,CMD 解决方案。 ## 3、AMD规范 AMD(异步模块定义)是RequireJS在推广过程中对模块化定义的规范化产出。 1、核心思想: > 异步加载所需的模块,然后在回调函数中执行主逻辑。 **可以并行加载多个模块,等所有模块都加载并且解释执行完成后**,才会执行接下来的代码, 2、吐槽点 > “提前执行”:所有依赖模块会被预先下载,并且提前执行(不管该模块的调用时机)。“提前执行”的性能消耗是不容忽视。 3、懒加载 > AMD保留了commonjs中的require、exprots、module这三个功能。因此,为了解决“提前执行”的性能消耗,依赖的模块不写在dependencies数组中, 4、示例 ```js // a.js define(function(){ return { say: function(){ console.log('hello, a.js'); } } }); // b.js define(function(){ return { say: function(){ console.log('hello, a.js'); } } }); // main.js require(['a', 'b'], function(a, b){ a.say(); $('#b').click(function(){ b.say(); }); }) // AMD懒加载方式 // main.js require(['a'], function(a){ a.say(); $('#b').click(function(){ require(['b'], function(b){ b.say(); }); }); }) ``` 这种懒加载减轻了初始化操作。**但是,在需要执行b.say\(\)时,需要实时下载代码然后在回调中才能执行,用户的操作会有明显的延迟卡顿。** ## 4、CMD规范 CMD\(同步模块定义\)是SeaJS\(淘宝团队\)在推广过程中对模块化定义的规范化产。 1、示例 ```js // a.js define(function(require, exports, module){ return { say: function(){ console.log('hello, a.js'); } } }); // b.js define(function(require, exports, module){ return { say: function(){ console.log('hello, b.js'); } } }); // main.js define(function(require, exports, module){ var a = require('a'); a.say(); $('#b').click(function(){ var a = require('b'); b.say(); }); }); }) ``` a.js和b.js都会预先下载,但是,**b.js不会预先执行(即用即返回)。** [1]: http://es6.ruanyifeng.com/#docs/module-loader