javascript 模块化

469 阅读4分钟

一.node服务端的模块化 CommonJs

示例

// 导入
require("module");
require("../app.js");
// 导出
exports.getStoreInfo = function() {};
module.exports = someValue;

特点:同步加载模块,运行时加载。主要符合服务端的应用场景,无法应用于浏览器客户端。

一.AMD模块化规范(代表库 require.js)

示例

// 定义模块
define("module", ["dep1", "dep2"], function(d1, d2) {...});
// 加载模块
require(["module", "../app"], function(module, app) {...});

特点:异步加载模块。适用于浏览器端,可并行加载多个模块,但是提升了加载逻辑的复杂度,加大了开发维护成本。

一.CMD模块化规范(代表库 sea.js 阿里玉伯作品)

示例

define(function(require, exports, module) {
  var a = require('./a');
  a.doSomething();
  // 依赖就近书写,什么时候用到什么时候引入
  var b = require('./b');
  b.doSomething();
});

特点:依赖就近,延迟执行,可运行在node中。

AMD 与 CMD 的区别

  1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从2.0开始,也改成了可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.
  2. AMD推崇依赖前置;CMD推崇依赖就近,只有在用到某个模块的时候再去require。

示例

// AMD
define(['./a', './b'], function(a, b) {  // 依赖必须一开始就写好  
   a.doSomething()    
   // 此处略去 100 行    
   b.doSomething()    
   ...
});
// CMD
define(function(require, exports, module) {
   var a = require('./a')   
   a.doSomething()   
   // 此处略去其他业务代码
   var b = require('./b') 
   // 依赖可以就近书写   
   b.doSomething()
   // ... 
});

UMD规范

  1. UMD是AMD和CommonJS的糅合
  2. CommonJS 模块以服务器第一原则发展,选择同步加载,它的模块无需包装。
  3. AMD 以浏览器第一原则发展异步加载模块。
  4. UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式;在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。 示例
(function (window, factory) {
    if (typeof exports === 'object') {
    
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
    
        define(factory);
    } else {
    
        window.eventUtil = factory();
    }
})(this, function () {
    //module ...
});

import ES6的模块化(目前前端开发主流模块化方案)

示例

// 导出
export function aaa() {...};
export var age = 18;
export default ...
// 导入
import "/app";
import React from "react";
import { Component } from “react”;

特点:js自己官方标准,以上都是过渡方案,但是目前浏览器直接支持不是很好 node v13版本能直接支持。

import 和 require 的区别(重要)

require使用与CommonJs规范,import使用于Es6模块规范;所以两者的区别实质是两种规范的区别;

  • CommonJS:
  1. 对于基本数据类型,属于复制。即会被模块缓存;同时,在另一个模块可以对该模块输出的变量重新赋值。
  2. 对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。
  3. 当使用require命令加载某个模块时,就会运行整个模块的代码。
  4. 当使用require命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
  5. 循环加载时,属于加载时执行。即脚本代码在require的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。
  • ES6模块
  1. ES6模块中的值属于【动态只读引用】。
  2. 对于只读来说,即不允许修改引入变量的值,import的变量是只读的,不论是基本数据类型还是复杂数据类型。当模块遇到import命令时,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
  3. 对于动态来说,原始值发生变化,import加载的值也会发生变化。不论是基本数据类型还是复杂数据类型。
  4. 循环加载时,ES6模块是动态引用。只要两个模块之间存在某个引用,代码就能够执行。
  • 最后:require/exports 是必要通用且必须的;因为事实上,目前你编写的 import/export 最终都是编译为 require/exports 来执行的。