Cover image
Hero image

托码特人

分享科技与人文

一个关注互联网的技术博客

浅谈js模块化

没有模块化之前的操作:script 标签引入 js 文件,相互罗列,但是被依赖的放在前面,否则使用就会报错。命名空间是由单一的全局对象来描述的,如JQuery$

关于 JS 模块化的问题,一直被忽视,今天重来拿出来谈谈:CommonJSAMDUMD,以及Harmony等…

CommonJS

主要用于 NodeJS 后端,采用同步的方式加载文件,它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global。实际使用时,用 module.exports 定义当前模块对外输出的接口(不推荐直接用 exports),用 require 加载模块。

特点:

  1. 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
  2. 模块加载会阻塞接下来代码的执行,需要等到模块加载完成才能继续执行——同步加载。

AMD

在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,CommonJS 不适合浏览器端模块加载,更合理的方案是使用异步加载,即:ADM:异步模块定义,Asynchronous Module Definition

AMD 规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。

一点优点:适合在浏览器环境中异步加载模块、并行加载多个模块;
一点缺点:不能按需加载、开发成本大。

CMD

CMD 是在 AMD 基础上改进的一种规范,和 AMD 不同在于对依赖模块的执行时机处理不同,CMD 是就近依赖,而 AMD 是前置依赖。此规范其实是在 sea.js 推广过程中产生的。

/** AMD写法 **/
define(["a", "b", "c", "d", "e", "f"], function (a, b, c, d, e, f) {
  // 等于在最前面声明并初始化了要用到的所有模块
  a.doSomething();
  if (false) {
    // 即便没用到某个模块 b,但 b 还是提前执行了
    b.doSomething();
  }
});

/** CMD写法 **/
define(function (require, exports, module) {
  var a = require("./a"); //在需要时申明,很明显CMD可以做到按需加载
  a.doSomething();
  if (false) {
    var b = require("./b");
    b.doSomething();
  }
});

UMD

通用模块定义(UMD,Universal Module Definition)。兼容 AMD 和 commonJS 规范的同时,还兼容全局引用的方式。可运行在浏览器或服务器环境。

无导入导出规范,实现原理如下:

  1. 先判断是否支持 Node.js 模块格式(exports 是否存在),存在则使用 Node.js 模块格式。
  2. 再判断是否支持 AMD(define 是否存在),存在则使用 AMD 方式加载模块。
  3. 前两个都不存在,则将模块公开到全局(window 或 global)。
(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;
});

ES6 Module

旨在成为浏览器和服务器通用的模块解决方案。

其模块功能主要由两个命令构成:export 和 import。export 命令用于规定模块的对外接口,import 命令用于输入其他模块提供的功能。import 命令会被 JavaScript 引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。也正因为这个,使得静态分析成为可能。

ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

特点:

  1. 按需加载(编译时加载)
  2. import 和 export 命令只能在模块的顶层,不能在代码块之中(如:if 语句中),import()语句可以在代码块中实现异步动态按需动态加载

语法:

  1. 导入:import {模块名 A,模块名 B…} from ‘模块路径’
  2. 导出:export 和 export default
  3. import(‘模块路径’).then()方法

注意:export 只支持对象形式导出,不支持值的导出,export default 命令用于指定模块的默认输出,只支持值导出,但是只能指定一个,本质上它就是输出一个叫做 default 的变量或方法。

规范:

/*错误的写法*/
// 写法一
export 1;

// 写法二
var m = 1;
export m;

// 写法三
if (x === 2) {
  import MyModual from './myModual';
}

/*正确的三种写法*/
// 写法一
export var m = 1;

// 写法二
var m = 1;
export {m};

// 写法三
var n = 1;
export {n as m};

// 写法四
var n = 1;
export default n;

// 写法五
if (true) {
    import('./myModule.js')
    .then(({export1, export2}) => {
      // ...·
    });
}

// 写法六
Promise.all([
  import('./module1.js'),
  import('./module2.js'),
  import('./module3.js'),
])
.then(([module1, module2, module3]) => {
   ···
});

System

systemjs 是模块加载器,可以导入任何流行格式的模块(CommonJS、UMD、AMD、ES6)。它是工作在 ES6 模块加载 polyfill 之上,它能够很好的处理和检测所使用的格式。 systemjs 也能使用插件转换 es6( 用 Babel 或者 Traceur)或者转换 TypeScript 和 CoffeeScript 代码。你只需要在导入你的模块之前使用 System.config({ … }) 进行系统配置。

Dynamic ES module loader

Harmony

未来的模块,目前仍处于建设性阶段。

支持基于远程来源的模块,如:

module cakeFactory from "http://addyosmani.com/factory/cakes.js";
cakeFactory.oven.makeCupcake( "sprinkles" );
cakeFactory.oven.makeMuffin( "large" );

一些资料

赞赏

声明: 本文内容由托码斯创作整理,由于知识水平和时效性问题,行文可能存在差错,欢迎留言交流。读者若需转载,请保留出处,谢谢!