JavaScript基础知识总结(六)模块化规范

模块化

什么是模块化

模块化是一种程序设计方案,指将程序拆分为多个功能单一,相对独立的模块。模块各司其职,互相协作。提升代码的可维护性与可读性。

**模块化主要解决的问题:**1.不同脚本之间可能会出现命名冲突,全局变量污染。2.依赖管理混乱,必须手动维护不同第三方依赖库的顺序。3.复用性很差,同一个函数可能需要在不同项目中重复定义。

**核心思想:**高内聚,低耦合。模块内部功能紧密相关,模块之间尽量减少依赖。

问题:在没有模块化的时候,解决问题的方法是什么?

在没有模块化标准时,开发者常使用 IIFE 实现作用域隔离(也就是立即调用函数)

js 复制代码
(function() {
  var myModule = {
    greet: function() {
      console.log('Hello, world!');
    }
  };
  window.myModule = myModule; // 暴露接口
})();

缺点很明显,模块间的依赖不明确,缺乏导入导出机制,不适合用于大型项目

下面将按照不同模块化规范出现时间依次讲解。

CJS规范

CommonJS 这种模块化规范是专门为服务器端设计的模块化规范(Node.js)。是出现时间最早的模块化规范。

注意这个规范只适用于服务端与webpack等打包工具,不适用于浏览器。

核心变量:

CJS中有三个核心变量:exportsmodule.exports,require

模块化的核心就是导入和导出,三个变量里面module.exportsexports负责模块导出,require负责模块导入

基本使用
使用module.exports导出

在文件util.js中使用规范规则导出,module.exports导出需要导出的是一个对象,对象是包含的导出的内筒

js 复制代码
// util.js
const info = {
    name: 'alex'
}

module.exports = info

main.js文件中接收

js 复制代码
// main.js
const moduleInfo = require('./util.js')

此时: util.js中的infomodule.exports的内存地址 与 main.js中的moduleInfo的内存是一样的 所以三者都指向的同一个内存地址上的同一个内容

使用exports导出

此时的exports本质上就是一个导出对象,这个对象可以挂载我们需要导出的属性

js 复制代码
// foo.js
exports.foo = 'foo'
js 复制代码
// index.js
const fooModule = require('./foo')

console.log(fooModule.foo) // foo

本质上也是使用的是同一地址上的引用.

exports与module.exports的本质

通过上面两个导出的示例,我们可以总结一下这两个导出在引擎上有什么却别

js 复制代码
module.exports = {}

exports = module.exports

所以 使用 exports.xx = 'xxx' 其实就是往 module.exports = {} 这个对象中添加属

我们可以深究一下,exports本质就是一个引用类型的对象,module.exports本质与exports是同一个引用 只不过后者可以直接暴露属性,但是前者不能直接暴露属性

那么为什么有了 module.exports 后还需要一个 exports 呢?其实根据 CommonJS 的规范来说是要通过 exports 来导出的,但是由于 Node 本身实现是通过 module.exports 来实现的,所以在 Node 开发中经常会使用 module.exports 反而不经常使用 exports

require函数的查找模式

require(X):

  • X是Node提供的核心模块:'http','path'模块等,那么require就会直接返回这个模块
  • X是./ ../ /开头的
    • 有后缀名如(.js等)
      • 按照后缀名格式查找
    • 没有后缀名
      • 先查找文件本身
      • 查找X.js
      • 查找X.json
      • 查找X.node
  • X 不是路径也不是核心模块(说明X可能是第三方库如"axios等等")
    • 会根据'module.path'一级一级去查找第三方库中node_moodules是否有X
  • 没有找到抛出异常not found
模块的加载过程

结论一:模块在被第一次引入时,模块中的 js 代码会被执行一次

js 复制代码
// foo.js
console.log('foo module execute')
// main.js
require('./foo')  // 会运行一遍 foo 模块中的代码

结论二:模块在被多次加载后,代码也会只运行一次:

  • 这是因为每个模块对象有一个 loaded 属性
  • 加载了 loaded 就变为 true 了
js 复制代码
require('./foo')  // 也只会运行一次 foo 模块
require('./foo')
require('./foo')
CJS规范的缺点:

CommonJS 模块的加载是同步的:

  • 同步意味着需要等模块中加载完毕后,后面的逻辑才会执行
  • 这个在服务器不会有什么问题,因为服务器加载的是本地 JS 文件,速度会很快

