JS 模块化

144 阅读6分钟

1、为什么要模块化

2、什么是模块化

  • 将一个复杂的程序依据一定的规则封装成几个块,并进行组合起来

立即执行函数 IIEF

2、模块化分类

1、CommonJS/CJS

  • 同步加载

2、AMD(异步模块定义)

  • 异步加载,回调函数,依赖模块,加载模块,

image.png

我们可以看到执行的时候,a和b先加载,后面才从main开始执行。所以require一个模块的时候,模块会先被加载,并返回一个对象,并且这个对象是整体加载的,也就是常说的 依赖前置。

3、CMD (common moduledefinition)

  • 依赖前置

image.png

看到执行结果,会在真正需要使用(依赖)模块时才执行该模块,感觉这好像和我们认知的一样,毕竟我也是这么想的执行顺序,但是看前面AMD的执行结果,是先把a和b都加载以后,才开始执行main的.

4、UMD

  • 通用 服务器和浏览器端同时使用
  • 帮你判断 用AMD还是CommonJS
let MYAPP = {
  count: 0,
  foo: function(){},
  bar: function(){}
}

MYAPP.foo();

(function (factory) {
  typeof define === 'function' && define.amd ? define(factory) :
  factory();
}((function () { 'use strict';

  let MYAPP = {
    count: 0,
    foo: function(){},
    bar: function(){}
  };

  MYAPP.foo();

})));


(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('crypto'), require('jsdom'), require('canvas'), require('jsdom/lib/jsdom/living/generated/utils')) :
    typeof define === 'function' && define.amd ? define(['crypto', 'jsdom', 'canvas', 'jsdom/lib/jsdom/living/generated/utils'], factory) :
      (global = global || self, global.OmoSDK = factory(global.crypto, global.jsdom, global.canvas, global.utils));
}(this, (function (crypto, jsdom, canvas, utils) {
})));

UMD模块(通用模块)

(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
    (global.libName = factory());
}(this, (function () { 'use strict';})));

如果你在js文件的头部看到这样的代码,这个js文件使用的规范就是UMD规范;

什么是UMD模块规范?就是AMD+CommonJs+全局变量的组合规范。

这段代码用来判断当前的运行环境,如果是node环境,就会使用CommonJs规范,然后判断是否是AMD环境,是的话就会使用AMD规范,最后导出全局变量

有了UMD后我们的代码可以同时运行在node和浏览器上。现在前端多数的库最后打包都是使用UMD规范。

(function(global,factory){

在如此多的Javascript库中,我看到global 、factory作为函数的参数,这是为什么呢?

答:

这是UMD模式,你可以很清晰的通过下图看到看到这段代码在你的库中做了什么:

 ┌──────────────────┐       ┌──────────────────┐
    │                  ▼       ▼                  │
    │    (function (global, factory) {            │
    │                                             │
    │                                             │
    │        /* deleted for clarity */            │
    │                 ┌───────────────────────────┘
    │                 │
    │    }(this, function () { 'use strict';
    │       │
    └───────┘
             /* */

         })


所以这根本上是一个立即执行函数,如果你把匿名函数改写为有名字的函数你可以更清晰的看到这段代码的结构。

// rename function () { 'use strict' ...
function Vue () { 'use strict';
    /* */
}

// rename function (global, factory) ...
function UMD (global, factory) {
    /* deleted for clarity */ 
}

UMD(this, Vue);

通用的模块化编译,兼容cjs amd和全局变量三种模块化方案,主要特点是可以在不同环境中运行。同时支持同步和异步加载模块。

global根本上就是当从函数外部引用时指向全局对象(浏览器的window和node.js没命名),factory是创建库对象的函数,根本上factory是vue,jq等三方库的完成。

用这样的方式来写这个结构,没有创建任何不必要的全局变量和函数,因此避免了污染全局区域并且避免了与其他库变量或函数名的冲突。

至于为什么要把this分派给global,这是因为window是一个完全没有保护的全局变量(这是为什么node.js没有给它起名字)并且任何第三放库都可以重写或者修改它。如果你在使用不知名的第三方库时想要浏览器的原始全局对象,你需要使用this这个小技巧。

5、ES6模块化

  • export和import

支持静态编译,什么叫做静态编译,可以按需加载和预编译

6、commonjs和es6的区别

立即执行函数 安全 引入依赖 注入 script请求多 依赖模糊 难以维护 模块化规范: 1、全局function模式 2、namespace模式 3、iife模式 4、iife增强模式

7、 JS 中的模块化方案

JS 的模块化经历了各种历史时期,在不同时期产生了不同的模块化方案。
到目前为止,对于编写源码来说,主流的方案只剩下两种。

  • esm: 从 ES6 起官方规范自带的方案
  • cjs: Node.js 使用的方案

但是为了支持不同的目标运行环境,需要编译成不同的输出格式(方案),
了解不同的模块化方案是很有必要的。

流行打包工具 Rollup.js (opens new window)和 Webpack (opens new window)都支持导出格式功能。

以 Rollup 文档为例子,一共有以下几种:

  • cjs: CommonJS, 支援 Node.js
  • esm: 作为 ES module 文件,现代浏览器中用 <script type=module> 标签可直接支持
  • iife: 立即执行函数,可直接使用 <script> 标签。
    (如果你想打包你的前端应用,也可以用这种方式)
  • umd: Universal Module Definition,通用模块定义,
    直接封装 amd、cjs、iife 三种方式并根据环境自动切换
  • amd: Asynchronous Module Definition,异步模块定义,以 RequireJS (opens new window)为代表
  • systemSystemJS (opens new window)的方式

链接(rualc.me/frontend/ja…)

8、总结

模块化开发是一种管理方式,一种生产方式,一种解决问题的方案。模块开发要遵循一定的规范,后面就出现了我们所熟悉的AMD和CMD规范。

AMD是依赖前置,CMD是依赖就近

CommonJS规范主要应用于Node

CommonJS规范规定,模块可以多次加载,但是只会在第一次加载时运行一次,运行结果就会被缓存下来,以后再加载就直接读取缓存结果,如果想让模块再次运行,必须清除缓存

在ES6没出来之前,模块加载方案主要使用CommonJS和AMD两种,前者用于服务器,后者用于浏览器

ES Module不支持动态导入,但已提案,指日可待。 ES Module是异步导入,因为用于浏览器,需要下载文件,如果采用同步导入对渲染有很大影响。CommonJS是同步导入,因为用于服务端,文件都在本地,同步导入即使卡主主线程影响也不大。 ES Module导出的是值的引用,导入导出值都指向同一个内存地址,所以导入值会跟随导出值变化。而CommonJS在导出时都是值的拷贝,就算导出的值变了,导入的值也不会改变,所以想要更新值,必须重新导入一次。

ES6的模块不是对象,import命令会被 JavaScript 引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。也正因为这个,使得静态分析成为可能。

es6 编译时加载 和 cmj amd运行时加载

cmj是值拷贝 es6是值引用