本文最后更新于 2025-01-07,文章内容可能已经过时。

https://www.bilibili.com/video/BV13W42197jR/?spm_id_from=333.337.search-card.all.click&vd_source=eecc2c8229facae905b6daed1650b44b

模块化概念

模块化是一种规范,将一个文件模块拆分多个文件模块,即可称为模块化!

模块化,通过模块拆分减少单独文件的代码量分成多个模块后,代码维护性更好,且每个模块代码之间都是相互隔离的,避免了变量名全局污染的可能性,另外也可以将重复的模块代码单独抽离成一个子模块,并供其它模块去使用提高代码的复用性!

没有模块化的一些问题

变量全局污染

变量全局污染,就是变量命名冲突的问题!

当在一个html文件中,同时引入两个js文件时,分别是a.jsb.js ,这时两个js文件内部有相同的变量名时,则后面的变量会覆盖前面的变量!

a.js

function getData(){
  return "我是a.js文件";
}

b.js

function getData(){
  return "我是b.js文件";
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="./css/app.css">
</head>
<body>
<script src="./a.js"></script>
<script src="./b.js"></script>
</body>
</html>

这里看到,b.js文件a.js文件之后引入,且内部变量覆盖a.js文件中的变量!

最终会打印: 我是b.js文件 !

文件依赖引入混乱

当在一个真实的开发环境时,且一个html文件引入大量的外部script链接,且每个链接之间产生互相依赖,且其中一个链接位置一旦发生变化,便会导致整个程序崩溃,无法正常运行!

数据安全问题

一个html引入的js外部链接,其每个js文件中的数据变量都是共享的,例如在a.js文件中定义数据变量,在b.js文件中可以直接获取,这样的数据是不安全的!

a.js

let user = {
  name: "张三",
  age: 18,
  address: "北京市"
}
function getData(){
  return "我是a.js文件";
}

b.js

b.js 在 a.js 后边引入,并可以直接操作 a.js 中定义的变量数据!

function getData(){
  user.name = "李四"
  console.log("a.js user", user.name)
  return "我是b.js文件";
}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="./css/app.css">
</head>
<body>
<script src="./a.js"></script>
<script src="./b.js"></script>
</body>
</html>

模块化规范

模块化规范,主要就是对文件模块代码进行分别导出,并在其它文件内部进行模块导入!

模块化规范主要有四种,分别为(CommonJs ESModule AMD CMD)!

CommonJs 是 Nodejs中提出的模块化规范,在Nodejs中用的比较多!

ESModule 是 ECMAScript提出的模块化规范,在浏览器中用的比较多!

CommonJs

CommonJsNodejs中提出的模块化规范,通过module.exports 可以对模块内容进行导出,通过require方法模块进行导入!

CommonJs 模块化是非官方标准Nodejs2009年提出的,主要用来开发服务器,当时js还没有模块化概念,所以社区就提出了CommonJs模块化,最后被NodeJs采纳使用!

初体验

student.js

let name = "张三";
let slogan = "让自己走起路来都带风!";

function getTel() {
  return "137299182731";
}

function getHobby() {
  return ["学习", "旅游", "羽毛球"];
}

exports.name = name;
exports.slogan = slogan;
exports.getTel = getTel;

school.js

let name = "尚硅谷";
let slogan = "让天下没有难学的技术";

function getTel(){
  return "010-12345678";
}

function getCities(){
  return "北京市海淀区西二旗";
}

exports.name = name;
exports.slogan = slogan;
exports.getTel = getTel;

index.js

使用require方法导入模块

const student = require("./student.js");
const school = require("./school.js");


console.log(student.name); // 张三
console.log(student.slogan); // 让自己走起路来都带风!
console.log(student.getTel()); // 13800138000
console.log(school.name); // 尚硅谷

这里使用了exports模块内部变量做了一个导出exports是一个对象,相当于给对象内部添加属性!

导出数据

导出数据两种方式: exportsmodule.exports

- exports.attr = value

- module.exports = value

注意事项:

  1. 每个模块中都有一个空对象{},在模块中可以通过this exports 和 module.exports来访问这个空对象,并且三个都是指向一个对象的引用地址!

    let name = "尚硅谷";
    let slogan = "让天下没有难学的技术";
    
    function getTel(){
      return "010-12345678";
    }
    
    function getCities(){
      return "北京市海淀区西二旗";
    }
    
    console.log(this)
    console.log(exports)
    console.log(module.exports)
    
    // 指向同一个对象地址
    console.log(this === module.exports && exports === module.exports) // true
    
  2. 无论修改某个对象中的值,最后向外导出的模块最终都是module.exports为准!

  3. 使用exports不能直接对其进行赋值!

    // exports = "value";  // 不允许
    exports.value = "value"; // 必须是这样
    module.exports = "value"; // 也可以这样
    
    • exports = "value"无论怎么修改,导出的对象都是module.exports

导入数据

导入数据,通过require引入模块引入的模块代码内部会被包裹成一个自执行函数,并获取module.exports的值,返回出去!

const { name, slogan } = require("./student.js");
const { name:stuName, getTel } = require("./school.js");


console.log(stuName); // 张三
console.log(slogan); // 让自己走起路来都带风!
console.log(getTel()); // 13800138000
console.log(name); // 尚硅谷

注意:

require("./student.js") 中的路径./ 表示为当前目录,是一个相对路径,且不能被省略!

相对路径在内部中,会转换为绝对路径,根据路径获取模块文件内容,最后包裹成自执行函数,获取module.exports值作为返回值!

扩展

  1. 每个模块中是如何能获取,exportsmodule.exports 对象的?

    • 其实每个模块都是一个函数体,内部会将模块代码通过函数进行包裹并执行,并获取module.exports中的值!
    function (exports, require, module, __filename, __dirname) {
       let school_name = "尚硅谷";
       let slogan = "让天下没有难学的技术";
    
       function getTel(){
         return "010-12345678";
       }
    
       function getCities(){
         return "北京市海淀区西二旗";
       }
    
       module.exports = {
         school_name,
         slogan,
         getTel,
         getCities,
       };
       console.log(arguments.callee.toString());
    }
    
    • arguments.callee.toString() callee可以获取函数本身函数体!
  2. 浏览器中使用CommonJs规范,本质上是不可以的,需要通过browserify工具来对文件进行编译后才能让浏览器识别!

    • 全局安装 browserify:

      npm i browserify -g
      
    • 编译文件

      browserify index.js -o build.js
      

      index.js编译输出结果为build.js!

    • 页面中使用

      <script src="./build.js"></script>
      

ESModule

ESModule模块化官方定义的模块化标准,通过指定的模块语法来实现模块化!

初体验

使用export变量进行导出!

school.js

export let school_name = "尚硅谷";
export let slogan = "让天下没有难学的技术";

export function getTel() {
  return "010-12345678";
}

function getCities() {
  return "北京市海淀区西二旗";
}

student.js

export let name = "张三";
export let slogan = "让自己走起路来都带风!";

export function getTel() {
  return "137299182731";
}

function getHobby() {
  return ["学习", "旅游", "羽毛球"];
}

index.js

使用import xx from "./xxx.js" 语法来导入模块代码!

import * as student from './student.js';
import * as school from './school.js';

console.log(student.name);
console.log(school.school_name);

这里的* 表示所有,as student 表示,将导入的模块对象放入到一个别名为student !

Node中运行ES模块

  1. 所有模块文件.js后缀改为.mjs!
  2. 可以在package.json中增加type属性,且值为module即可!

导出数据

使用export关键字可以对模块中的数据进行导出!

分别导出

分别导出,就是在每个声明变量函数前使用export关键字进行导出!

export let school_name = "尚硅谷";
export let slogan = "让天下没有难学的技术";

export function getTel() {
  return "010-12345678";
}
export const funName = () => {
  console.log("funName", funName)
}
function getCities() {
  return "北京市海淀区西二旗";
}
统一导出

统一导出,在代码尾部使用export关键字并跟一个类似于对象结构花括号,对声明变量名进行导出!

注意: export 后面 {} 花括号里面的内容不是以"键值对"形式去书写的,它类似于对象,但并不是属于对象范畴!

let school_name = "尚硅谷";
let slogan = "让天下没有难学的技术";

function getTel() {
  return "010-12345678";
}
const funName = () => {
  console.log("funName", funName)
}
function getCities() {
  return "北京市海淀区西二旗";
}

export {
  school_name,
  slogan,
  funName
}
默认导出

默认导出,在export关键字后面增加default关键字,后面可以跟字面量表达式!

默认导出 ,只能在模块中存在一个,且不能有多个默认导出!

let school_name = "尚硅谷";
let slogan = "让天下没有难学的技术";

function getTel() {
  return "010-12345678";
}
const funName = () => {
  console.log("funName", funName)
}
function getCities() {
  return "北京市海淀区西二旗";
}

export default {
  school_name,
  slogan,
  funName
}

这里会导出一个default对象!

export default function (){
  console.log("aaa")
}

也可以默认导出一个字面量

混入导出

混入导出,同时可以使用分别导出统一导出默认导出一起使用!

export let school_name = "尚硅谷";
export let  = "让天下没有难学的技术";

export function getTel() {
  return "010-12345678";
}

export const funName = () => {
  console.log("funName");
};

function getCities() {
  return "北京市海淀区西二旗";
}

export default getCities;

index.js

import * as student from './student.js';
import * as school from './school.js';
console.log(student.name);
console.log(school.school_name);
console.log(school.default);
console.log(school.funName());

导入数据

通过import关键字可以对模块进行导入,需要注意的是,导入的数据都是常量,且无法被修改!

TypeError: Assignment to constant variable.
import { school_name as name, slogan } from "./school.js"
// school_name = "new name"; 不能去修改值
导入全部

导入全部,是指分别导入以及默认导入都会被加载进来!

import * as school from "./school.js"
命名导入

只能用于分别导出默认导出不能用!

import { school_name as name, slogan } from "./school.js"
默认导入

export default

import school from "./school.js"
混入导入

school.js

export let school_name = "尚硅谷";
export let slogan = "让天下没有难学的技术";

export function getTel() {
  return "010-12345678";
}

const funName = () => {
  console.log("funName");
};

function getCities() {
  return "北京市海淀区西二旗";
}

export { funName }

export default getCities;
import getCities, { school_name, slogan, getTel, funName } from "./school.js";

console.log(getCities);
console.log(school_name);
console.log(slogan);
console.log(getTel());
funName();
动态导入

动态导入,也是按需导入,当触发某一个时机时,可以加载某一个模块!

import可以当做一个函数去执行,返回一个promise!

const btn = document.querySelector('button');

btn.onclick = dsyncImport;
async function dsyncImport() {
  let data = await import('./school.js');
  console.log("dsyncImport", data);
}
import不接收数据

当一个模块没有导出语句时,也可以通过import语法将模块进行导入执行模块中的代码!

import "./school.js"

这里会导入该模块,并执行模块中的代码