那么如果将 CommonJS 规范用于客户端(浏览器)呢?

  • 浏览器加载 js 文件需要先从服务端下载下来,之后再加载运行
  • 那么采用同步就意味着后续的 js 代码都无法正常运行,即使是一些简单的 DOM 操作

所以如果面向的是客户端,我们将不再采用 CommonJS 规范,在早期浏览器中如果使用模块化开发,通常会采用 AMD 和 CMD 规范。

  • 但是由于目前 ES 已经支持模块化 ESM(ES Module),另一方面借助于 webpack 等打包工具也可以将 CommonJS 和 ESM 模块的转换
  • 所以 AMD 和 CMD 用的已经非常少了,下面我们就简单的了解一下

AMD规范

AMD规范是主要用于浏览器的一种模块化规范,AMD是Asynchronous Module Definition(异步模块定义)的缩写

顾名思义,AMD规范是采用异步加载的模块化规范,AMD规范已经很少被使用了,实现AMD规范的库主要有require.jscurl.js

requires的使用

使用requires需要在入口文件加载前引用cdn

html 复制代码
<script src='./lib/require.js' data-main="./index.js"></script>

导出模块

js 复制代码
// /src/foo.js

// 在 define 中写逻辑
define(function () {
  const name = 'foo'
  const age = 18
  function sum(num1, num2) {
    return num1 + num2
  }
  // 这里的 return 就导出了
  return {
    name,
    age,
    sum,
  }
})

引入模块

js 复制代码
// /index.js

// 先配置每个模块的路径
require.config({
  paths: {
    main: './src/main',
    foo: './src/foo',
  },
})

// 这里再引入模块
require(['foo'], function (foo) {
  // 加载完成 foo 后触发回调 并传入加载的数据
  console.log(foo.name)
  console.log(foo.age)
  console.log(foo.sum(1, 2))
})

UMD规范

UMD 的全称是 Universal Module Definition ,即 通用模块定义 。它是一套 JavaScript 模块模式的组合,主要目的是让一个 JavaScript 模块能够在多种不同的环境中运行,尤其是同时支持:

  1. AMD - 异步模块定义,用于浏览器端,如 RequireJS。
  2. CommonJS - 用于服务器端,如 Node.js。
  3. 全局变量 - 作为浏览器中的全局变量使用,当没有模块加载器时。

简单来说,UMD 就是一种"通用"的 JavaScript 模块写法,它通过一系列条件判断,来检测当前代码运行在何种环境下,然后采用该环境对应的方式导出模块。

在 JavaScript 发展的早期,缺乏官方的模块标准。社区出现了多种模块化方案,但它们互不兼容。

  • 你写了一个库,如果只用 AMD 方式导出,那么在 Node.js 中就无法直接使用。
  • 如果只用 CommonJS 方式导出,在浏览器中没有模块加载器时也无法使用。

为了解决这个问题,开发者们创造了 UMD 模式。它让你的代码"自适应"环境,大大提高了库的通用性和可移植性。在 ES6 模块成为标准之前,UMD 是许多流行库(如 jQuery、React 等)的常见选择。

UMD 的本质是一个 立即执行函数表达式(IIFE),它接收一个工厂函数。在这个 IIFE 中,会进行一系列的条件判断,以决定如何导出模块。

这里就不介绍代码了

CMD规范

CMD是引用于浏览器的一种模块化规范(Common Module Definite 通用定义模块) 采用的是异步加载模块

使用的是SeaJS实现CMD规范

seajs的基本使用

在使用前需要引入seajs文件(或者使用CDN远程引入)

html 复制代码
<script src="./lib/sea.js"></script>

导出模块

js 复制代码
// /src/foo.js

define(function (require, exports, module) {
  const name = 'foo'
  const age = 18
  function sum(num1, num2) {
    return num1 + num2
  }
  // 可以通过 exports.name = name
  // 和 module.exports 两种
  module.exports = {
    name,
    age,
    sum,
  }
})

导入模块

js 复制代码
// /index.js

// define 函数接收一个回调函数,参数 require、exports、module
define(function (require, exports, module) {
  // 通过 require 导入模块
  const foo = require('./src/foo.js')
  console.log(foo)
})

