JavaScript基础知识总结(六)模块化规范
模块化
什么是模块化
模块化是一种程序设计方案,指将程序拆分为多个功能单一,相对独立的模块。模块各司其职,互相协作。提升代码的可维护性与可读性。
**模块化主要解决的问题:**1.不同脚本之间可能会出现命名冲突,全局变量污染。2.依赖管理混乱,必须手动维护不同第三方依赖库的顺序。3.复用性很差,同一个函数可能需要在不同项目中重复定义。
**核心思想:**高内聚,低耦合。模块内部功能紧密相关,模块之间尽量减少依赖。
问题:在没有模块化的时候,解决问题的方法是什么?
在没有模块化标准时,开发者常使用 IIFE 实现作用域隔离(也就是立即调用函数)
(function() {var myModule = {greet: function() {console.log('Hello, world!');}};window.myModule = myModule; // 暴露接口
})();
缺点很明显,模块间的依赖不明确,缺乏导入导出机制,不适合用于大型项目
下面将按照不同模块化规范出现时间依次讲解。
CJS规范
CommonJS 这种模块化规范是专门为服务器端设计的模块化规范(Node.js)。是出现时间最早的模块化规范。
注意这个规范只适用于服务端与webpack等打包工具,不适用于浏览器。
核心变量:
CJS中有三个核心变量:exports,module.exports,require
模块化的核心就是导入和导出,三个变量里面module.exports和exports负责模块导出,require负责模块导入
基本使用
使用module.exports导出
在文件util.js中使用规范规则导出,module.exports导出需要导出的是一个对象,对象是包含的导出的内筒
// util.js
const info = {name: 'alex'
}module.exports = info
在main.js文件中接收
// main.js
const moduleInfo = require('./util.js')
此时: util.js中的info 和 module.exports的内存地址 与 main.js中的moduleInfo的内存是一样的 所以三者都指向的同一个内存地址上的同一个内容
使用exports导出
此时的exports本质上就是一个导出对象,这个对象可以挂载我们需要导出的属性
// foo.js
exports.foo = 'foo'
// index.js
const fooModule = require('./foo')console.log(fooModule.foo) // foo
本质上也是使用的是同一地址上的引用.
exports与module.exports的本质
通过上面两个导出的示例,我们可以总结一下这两个导出在引擎上有什么却别
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
- 有后缀名如(.js等)
- X 不是路径也不是核心模块(说明X可能是第三方库如"
axios等等")- 会根据’
module.path’一级一级去查找第三方库中node_moodules是否有X
- 会根据’
- 没有找到抛出异常not found
模块的加载过程
结论一:模块在被第一次引入时,模块中的 js 代码会被执行一次
// foo.js
console.log('foo module execute')
// main.js
require('./foo') // 会运行一遍 foo 模块中的代码
结论二:模块在被多次加载后,代码也会只运行一次:
- 这是因为每个模块对象有一个
loaded属性 - 加载了 loaded 就变为 true 了
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.js 和 curl.js
requires的使用
使用requires需要在入口文件加载前引用cdn
<script src='./lib/require.js' data-main="./index.js"></script>
导出模块
// /src/foo.js// 在 define 中写逻辑
define(function () {const name = 'foo'const age = 18function sum(num1, num2) {return num1 + num2}// 这里的 return 就导出了return {name,age,sum,}
})
引入模块
// /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 模块能够在多种不同的环境中运行,尤其是同时支持:
- AMD - 异步模块定义,用于浏览器端,如 RequireJS。
- CommonJS - 用于服务器端,如 Node.js。
- 全局变量 - 作为浏览器中的全局变量使用,当没有模块加载器时。
简单来说,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远程引入)
<script src="./lib/sea.js"></script>
导出模块
// /src/foo.jsdefine(function (require, exports, module) {const name = 'foo'const age = 18function sum(num1, num2) {return num1 + num2}// 可以通过 exports.name = name// 和 module.exports 两种module.exports = {name,age,sum,}
})
导入模块
// /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"
<script src="./index.js" type="module"></script>
导入导出的基本使用
导出
// 01.1xxxxlet title = "hello";
let url='www.baidu.com';
function say(){console.log("hello");
}
export{title,say}
导入
// 01.2xxxx
import{title,say}from './01.1模块的基本使用.js'
console.log(title);//很神奇吧
say()
//利用导出和导入 我们只需要暴露我们需要的功能就可以了
//如果在浏览器中使用 要给script加上type="module"
模块的具名导入和导出
具名导出
// ./05.2 xxxxx
export let site='www.baidu.com';//具名导出
export function add(a,b){return a+b;
}//必须要有名字 不然会报错
具名导入
// ./05.1xxxxxx
import {site} from "./05.2.js"
console.log(site);
import { add } from "./05.2.js";
console.log(add(1, 2));
模块批量导入和导出
导出
// 06.2
export let site='www.baidu.com';//具名导出
export function add(a,b){return a+b;
}//必须要有名字 不然会报错
导入
/* 使用 * as 别名可以批量导入,但是打包时会认为我们要用到这个模块里面的所有内容,
导致打包文件的体积过大,建议使用具名导入,打包是只会打包我们用到的一些文件 */
// 批量导入
import * as api from "./06.2.js"
console.log(api);
console.log(api.site);
起别名
导出起别名
let site='houdunren.com'
export{site}
let sites=['www.baidu.com','www.taobao.com','www.jd.com']
export{sites as web};//导出起别名
导入起别名
import {site as site1, web} from './07.2.js';
console.log(site1);//site1是site的别名
console.log(web);//web是sites的别名
默认导出
导出时使用default关键字,一个文件中只能默认导出一次
class User{static render(){return 'user static render'}
}
export default User;
导入
import Use from "./08.2.js"console.log(Use.render())
混合导入与导出
默认导出和正常导出一起混合使用
混合导出
export default class User{static render(){return 'user static render'}
}
let site='URL_ADDRESS.baidu.com'export {site}
混合导入
import hd,{ site } from "./09.2.js"
console.log(hd.render());
console.log(site);
//如果是批量导入 就使用api.default
多个模块合并导出
导出文件1
export let site='www.baidu.com';//具名导出
export function add(a,b){return a+b;
}//必须要有名字 不然会报错
导出文件2
export default class User{static render(){return 'user static render'}
}
let site='URL_ADDRESS.baidu.com'export {site}
合并导出文件
import *as t3 from './10.3.js'
import *as t4 from './10.4.js'
export {t3,t4}
导入文件
import *as api from './10.2.js'
console.log(api);
console.log(api.t3.default.render());
