JS 模块化

103 阅读4分钟

JS 模块化

1.历史

背景

JS 本身是为了简单的页面设计:页面动画,表单提交,并不会内置命名空间或者模块化的概念

随着业务的飞速发展,针对 JS 模块化涌现了大量的解决方案

幼年期:无模块化

  1. 开始需要在页面中增加不同的 js 文件,如:动画.js、验证.js、格式化.js
  2. 多种 JS 为了维护和可读性,被分在了不同的 js 文件里
  3. 不同的模块在同一个模板中被引用
<script src="jquery.js"></script>
<script src="main.js"></script>
<script src="deps1.js"></script>
<script src="deps2.js"></script>

对此表示认可:相比较使用一个 js 文件,这种多个 JS 文件的实现是最基础的 JS 模块化思想,是进步的 问题:污染全局作用域,每一个代码块都暴露在全局,协调每一个代码块中的变量函数不能相同,不利于大型项目的开发和维护

成长期:模块化的雏形 --- IIFE 立即执行函数(语法测的优化)

作用域的把控

例子:

// 定义全局变量 count.js
let count = 0;
const increase = () => ++count;
const reset = () => {
  count = 0;
  console.log("count is reset");
};
  • 独立作用域-函数
  • 利用函数作用域
// 定义全局变量 count.js
() => {
  let count = 0;
  const increase = () => ++count;
  const reset = () => {
    count = 0;
    console.log("count is reset");
  };
};
  • 仅定义了一个函数,并未执行
  • 解决方案:立即执行
// 定义全局变量 count.js
() => {
  let count = 0;
  const increase = () => ++count;
  const reset = () => {
    count = 0;
    console.log("count is reset");
  };
}();
  • 定义一个简单的模块:完成一个模块的封装,实现了对外暴露功能,保留变量,不污染全局作用域的能力
// 定义全局变量 count.js
const iifeCountModule = (() => {
  let count = 0;
  return {
    increase : () => ++count,
    reset : () => {
        count = 0;
        console.log("count is reset");
    };
  }
})();

优化 1:如果要依赖其他模块?--- 传参依赖

// 定义全局变量 count.js
const iifeCountModule = ((dependenceModule1,dependenceModule2) => {
  let count = 0;
  // dependenceModule1,dependenceModule2
  return {
    increase : () => ++count,
    reset : () => {
        count = 0;
        console.log("count is reset");
    };
  }
})(ependenceModule1,dependenceModule2);

** 面试题:了解早期的 jquery 的依赖处理和模块加载方案吗? | JS 中如何实现私有变量模块? 答案: IIFE + 传参调配 **

成熟期

CJS module:CommonJS

特征:

  • 通过 module + exports 来进行模块的输出定义
  • require 来调用其他模块
// 定义全局变量 count.js
const dependenceModule1 = require("./dependenceModule1.js");
const dependenceModule2 = require("./dependenceModule2.js");
let count = 0;
const increase = () => ++count;
const reset = () => {
  count = 0;
  console.log("count is reset");
};
exports.increase = increase;
module.exports = {
  increase,
  reset,
};

模块使用方式

// main.js
const { increase, reset } = require("./count.js");
increase();
reset();

const commonJsCountModule = require("./count.js");
commonJsCountModule.increase();
commonJsCountModule.reset();
  • 优点:CommonJs 规范在服务器端率先完成了 JS 的模块化,解决了依赖、全局变量污染的问题,这也是 JS 能够在服务器端运行的必要条件
  • 缺点:优先考虑同步场景,在浏览器端所有文件异步拉取的场景中,适用性差
AMD 规范

非同步加载模块,允许制定回调函数

新增定义方式:

require([module], callback); define(id, [depends], callback); // 模块名,依赖模块,工厂方法

// 加载完依赖,再执行回调函数
define("amdCountModule", ["dependenceModule1", "dependenceModule2"], (
  dependenceModule1,
  dependenceModule2
) => {
  let count = 0;
  const increase = () => ++count;
  const reset = () => {
    count = 0;
    console.log("count is reset");
  };
  return {
    increase,
    reset,
  };
});

引入模块:

require(["amdCountModule"], (amdCountModule) => {
  amdCountModule.increase();
  amdCountModule.reset();
});
  • 面试题:如果想在 AMD 中使用 require 来加载同步模块 -- AMD 支持向前兼容
define((require) => {
  const dependenceModule1 = require("./dependenceModule1.js");
  const dependenceModule2 = require("./dependenceModule2.js");
  let count = 0;
  const increase = () => ++count;
  const reset = () => {
    count = 0;
    console.log("count is reset");
  };
  return {
    increase,
    reset,
  };
});
  • 面试题:有没有方式可以同时兼容 AMD & Common => UMD 实现机制:
(define=>define((require,exports,module)=>{
    const dependenceModule1 = require("./dependenceModule1.js");
    const dependenceModule2 = require("./dependenceModule2.js");
    let count = 0;
    const increase = () => ++count;
    const reset = () => {
        count = 0;
        console.log("count is reset");
    };
    exports.increase = increase;
    module.exports = {
        increase,
        reset,
    };
}))(
    typeof module ==='object'
        && module.exports
        && typeof define !== 'function'
            // commonJS
            ? factory => module.export=>factory(require,exports,module)
            // AMD
            : define
);
  • 优点:适合在浏览器环境中异步加载模块,并行加载多个模块。向前兼容工厂函数中使用 commonJS 规范 缺点:提高了开发成本,不能按需加载,而是必须提前加载所有的依赖
CMD 规范 -SeaJS -按需加载
define((require, exports, module) => {
  const $ = require("./jquyer.js");
  // .....
  const module1 = require("./module1.js");
  // .....
});
  • 优点:按需加载,依赖就近
  • 缺点:依赖打包,依赖逻辑存在于每一个模块中

ESM 模块化

export 导出关键字 import 导入关键字

import dependenceModule1 from "./dependenceModule1.js";
import dependenceModule2 from "./dependenceModule2.js";

let count = 0;
export const increase = () => ++count;
export const reset = () => {
  count = 0;
  console.log("count is reset");
};
export default {
  increase,
  reset,
};

模块引入

<script type="module" src="./count.js"></script>;

import { increase, reset } from "./count.js";
increase();
reset();

import esmCountModule from "./count.js";
esmCountModule.increase();
esmCountModule.reset();

ES11 原生异步解决方案

import("./count.js").then(({ increase, reset }) => {
  increase();
  reset();
});