前端模块化(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



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。