模块化——将整个代码分成小模块

一、原始模块化的问题

  • 早期的网页中,没有实质的模块化规范,当时实现模块化的方式,就是原始地通过script标签引入多个js文件
    - 例如:引入jquery是直接引入一个js文件<script src="./jquery.js"></script>,$中有jquery中所有的 功能
    - 问题:
  1. 无法选择要引入这个模块的哪些内容(一引全引)
  2. 在复杂的模块常见下容易出错(引入顺序)
  • 解决方法:第三方的模块化规范CommonJS、ES官方的模块化规范(2015年)

二、CommonJS

  • 2009年,Nodejs采用CommonJS模块化
    - 直到2015年,ES官方才推出模块化规范
    - Node.js支持两种模块化,默认支持的模块化规范是CommonJS,在CommonJS中,一个JS文件就是一个模块

1. 定义一个模块:

  1. 定义一个模块时,模块中的内容默认不能被外部看到(CommonJS就是一个闭包),但是可以通过exports设置要暴露出去的内容,exports其实就是一个对象

图片.png

  1. 访问exports的方式有两种 console.log(exports === module.exports)
  2. 当我们在其他模块引入当前模块时,require(“”)函数的返回值就是exports对象,因此可以将要暴露的数据设置为exports的属性
exports.a = "孙悟空"
exports.b = {
  name:"猪八戒",
  age:28,
  gender:"男"
}
exports.c = function fn(){
  console.log("哈哈")
}
  1. 可以通过module.exports = {}给exports对象赋值

修改值:此后修改暴露的内容时,相当于在修改modul对象的exports属性,那么所有指向exports对象的都会受到影响;exports变量存储的地址不变,该地址指向的对象的内容变化了,因此其他引入exports的地方(它们的地址永远不变),能看到修改后的内容

  1. 不可以通过exports = {}给exports对象赋值

修改地址:此后修改暴露的内容时,相当于重新给exports变量赋值,改变量,只会影响该变量本身;此时exports存储的地址变了,而其他引入exports的地方,它们存储的地址不变,因此它们看不到任何修改后的内容

// 修改module对象的exports属性
// 所有引入该模块(module.exports)的地方都会受到影响
module.exports = {
  a : "孙悟空",
  b : {
      name:"猪八戒",
      age:28,
      gender:"男"
  },
  c : function fn(){
        console.log("哈哈")
       }
}

// 这是在给exports重新赋值
// 修改exports变量,只会影响exports变量自身,其他引入了该模块的模块不会受到影响
exports = {
  a:xxx,
  b:xxx,
  c:xxx
}

2. 引入一个模块:

  1. 使用require("模块的路径")函数来引入模块
  • 引入自定义模块时,模块名要以../或者./开头(即模块的路径是相对路径)
  • 可以省略扩展名.js(node会自动为文件补全扩展名,先补全.js,再.json,再.node)
const m1 = require("./m1") 
 // 完整写法是"m1.js"
  1. 引入核心模块(第三方模块)时,
  • 直接写核心模块的名字即可,例如 const path = require("path") //引入node内置的path模块
  • 可以在核心模块名字前面加node:,例如 const path = require("node:path")
  1. Node.js会将以下内容视为CommonJS模块:
  • 扩展名为.cjs时,遵循CommonJS的模块化标准;m1.js文件 m1.cjs文件
  • package.json文件的"type":"commonjs"时,扩展名为.js的文件也遵循CommonJS模块化
  • 一个名为hello文件夹为模块时,require("./hello")相当于require("./hello/index.js"),hello文件夹的默认入口文件是index.js
  • 文件扩展名为mjs、cjs、json、node、js以外的值时(type属性不是module时)
  1. require()是同步加载模块的方法,所以无法用来加载ES6的模块(加载ES模块,需要用import()方法来加载)

  2. 默认情况下,Node使用CommonJS模块化标准,若想使用ES的模块化标准,有两个方法:

  • 把文件扩展名改为.mjs
  • 修改package.json文件,修改属性type为"type":"module"
    console.log(module)在CommonJS标准下module才有值

3. CommonJS运行原理

  1. Node将所有的CommonJS模块封装到一个立即执行函数中,该函数有五个参数
