模块化开发

200 阅读5分钟

common.js node环境中内置的模块化规范 优点 一个文件就是一个模块 每个文件都有独立的作用域 用法 module.exports 导出成员 可以用exports代替 但是exports不能导出对象 通过require函数导入模块 原理 common.js规范实际就是将js模块当成一个函数,内置了几个参数,包括require,module等五个参数, module.exports指向的是一个对象,而exports也指向了同一个对象,所以可以代替module.exports,当exports导出一个新对象的时候,他就失效了,因为模块导入的过程就是执行那个模块对应的函数的过程,并将module.exports指向的对象返回出来。 局限性 commonJs规范是以同步的方式加载模块,node 环境下会首先加载所有模块然后使用 不适合浏览器的执行环境

AMD 异步模块定义规范,require.js实现了AMD规范 导出模块 通过define函数来定义模块,该函数接受三个参数 1:模块名称 2:依赖的模块,可选项 数组 3:一个函数 为模块提供私有作用域 函数的参数与依赖的模块一一对应 导入模块 require 该方法接受两个参数 1:所加载的模块 2:模块加载之后的执行方法 方法的参数与加载的模块一一对应

define('a',['jQuery','/c'],function($,angular){
    const user = { name: 123}
    return {
    jquery:$,
    user,
    }
}

require([./a,'Angular'],function(a,angular){
    
})

缺点: AMD规范使用相对复杂,而且每次执行require时会生成script标签去请求对应模块,对于同一个模块可能会被多次加载,影响页面性能

CMD 通用模块定义规范 sea.js实现了CMD规范 特点

  1. 一个模块是一个文件
  2. 不应该在模块内引入新的自由变量
  3. 异步执行
  4. 通过define来定义模块和引入模块, define接受一个函数作为参数,该函数有三个参数
  5. require 导入模块
  6. exports 导出模块
  7. module 模块的描述对象 module 是一个对象,上面存储了与当前模块相关联的一些属性和方法。
// math.js
define(function(require, exports, module) {
  exports.add = function() {
    var sum = 0, i = 0, args = arguments, l = args.length;
    while (i < l) {
      sum += args[i++];
    }
    return sum;
  };
});
// increment.js
define(function(require,exports,module){
    var add = require('math').add
    exports.increment = function(val){
       return add(val,1)
    }
})

// program.js
define(function(require, exports, module) {
  var inc = require('increment').increment;
  var a = 1;
  inc(a); // 2

  module.id == "program";
});

实践: Node中使用CommonJs规范,web中使用ES Module

ESModule ESM是ES6中的定义的模块系统

特点:

  1. ESM默认采用严格模式,忽略'use strict' ,this为undefined
    <script type="modue">
        console.log(this) // undefined
    </script>
  1. 每个ESM都具有单独的做那个作用域 不能直接访问
  2. ESM 是通过 CORS 方式请求外部 js 的。CORS不能通过文件的形式请求,只能通过http的方式请求 所以有时候会存在跨域
  3. ESM是延迟执行的,类似于给script标签添加了defer属性 等到页面加载完毕在执行 使用 Html中给script标签添加type="module"来定义

import 导入模块 要求: from后面的文件路径必须包含完整的文件名 可以是相对路径,也可以是绝对路径,又或者是模块所在位置的完整url 不能省略后缀或者index.js 不引用模块中的成员,而是直接记载模块时候,

    import {} from '路径'
    import '路径'

导入模块中所有成员

    import * as 别名 from '路径'

import只能在顶层作用域中使用 不能在if,for等作用域中使用 动态导入

   import(modulePath).then(mod => {
  console.log(mod);
})

   import(modulePath).then({mod拆开来写} => {
  console.log(mod);
})

同时导入命名成员和默认成员

   import { default as name,  Person, hello } from './module.js'
   
   import name, { Person, hello } from './module.js'

模块导出

1. 直接导出变量、函数、类等
    export var name = 'Json'

    export function hello() {
      console.log('hello');
    }

    export class Person {
      constructor(name) {
        this.name = name
      }
    }
 2.  export 导出一个对象,将变量作为一个对象的属性导出
 export {
      name, hello, Person
    }

3. 导出时重命名,通过 as 来对导出的变量进行重命名,导入时只能使用重命名之后的变量名
// 导出
export {
  name as userName, hello, Person
}
// 导入
import { name } from './module.js' // 报错 应该是userName

如果将变量重命名为 default , 则该变量将作为模块默认导出成员。在 import 导入时,直接使用任意名字来存储模块导出的变量。

4.如果将变量重命名为 default , 则该变量将作为模块默认导出成员。在 import 导入时,直接使用任意名字来存储模块导出的变量。
export {
  name as default, hello, Person
}
import personName from './module.js'
console.log(personName);

5. 通过 export default 变量名 可以将变量作为模块的默认导出来导出。在导入时的用法和 4 一致。
export default name

注意事项 导入导出的写法是固定的语法,而不是对象的解构 比如导出一个对象 导入的之后不能通过结构的形式来获取 其中一个属性 导入导出的是变量的引用,如果在使用了导入的变量之后修改变量,引用的变量的值将改变 导入模块的时候,模块的成员是只读的,不能修改 直接导出导入的成员

export { Person, hello } from './module.js'
直接导出默认成员时,需要通过 as 进行重命名 
export { default as name, Person, hello } from './module.js'

esmodule在低版本浏览器的兼容 ESM 浏览器环境 polyfill。通过引入browser-es-module-loader 来兼容不支持 ESM 的浏览器环境。 1.可以通过unpkg.com/moduleName来…

<script nomodule type="text/javascript" src="https://unpkg.com/browse/browser-es-module-loader@0.4.1/dist/babel-browser-build.js"></script>
<script nomodule type="text/javascript" src="https://unpkg.com/browse/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js"></script>
  1. script 标签的 nomodule 属性,在不支持ESM的浏览器中才会运行其中的代码。可以利用这个属性来兼容对ESM的支持
<script nomodule>
    alert(1234)
</script>

node环境下使用ESM

  1. 需要将后缀名改为.mjs
  2. 使用node命令执行.mjs文件时,需要添加--experimental-modules参数 因为还处于试验阶段 3.可以使用 import {} from ‘’ 的形式导入内置模块的成员,因为内置模块对ESM进行了兼容
// import fs from 'fs'
import { writeFileSync } from 'fs'
writeFileSync('.test.txt', 'Hello Node 123')

4.不能使用 import {} from ‘’ 的方式导入第三方模块,因为第三方模块导出的是默认成员(主要看node第三方模块是否添加了ESM支持)

import { camelCase } from 'lodash'
console.log(camelCase('ES Module')); 报错 不支持esm

CommJS和ESM在Node环境下的交互 1.ESM 可以导入 CommonJs 的模块 2.CommonJs 模块不能 导入 ESM 的模块 3.CommonJs 模块只会默认导出

ESM和CommonJs 在 Node 中差异:主要ESM是对于node内置模块中的5个变量不支持

// 加载模块的函数
console.log(require);
// 模块对象
console.log(module);
// 导出对象别名
console.log(exports);
// 当前文件的绝对路径
console.log(__filename);
// 当前文件所在目录
console.log(__dirname);

在 ESM 中使用相关功能

import { fileURLToPath } from 'url'
import { dirname } from 'path'

// 当前文件的url
// console.log(import.meta.url);
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
console.log(__filename);
console.log(__dirname);

在node中设置默认使用ESM规范执行js

  1. package.json文件中的type设置为 module
{
  "type": "module",
  "dependencies": {
    "lodash": "^4.17.20"
  }
}

2 .如果要使用CommonJs规范,则需要将后缀改为.cjs

使用babel让node低版本环境兼容ESM

使用preset-env转换 preset-env是一个插件集合 安装插件@babel/node @bable/core @babel/preset-env 配置babel,在.babelrc文件中添加配置 "presets": ["@babel/preset-env"] 运行命令 yarn babel-node index.js

使用插件@babel/plugin-transform-modules-commonjs转换 模块转换的专用插件 安装插件@babel/node @bable/core @babel/plugin-transform-modules-commonjs 配置babel,在.babelrc文件中添加配置 "plugins": ["@babel/plugin-transform-modules-commonjs"] 运行命令 yarn babel-node index.js