前端模块化(commonJs、ES Module)

模块的概念及使⽤原因

        模块化开发是我们开发当中用于组织和管理代码的方法,它的目的是将复杂的应用程序去拆分为更小和更好管理的模块单元,从而提高代码的复用性和可维护性。

        在早期的前端开发中,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看看是什么,是一个对象,展示当前模块对应的一些信息.

// 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,再执行。

// 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
  • 25
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值