前端模块化(commonJs、ES Module)
weijia_kmys 2024-10-01 09:03:02 阅读 97
模块的概念及使⽤原因
模块化开发是我们开发当中用于组织和管理代码的方法,它的目的是将复杂的应用程序去拆分为更小和更好管理的模块单元,从而提高代码的复用性和可维护性。
在早期的前端开发中,javascript代码通常以脚本的形式直接嵌在html文件中,或者通过多个脚本文件呢去进行一个处置,那这种方式存在一些问题,比如全局命名冲突,代码复用困难,依赖关系难以管理等等,特别是在多人协作的场景下,这种情况尤为常见,直到模块化的出现才从根本上去解决这些问题,这也正是js为什么需要模块的原因。
那js的模块化是什么?怎么用?先说概念,js模块化允许我们将功能相同的代码封装在独立的模块中,并且通过导入和导出的机制来管理模块之间的依赖关系,模块可以是独立的文件,也可以是一个包含多个相关功能的文件集合。
模块化规范
模块化的规范它具体有哪些分类呢?
第一个就是 ES Module,它是 ES6 引入的官方模块化规范,它使用 import 和 export 关键字来导入和导出模块。它也支持静态分析,这使得浏览器或者nodejs环境能够对它进行优化,从而实现更高效的模块加载。
第二个就是commonJS,最初它是用于服务端的js模块化规范,当下也被广泛应用于前端开发当中,它使用require和module.exports来导入和导出模块,nodejs就是commonJS模块化规范的一种实现。
这两种方法规范在现阶段的前端开发中使用最为广泛,除了这些还有像amd、cmd、umd等等,这些我们也基本不会在项目中进行使用,就不多说了。
(一)commonJS
在Nodejs中每个.js文件就是一个模块,每个模块中的代码都是通过类似如下代码去进行包裹的。好比说它已经把这个模块的架子搭好了,我们直接填充代码进去就可以了。通常我们的代码是写在这个函数里面的,函数提供的这些参数我么可以直接在模块中去访问。接下来看看这些参数是什么。
1. module
新建一个index.js文件,先打印一下module看看是什么,是一个对象,展示当前模块对应的一些信息.
<code>// index.js
console.log(module)
// 执行结果
{
id: '.',
path: '/Users/jiawei/Projects/WebstormProjects/demo/learning',
exports: {},
filename: '/Users/jiawei/Projects/WebstormProjects/demo/learning/index.js',
loaded: false,
children: [],
paths: [
'/Users/jiawei/Projects/WebstormProjects/demo/learning/node_modules',
'/Users/jiawei/Projects/WebstormProjects/demo/node_modules',
'/Users/jiawei/Projects/WebstormProjects/node_modules',
'/Users/jiawei/Projects/node_modules',
'/Users/jiawei/node_modules',
'/Users/node_modules',
'/node_modules'
]
}
(1)id:当前模块唯一标识,本模块中使用。如果被导出则其值与module.filename相等。
(2)path:当前模块文件夹绝对路径。
(3)exports:默认空,Nodejs中,module.exports里面的内容作为我当前模块默认导出的内容,如果要导出当前模块里面的一些方法或者变量,一般就是通过module.exports等于一个对象,并且在这个对象里面写key和value,然后导出去,如果没有给它内容,那默认是一个空对象,那在其他文件中或者其他模块中去导入这个模块的时候,导入进去的它就是一个空对象,他默认导出的就是model exports它里面的内容。
(4)filename:我们当前模块所在的一个绝对路径。
(5)loaded:它是表示我们当前模块是否被引用加载过。
(6)children:它是一个数组,里面存放了我们当前模块引用的一个又一个的模块。比如说新建一个 child.js,不写内容,然后去index.js里导入,再打印 module。可以看到children放入了一个对象,id为对应文件的绝对路径。
// index.js
const child = require('./child')
console.log(module)
// 打印结果
{
id: '.',
path: '/Users/jiawei/Projects/WebstormProjects/demo/learning',
exports: {},
filename: '/Users/jiawei/Projects/WebstormProjects/demo/learning/index.js',
loaded: false,
children: [
{
id: '/Users/jiawei/Projects/WebstormProjects/demo/learning/child.js',
path: '/Users/jiawei/Projects/WebstormProjects/demo/learning',
exports: {},
filename: '/Users/jiawei/Projects/WebstormProjects/demo/learning/child.js',
loaded: true,
children: [],
paths: [Array]
}
],
paths: [
'/Users/jiawei/Projects/WebstormProjects/demo/learning/node_modules',
'/Users/jiawei/Projects/WebstormProjects/demo/node_modules',
'/Users/jiawei/Projects/WebstormProjects/node_modules',
'/Users/jiawei/Projects/node_modules',
'/Users/jiawei/node_modules',
'/Users/node_modules',
'/node_modules'
]
}
(7)paths,在当前模块中去引用其他模块的时候,去查找这个模块的路径列表时,首先会在当前这个文件夹下的到node_modules去找,如果没有,再去当前文件夹的父级的node_modules里找,依次往父级的父级去找,一直找到根路径的node_modules,如果还没有就报错。
(8)parent: 它返回一个对象,表示调用该模块的模块。但是在最新的Nodejs 14.6版本中module.parent被弃用了,官方推荐使用 require.main 或者 module.children代替。我当前使用的node版本是 18.20.3,所以不显示这个属性。
2. exports
打印 exports 是一个空对象,module也有一个exports,exports就是module.exports的一个引用,就是往module.exports添加key时,exports也会有,往exports添加key时,module.exports同样会有。如果说直接给exports赋值一个值或对象,那么它就指向新的对象了。
// child.js
console.log('exports:', exports)
console.log('module.exports:', module.exports)
console.log('1.exports == module.exports:', exports == module.exports)
exports.username = 'jiawei'
module.exports.age = 18
exports = {}
console.log('2.exports == module.exports:', exports == module.exports)
// index.js
const child = require('./child')
console.log('child', child)
// 打印结果:
exports: {}
module.exports: {}
1.exports == module.exports: true
2.exports == module.exports: false
child { username: 'jiawei', age: 18 }
3. require
它是一个函数,也是对象,用于加载函数或者JSON,通常加载模块的path或者id标识,最终接收到的是module.exports导出的数据。
// index.js
const child = require('./child')
console.log(require)
// 执行结果:
[Function: require] {
resolve: [Function: resolve] { paths: [Function: paths] },
main: {
id: '.',
path: '/Users/jiawei/Projects/WebstormProjects/demo/learning',
exports: {},
filename: '/Users/jiawei/Projects/WebstormProjects/demo/learning/index.js',
loaded: false,
children: [ [Object] ],
paths: [
'/Users/jiawei/Projects/WebstormProjects/demo/learning/node_modules',
'/Users/jiawei/Projects/WebstormProjects/demo/node_modules',
'/Users/jiawei/Projects/WebstormProjects/node_modules',
'/Users/jiawei/Projects/node_modules',
'/Users/jiawei/node_modules',
'/Users/node_modules',
'/node_modules'
]
},
extensions: [Object: null prototype] {
'.js': [Function (anonymous)],
'.json': [Function (anonymous)],
'.node': [Function (anonymous)]
},
cache: [Object: null prototype] {
'/Users/jiawei/Projects/WebstormProjects/demo/learning/index.js': {
id: '.',
path: '/Users/jiawei/Projects/WebstormProjects/demo/learning',
exports: {},
filename: '/Users/jiawei/Projects/WebstormProjects/demo/learning/index.js',
loaded: false,
children: [Array],
paths: [Array]
},
'/Users/jiawei/Projects/WebstormProjects/demo/learning/child.js': {
id: '/Users/jiawei/Projects/WebstormProjects/demo/learning/child.js',
path: '/Users/jiawei/Projects/WebstormProjects/demo/learning',
exports: {},
filename: '/Users/jiawei/Projects/WebstormProjects/demo/learning/child.js',
loaded: true,
children: [],
paths: [Array]
}
}
}
(1)resolve:传进去一个模块的路径,它能解析出这个模块的绝对路径。
// index.js
console.log(require.resolve('./child'))
// 执行结果
/Users/jiawei/Projects/WebstormProjects/demo/learning/child.js
(2)main: 也是一个module对象,表示nodejs进程启动时加载的入口脚本。
(3)extensions:已经废弃了。
(4)cache:缓存已加载模块的对象,key是路径,value是module对象,因为导入了child,所以它也会被缓存,这个有什么用呢?在我们重复加载某个模块的时候,他就可以从缓存里读取,加快读取速度。
require的导入:
// child.js
const username = 'jiawei'
const age = 18
module.exports = {
username,
age
}
// index.js
// 方式一输出:
const child = require('./child')
console.log(child)
// 方式一输出结果:
{ username: 'jiawei', age: 18 }
// 方式二输出(解构):
const { username, age } = require('./child')
console.log(username, age)
// 方式二输出结果:
jiawei 18
4. __filename
当前模块文件绝对路径的文件名
console.log('__filename', __filename)
console.log('module.filename === __filename:', module.filename === __filename)
// 执行结果:
__filename /Users/jiawei/Projects/WebstormProjects/demo/learning/index.js
module.filename === __filename: true
5. __dirname
当前文件所在目录的完整目录名(dir 即 directory)
console.log('__dirname', __dirname)
console.log('module.path === __dirname', module.path === __dirname)
// 执行结果:
__dirname /Users/jiawei/Projects/WebstormProjects/demo/learning
module.path === __dirname true
(二)ES Module
使用import导入模块,使用export导出模块。
// child.js
export const username = 'jiawei'
export const age = 18
export const sayName = function () {
console.log('sayName', username)
}
// index.js
import { username, age, sayName } from './child.mjs'
console.log('username, age', username, age)
sayName()
执行结果如下:
结果有个警告,如果要导入ES Module,在package.json设置 "type":"module",或者使用.mjs扩展名。
方法一:先使用.mjs扩展名。将child.js和index.js改为child.mjs和index.js,导入路径里.js也改为.mjs,再执行。
<code>// child.mjs
export const username = 'jiawei'
export const age = 18
export const sayName = function () {
console.log('sayName', username)
}
// index.mjs
import { username, age, sayName } from './child.mjs'
console.log('username, age', username, age)
sayName()
// 执行结果:
username, age jiawei 18
sayName jiawei
但是我们基本不会去这么做。故将child.mjs和index.js改回child.js和index.js,导入路径里的.mjs也改回.js。
方法二:通过 命令行执行 npm init -y ,生成package.json,然后添加属性 "type": "module"。
{
"name": "learning",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module", // 添加
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
再执行,结果如下:
// child.js
export const username = 'jiawei'
export const age = 18
export const sayName = function () {
console.log('sayName', username)
}
// index.js
import {username, age, sayName } from './child.js'
console.log('username, age', username, age)
sayName()
// 执行结果:
username, age jiawei 18
sayName jiawei
导入内容也可以起个别名,打印出来是个对象,对象里有我们声明的变量或方法,然后可以通过 . 的形式去使用。如下所示。
// index.js
import * as child from './child.js'
console.log(child)
console.log(child.username)
console.log(child.age)
child.sayName()
// 执行结果:
[Module: null prototype] {
age: 18,
sayName: [Function: sayName],
username: 'jiawei'
}
jiawei
18
sayName jiawei
我们也可以使用 export {} 做一个统一的导出。
// child.js
const username = 'jiawei'
const age = 18
const sayName = function () {
console.log('sayName', username)
}
export { username, age, sayName }
// 第一种 index.js
import { username, age, sayName } from './child.js'
console.log('username, age', username, age)
sayName()
// 第一种 执行结果:
username, age jiawei 18
sayName jiawei
// 第二种 index.js
import * as child from './child.js'
console.log(child.username)
console.log(child.age)
child.sayName()
// 第二种 执行结果:
jiawei
18
sayName jiawei
有时候我们会看到,模块里有 export default,作为当前模块默认导出的内容,可以给它任意值。整个模块里export default只能存在一个,但是 export 可以存在多个。
// child.js
const username = 'jiawei'
const age = 18
const sayName = function () {
console.log('sayName', username)
}
export { username, age }
export {sayName}
export default function () {
console.log('export default age:', age)
}
// index.js
import {username, age, sayName} from './child.js'
import sayAge from './child.js'
console.log('username, age', username, age)
sayName()
sayAge()
// 执行结果:
username, age jiawei 18
sayName jiawei
export default age: 18
两个 import 导入同一个文件,我们可以写一个如下的结合形式,结果相同。
import sayAge, {username, age, sayName} from './child.js'
// 或者
import { default as sayAge, username, age, sayName} from './child.js'
如果模块多了,也需要写很多个。我们也可以以更好的形式去处理模块间的依赖关系,我们可以把import和export结合起来使用,建一个模块,将它作为所有模块统一导出入口。
// child.js
const username = 'jiawei'
const age = 18
const sayName = function () {
console.log('sayName', username)
}
export { username, age }
export {sayName}
export default function () {
console.log('export default age:', age)
}
// kid.js
const kidName = 'jiawei'
const kidAge = 10
const sayKidName = function () {
console.log('sayKidName', kidName)
}
export { kidName, kidAge, sayKidName }
// main.js
export { default as sayAge, username, age, sayName} from './child.js'
export { kidName, kidAge, sayKidName } from './kid.js'
// index.js
import { sayAge, username, age, sayName, kidName, kidAge, sayKidName } from './main.js'
console.log('username, age', username, age)
sayName()
sayAge()
console.log('kidName, kidAge', kidName, kidAge)
sayKidName()
// 执行结果:
username, age jiawei 18
sayName jiawei
export default age: 18
kidName, kidAge jiawei 10
sayKidName jiawei
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。