模块的概念及使⽤原因
模块化开发是我们开发当中用于组织和管理代码的方法,它的目的是将复杂的应用程序去拆分为更小和更好管理的模块单元,从而提高代码的复用性和可维护性。
在早期的前端开发中,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
还没有评论,来说两句吧...