乐趣区

前端模块化

模块化

模块化用来分割,组织和打包软件。每个模块完成一个特定的子功能,所有的模块按某种方法组装起来,成为一个整体,完成整个系统所要求的功能。

以前开发没用模块化可能导致的问题

  1. 命名空间冲突,多个库可能会使用同一名称
  2. 无法合理的管理项目的依赖和版本
  3. 无法方便的控制依赖的加载顺序

## 如今前端模块化的主流方式
利用自动化构建工具 Gulp/Webpack 把源代码转换成发布线上的可执行 JavaScrip、CSS、HTML 代码
通常自动化构建工具做的内容有:

  • 代码转换:ECMASCRIPT6 编译成 ECMASCRIPT5、LESS 编译成 CSS 等。
  • 文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等
  • 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载。
  • 模块合并:在采用模块化的项目里会有很多个模块和文件,需要构建功能把模块分类合并成一个文件。
  • 自动刷新:监听本地源代码的变化,自动重新构建、刷新浏览器。
  • 代码校验:在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。
  • 自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统。

## 模块化的几种规范

  1. CommonJS
  2. AMD
  3. ES6 模块化

## CommonJS
CommonJS 定义的模块分为:{模块引用 (require)} {模块定义(exports)} {模块标识(module)}
require() 用来引入外部模块;exports 对象用于导出当前模块的方法或变量,唯一的导出口;module 对象就代表模块本身。

  • 使用方式
// foo.js 
module.exports = function(x) { // 导出
  console.log(x);
};

// main.js
var foo = require("./foo"); // 导入
foo("Hi");
  • 原理

浏览器不兼容 CommonJS 的根本原因,在于缺少四个 Node.js 环境的变量。

  • module
  • exports
  • require
  • global

换言之,只要我们提供了这几个,浏览器就能加载 CommonJS 模块。下面是一个简单的示例

var module = {exports: {}
};

(function(module, exports) {exports.multiply = function (n) {return n * 1000};
}(module, module.exports))

var f = module.exports.multiply;
f(5) // 5000
  • 模拟实现 require
function require(p){var path = require.resolve(p);   // 返回路径
  var mod = require.modules[path];  // 是否已注册
  if (!mod) throw new Error('failed to require"' + p + '"');  // 未注册抛出
  if (!mod.exports) {   // 如果注册了  就执行模块 
    mod.exports = {};
    mod.call(mod.exports, mod, mod.exports, require.relative(path));    // 执行模块
  }
  return mod.exports;
}

require.modules = {};
require.resolve = function (path){
  var orig = path;
  var reg = path + '.js';   
  var index = path + '/index.js';
  return require.modules[reg] && reg || require.modules[index] && index || orig;
};

require.register = function (path, fn){  // 注册模块
  require.modules[path] = fn;  
};

require.relative = function (parent) {return function(p){if ('.' != p.charAt(0)) return require(p);
    var path = parent.split('/');
    var segs = p.split('/');
    path.pop();

    for (var i = 0; i < segs.length; i++) {var seg = segs[i];
      if ('..' == seg) path.pop();
      else if ('.' != seg) path.push(seg);
    }

    return require(path.join('/'));
  };
};

require.register("moduleId", function(module, exports, require){// 代码写在这里});
var result = require("moduleId");

AMD

优点

  1. 可在不转换代码的情况下直接在浏览器中运行
  2. 可加载多个依赖
  3. 代码可运行在浏览器环境和 Node.js 环境下

缺点

  1. JavaScript 运行环境没有原生支持 AMD,需要先导入实现了 AMD 的库后才能正常使用
  • 语法
define([module-name?],  // 模块名
[array-of-dependencies?],  // 依赖模块
[module-factory-or-object]); // 模块的实现,或者一个 JavaScript 对象
  • 使用方法
define('a', [], function () {return 'a';});
define('b', ['a'], function (a) {return a + 'b';});
// 导入和使用
require(['b'], function (b) {console.log(b);
});

define 函数具有异步性。当 define 函数执行时,首先会异步的去调用第二个参数中列出的依赖模块,当所有的模块被载入完成之后,
如果第三个参数是一个回调函数则执行;然后告诉系统模块可用,也就通知了依赖于自己的模块自己已经可用

  • 模拟实现
let factories = {};
function define(modName, dependencies, factory) {
    factory.dependencies = dependencies;  // 依赖
    factories[modName] = factory; // 储存模块 
}
function require(modNames, callback) {let loadedModNames = modNames.map(function (modName) {let factory = factories[modName];
        let dependencies = factory.dependencies;
        let exports;
        require(dependencies, function (...dependencyMods) {exports = factory.apply(null, dependencyMods);
        });
        return exports;
    })
    callback.apply(null, loadedModNames);
}

目前 AMD 使用比较少了

ES6 模块化

ES6 模块化是 ECMA 提出的 JavaScript 模块化规范,它在语言的层面上实现了模块化。浏览器厂商和 Node.js
都宣布要原生支持该规范。它将逐渐取代 CommonJS 和 AMD` 规范,成为浏览器和服务器通用的模块解决方案。

  • 优点

    1. 主流方案,各种浏览器厂商和 node 都逐渐支持
  • 缺点

    1. 目前无法直接运行在大部分 JavaScript 运行环境下,必须通过工具转换成标准的 ES5 后才能正常运行
  • 特点

    1. 如果你通过本地加载 Html 文件 (比如一个 file:// 路径的文件), 你将会遇到 CORS 错误,因为 Javascript 模块安全性需要。你需要通过一个服务器来测试。
    2. 自动使用严格模式。
    3. 加载一个模块脚本时不需要使用 defer 属性, 模块会自动延迟加载。
    4. 无法在全局获得. 因此,你只能在导入这些功能的脚本文件中使用他们,你也无法通过 Javascript console 中获取到他们
  • 使用方法
  1. 基本使用
// 导出
export const name = 'xiaoming';
// 导入
import {name} from './person.js';
  1. 默认导出
// 导出
const name = 'xiaoming';
export default name
// 导入
import name from './person.js';
//  或者
import {default as name} from './person.js';
  1. 重命名导出与导入
export {
  function1 as newFunctionName,
  function2 as anotherNewFunctionName
};

// inside main.mjs
import {newFunctionName, anotherNewFunctionName} from '/modules';
  1. 全部导入
import * as Module from '/modules/module';
Module.function1()
Module.function2()
  1. 动态导入
import('/modules/myModule.mjs')
  .then((module) => {// Do something with the module.});
//    需要 babel 解析才能使用

参考资料

珠峰培训
深入理解 commonjs
AMD 与 requireJS
JavaScript modules 模块

退出移动版