ES6规范

ES Module 规范是 ES 提出的,所以是官方的模块化规范

ESM 和 CommonJS 的模块化有一些不同:

  • ESM 使用了 import (导入)和 export(导出)
  • ESM 采用编译期的静态分析,并且也加入了动态引用的方式
  • 采用 ESM 将自动采用严格模式 use strict

如果在 html 中引入 ESM 模块,需要这样:加上 type="module"

html 复制代码
<script src="./index.js" type="module"></script>
导入导出的基本使用

导出

js 复制代码
// 01.1xxxx

let title = "hello";
let url='www.baidu.com';
function say(){
    console.log("hello");
}
export{title,say}

导入

js 复制代码
// 01.2xxxx
import{title,say}from './01.1模块的基本使用.js'
console.log(title);//很神奇吧
say()
//利用导出和导入 我们只需要暴露我们需要的功能就可以了
//如果在浏览器中使用 要给script加上type="module"
模块的具名导入和导出

具名导出

js 复制代码
// ./05.2 xxxxx
export let site='www.baidu.com';//具名导出
export function add(a,b){
    return a+b;
}//必须要有名字 不然会报错

具名导入

js 复制代码
// ./05.1xxxxxx
import {site} from "./05.2.js"
console.log(site);
import { add }  from "./05.2.js";
console.log(add(1, 2));
模块批量导入和导出

导出

js 复制代码
// 06.2
export let site='www.baidu.com';//具名导出
export function add(a,b){
    return a+b;
}//必须要有名字 不然会报错

导入

js 复制代码
/* 使用 * as 别名可以批量导入,但是打包时会认为我们要用到这个模块里面的所有内容,
导致打包文件的体积过大,建议使用具名导入,打包是只会打包我们用到的一些文件 */
// 批量导入
import * as api from "./06.2.js"
console.log(api);
console.log(api.site);
起别名

导出起别名

js 复制代码
let site='houdunren.com'
export{site}
let sites=['www.baidu.com','www.taobao.com','www.jd.com']
export{sites as web};//导出起别名

导入起别名

js 复制代码
import {site as site1, web} from './07.2.js';
console.log(site1);//site1是site的别名
console.log(web);//web是sites的别名
默认导出

导出时使用default关键字,一个文件中只能默认导出一次

js 复制代码
class User{
    static render(){
        return 'user static render'
    }
}
export default User;

导入

js 复制代码
import Use from "./08.2.js"

console.log(Use.render())
混合导入与导出

默认导出和正常导出一起混合使用

混合导出

js 复制代码
export default class User{
    static render(){
        return 'user static render'
    }
}
let site='URL_ADDRESS.baidu.com'

export {site}

混合导入

js 复制代码
import hd,{ site } from "./09.2.js"
console.log(hd.render());
console.log(site);
//如果是批量导入 就使用api.default
多个模块合并导出

导出文件1

js 复制代码
export let site='www.baidu.com';//具名导出
export function add(a,b){
    return a+b;
}//必须要有名字 不然会报错

导出文件2

js 复制代码
export default class User{
    static render(){
        return 'user static render'
    }
}
let site='URL_ADDRESS.baidu.com'

export {site}

合并导出文件

js 复制代码
import *as t3 from './10.3.js'
import *as t4 from './10.4.js'
export {t3,t4}

导入文件

js 复制代码
import *as api from './10.2.js'
console.log(api);
console.log(api.t3.default.render());
相关推荐
lapiii3581 小时前
[前端-React] Hook
前端·javascript·react.js
qk学算法1 小时前
Collections工具类
java·开发语言
一枚前端小能手1 小时前
🎬 使用 Web 动画 API - 关键帧与交互控制实战指南
前端·javascript·api
缺点内向2 小时前
Java: 为PDF批量添加图片水印实用指南
java·开发语言·pdf
西西学代码2 小时前
Flutter---异步编程
开发语言·前端·javascript
song8546011342 小时前
锁的初步学习
开发语言·python·学习
米欧2 小时前
取消当前正在进行的所有接口请求
前端·javascript·axios
浪里行舟2 小时前
告别“拼接”,迈入“原生”:文心5.0如何用「原生全模态」重塑AI天花板?
前端·javascript·后端
重整旗鼓~2 小时前
38.附近商户实现
java·开发语言