(function(exports, require, module, __filename, __dirname){
  // 模块代码会被放在这里
  let a = 10;
  const b = { }
  /*
    console.log(arguments)  
    得到exports, require, module, __filename, __dirname
  */
})
  1. 怎么证明这一点?找到一个只有函数里有的东西——arguments实参,一个类数组
  • _ _filename表示当前模块的绝对路径
  • _ _dirname表示当前模块所在目录的绝对路径
  • module是一个对象,里面有exports属性

图片.png

  • require是一个函数

4. 总结

CommonJS记住 怎么导出module.exports怎么导入require 就行了

  • 不涉及别名,因为导入时是用变量接收的const a = require("./xxx")

  • 如果只想导入其中某个变量呢

    1. 要么按需引入const a = require("./m3").name
    2. 要么解构赋值const {name,gender} = require("./m3")
      (将一个对象的属性拆解给对应的变量,左边的name、gender是变量,因为key和value一致,省略了value)

三、ES模块化

让Node遵循ES模块化标准:
图片.png

1. 模块的导出export和引入import

注意:

  • 自定义模块中,无论文件的扩展名为.mjs.js,ES模块都不能省略扩展名(官方标准)

    webpack是打包工具,会自动帮助补充扩展名

  • 核心模块中,

1.1命名导出export以及引入{}:

  • 使用 export 关键字将模块中的变量、函数或类进行命名导出
  • 引入时{}里的名字必须 === 导出时的名字,或者import {a as hello} from "./m1.mjs"
  • 引入全部变量、函数、类:import * as m4 from "./m4.mjs"(全部引入的话,项目会臃肿)
import {a,b,c,d……} from "./m4.mjs"   
//从模块里导入a,b,c,d(名字必须跟定义时一致)
// 相当于把这个模块导入成一个对象,对这个对象进行解构赋值

import {a as hello, b, c} from "./m4.mjs" 
// 利用as可以起别名

import * as m from "./m4.mjs"
// 把m4.mjs模块的所有东西引进来,统一放在m这个对象中
// 开发时尽量避免这种情况
  1. 导出单个变量或常量
// 导出单个变量
export const myVariable = 42;

// 也可以在声明时直接导出
const myVariable = 42;
export { myVariable };
import { myVariable } from './moduleA';
  1. 导出多个变量或常量
export const var1 = 'foo';
export const var2 = 'bar';
import { var1, var2 } from './moduleB';
  1. 导出函数或类:
// 导出单个函数
export function myFunction() { }
// 或者
function myFunction() {}
export { myFunction };


// 导出单个类
export class MyClass { }
// 或者
class MyClass { }
export { MyClass };
import { myFunction, MyClass } from './moduleC';

1.2 默认导出(export default)以及引入

  1. 一个模块只有一个默认导出(export default只有一个)

    可以将一个值、函数、类或对象作为默认导出  
    
  2. 引入其他模块的"默认导出"时,可以随意命名 import xxx from "./m1.mjs"

  3. 对于 ES6 模块中的默认导出(Default Export),通过 export default 语法将要导出的内容赋值给 default。这样在导入模块时,可以使用 import 语句指定一个名称,它会引用被赋值给 default 的内容。

  4. 通过 ES模块化 导入的内容都是常量,不能直接修改常量(即不能直接修改常量存储的地址值),但是常量并不影响对象的修改

import {c} from "./m4.mjs" 
c.name = "猪八戒"  // c存储的地址没有变,但是地址指向的对象里的属性name,变了
console.log(c) 
// 发现之前模块里定义的对象的name由"孙悟空"变成了"猪八戒"
export default function sum(a,b){}
// import sum,{a,b,c} from "./m4.mjs"  默认导出不要写在中括号里
// 默认导出后面需要一个值,而不是语句(export default let d = 20)

// 这样的默认导出才是正确的
let d
export default d = 20
  1. ES模块都是运行在严格模式下(即,只要启动ES模块,就默认开启了严格模式)
  2. 浏览器中也支持ES模块化,但是我们通常不会直接使用(要考虑兼容性问题),通常都会结合打包工具
  • 23
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值