模块化学习

95 阅读5分钟

一、AMD

AMD规范是由RequireJS提出的,主要用于浏览器的JavaScript开发,它采用异步加载模块的方式。

在AMD规范中,使用‘define’函数来定义一个模块,并且可以通过‘require’函数来引入其他模块。

例子

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
<script src="./modules/require.js" data-main="./index"></script>//require.js是模块加载器(RequireJS)
</body>
</html>
define(['./src/AMD.js'], function(math) {
  var result = math.add(5,3);
  console.log(result, 'result')
})
define(function() {
  var math = {};
  math.add = function(a, b) {
    return a + b;
  }
  math.subtract = function(a, b) {
    return a - b;
  }
  return math;
})

运行index.html,控制台输出

image.png

二、CMD

CMD规范是由SeaJS提出来的,采用同步加载模块的方式

在CMD规范中,使用define函数来定义一个模块,并且可以通过require函数来引入其他模块

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
<script src="./modules/sea.js"></script>
<script>
  seajs.use('./index.js')
</script>
<script></script>
</body>
</html>
define(function(require, exports, module) {
  var math = require('./src/CMD')

  var result = math.add(5, 3);
  console.log(result); // 输出: 8
})
define(function(require, exports, module) {
  var math = {};

  math.add = function(a, b) {
    return a + b;
  };

  math.subtract = function(a, b) {
    return a - b;
  };
  module.exports = math;
})

控制台输出 image.png

三、同步加载和异步加载的区别

  • 模块加载方式

异步加载: 在需要使用模块时采取加载,不会阻塞后续代码的执行

同步加载:页面加载过程中就会立即加载所有需要的模块

  • 代码执行顺序

异步加载:代码执行顺序是不确定的,取决于模块加载的速度和网络状况

同步加载:会阻塞后续代码的执行,直到所有模块都加载完毕,代码的执行顺序是确定的,按照模块定义好的顺序依次执行

  • 使用场景

异步加载适用于提高页面加载速度和避免阻塞的情况,但在代码执行顺序上需要注意处理异步的特性

同步加载更容易控制代码的执行顺序,但可能导致耶main加载速度变慢和阻塞其他操作

四、commonJS

CommonJS规范是用于服务端的javascript开发,例如Node.js

是一种同步模块的规范,即模块的依赖关系会在运行时被解析和加载。

在commonJS规范中,使用‘require’和‘export’来运入和导出模块

要想在浏览器环境内使用commonJS标准,需要webpack支持

const path = require('path');
module.exports = {
  entry: './index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname)
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
<script src="./bundle.js"></script>
<script></script>
</body>
</html>
const math = require('./src/commonJS')
console.log(math.add(1, 2));
function add(a, b) {
  return a + b;
}
function subtract(a, b) {
  return a - b;
}
module.exports = {
  add,
  subtract
}

五、require和import的区别

引用方式不同

  1. require/exports是commmonJS的写法,import是ES6的写法;
  2. require是运行时动态加载,import是在编译时静态加载的
  3. require是值的拷贝,import是值的引用,也就是,若文件引用的模块发生改变,require的引用方式的文件也不会发生改变,但是import方式的,是会发生改变的
  4. 用法不一致
  • require/exports

在不改变 exports 指向的情况下,使用 exports 和 module.exports 没有区别;如果将 exports 指向了其他对象,exports 改变不会改变模块输出值。示例如下:

//utils.js
let a = 100;

exports.a = 200;
console.log(module.exports) //{a : 200}
exports = {a:300}; //exports 指向其他内存区

//test.js

var a = require('./utils');
console.log(a) // 打印为 {a : 200}
  • import/export
import {readFile} from 'fs' //从 fs 导入 readFile 模块
import {default as fs} from 'fs' //从 fs 中导入使用 export default 导出的模块
import * as fileSystem from 'fs' //从 fs 导入所有模块,引用对象名为 fileSystem
import {readFile as read} from 'fs' //从 fs 导入 readFile 模块,引用对象名为 read
import('/modules/my-module.js') //动态导入
  .then((module) => {
    // Do something with the module.
});

export default fs
export const fs
export function readFile
export {readFile, read}
export * from 'fs'

建议1:建议明确列出我们要引用的内容。,使用 * 虽然很方便,但是不利于现代的构建工具检测未被使用的函数,影响代码优化。

建议2: 请不要滥用动态导入 import()(只有在必要情况下采用)。静态框架能更好的初始化依赖,而且更有利于静态分析工具和 tree shaking 发挥作用

  • import/export 不能对引入模块重新赋值/定义,但是require可以

当我尝试给 import 的模块重新赋值时

import {e1} from './webUtils.js';
  e1=234;

浏览器显示

Uncaught TypeError: Assignment to constant variable.

当我重新定义引用的模块

import {e1} from './webUtils.js';
 var e1=1;

浏览器显示

(index):17 Uncaught SyntaxError: Identifier 'e1' has already been declared

  • ES6 模块可以在 import 引用语句前使用模块,CommonJS 则需要先引用后使用

ES6 模块

//webUtils.js
export var e='export';
console.log(e) //export
  import {e} from './webUtils.js';
  console.log(e) //export

CommonJS

//utils.js
exports.e = 'export';
console.log(a) 
a = require('./utils');
console.log(a)

程序报错

ReferenceError: a is not defined

  • import/export 导出的模块默认调用严格模式;require/exports 默认不使用严格模式,可以自定义是否使用严格模式。

import/export 导出的模块默认调用严格模式。

var fun=()=>{
    mistypedVaraible = 17; //报错,mistypedVaraible is not defined
};
export default fun;

require/exports 默认不使用严格模式,可以自定义是否使用严格模式。 例如

exports.fun = ()=>{
 mistypedVaraible = 17; //没有调用严格模式,不会报错
};

UMD模块定义模块规范

UMD是一种用于支持多种模块加载方式的模块定义规范。他可以在浏览器环境和node环境下使用,并且兼容AMD和CommonJS这两种常见的模块加载方式

UMD模块的定义方式允许模块以多种形式被加载和使用。它首先检测是否存在AMD加载器,如果存在,则使用AMD的方式加载模块;如果不存在AMD加载器,则检测是否存在CommonJS加载器,如果存在,则使用CommonJS的方式加载模块;如果两者都不存在,则将模块暴露到全局作用域下。

UMD模块的定义通常使用类似以下的代码结构

(function (root, factory) {
    if(typeif define === 'function' && define.amd) {
        // AMD
        define(['dep1', 'dep2'], factory);
    } else if(typeof exports === 'object' && typeof module === 'object') {
        // CommonJS
        modules.exports = factory(require('dep1'), require('dep2'));
    } else {
        // global variable
        root.MyModule = factory(root.dep1, root.dep2)
    }
})(this, function(dep1, dep2) {
    // 模块的实际代码
    // 返回模块的导出内容
    return {
        // 导出的内通
    }
})

UMD模块的好处在于它兼容了多种模块加载方式,可以在不同的环境中使用,这使得开发人员可以编写一次模块定义,然后再不用的环境中灵活的使用和加载模块,这提高了模块的可复用性和跨平台性