Node.js模块化基本概念
Node.js遵循CommonJs模块化规范,CommonJS规定了模块的特性和各模块之间的相互依赖的关系 CommonJS规定: 模块化标识('module')、模块化定义(exports)、模块化引入(require)
// 在一个自定义模块中,module是一个最终导出的模块的标识,所有的expots都会追加到module对象中
const age = 20
// 向 module.exports 对象上挂载 username 属性
module.exports.username = 'zs'
// 向 module对象上挂载 sayHello 方法
exports.sayHello = function() {
console.log('Hello!')
}
exports.age = age
exports.sex = '男'
// 输出{ username: 'zs', sayHello: [Function (anonymous)], age: 20, sex: '男' }
// 在一个自定义模块中,默认情况下, module.exports = {}
const age = 20
// 向 module.exports 对象上挂载 username 属性
module.exports.username = 'zs'
// 向 module.exports 对象上挂载 sayHello 方法
exports.sayHello = function() {
console.log('Hello!')
}
exports.age = age
exports.sex = '男'
// 让 module.exports 指向一个全新的对象(以最后的module.exports为准,只会导出一个模块化标识)
module.exports = {
nickname: '小黑',
sayHi() {
console.log('Hi!')
}
}
// 输出{ nickname: '小黑', sayHi: [Function: sayHi] }
CommonJS模块化加载机制:(require时动态的,在运行时加载)
- 1、使用require()引入,在模块第一次加载时候会被缓存,这意味着多次调用require()不会导致代码会执行多次;
- 2、由于require是运行时候调用,所以require理论上可以运用在代码的任何地方;
- 3、使用require()加载自定义模块的时候,必须指定以./或../开头的路径标识符,如果加载自定义模块的时,如果没有指定./或../,则node会把它当做内置模块或者第三方模块进行加载;
- 4、在使用require()导入自定义模块时,如果省略了文件扩展名。则node会按照一下顺序加载文件:
- A、按照确切的文件名进行加载;
- B、补全的.JS扩展名进行加载;
- C、补全的.json的扩展名进行加载;
- D、补全.node扩展名进行加载;
- E、加载失败,终端报错;
- 备注:require()是不能识别.ts文件的扩展名,必须先转化成能识别扩展名;
- 5、node常见内置模块:例如 fs(读写文件)、path(路径)、http(请求) 等
- 6、node加载第三方模块: 默认会从当前模块的父目录开始,尝试从 /node_modules 文件夹中加载第三方模块,如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。
- 7、目录作为模块标识符,传递给require()进行加载是有三种方式:
- A、在被加载的目录找一个叫做package.json的文件,并寻找main属性,作为加载入口;
- B、如果目录内没有package.json文件,或者文件内没有没有main属性,则会加载目录下的index.js文件;
- C、如果以上两步都没有则会在终端报错,报告模块的缺失:Error: Cannot find module 'xxx'
es6模块化规范基本概念
es6模块化规范:
用import引入自定义模块的函数、对象或者基本类型等,自定义模块使用export(可以有多个)和exprot default(默认只有一个)进行导出,且export default的默认导出不能修改
//model.js
let e1='测试1';
let e2='测试2';
export {e2};
export default e1;
e1='测试3';
e2='测试4;
import {e1, e2 } from "./model" // 导入方式1
console.log(e1) // 输出"测试1"
console.log(e2) // 输出 "测试4"
import * as e from "./model" // 导入方式2(model中的对象全部通过*导入)
console.log(e.e1) //输出"测试1"
console.log(e.e2) // 输出 "测试4"
备注:如果想修改默认导出的值,可以使用export {e1 as default}这种方法。
// model.js修改
export {el as detault}
console.log(e1) // 输出"测试3"
console.log(e2) // 输出 "测试4"
export default和export的语法差异
1.export var e1 = { ... }是合法语句,可以导出对象、变量等,但是export default var e2 = "{ ... }"是不合法的(使用let和const也是一样)
2.export default的导出要直接添加标识符导出
例如: const e3 = "123"
export default e3
es6模块化编译(import是静态编译在编译的时候加载)
import导入的模块只是生成引用,等需要时才去取值,所以不存在缓存问题,也正是由于这个特性所以在自定义模块发生变化的时候,import是可以感知模块内,并导出最新的变量
//model.js
export const e1 = '测试1'
setTimeout(() => { e1 = "测试2" }, 500)
import { e1 } from './model'
console.log(e1) // 输出 "测试1"
setTimeout(() => { console.log(e1) }, 600) // 输出"测试2"
所以import为了实现像require()那样的动态加载功能,引入了import()函数,该函数返回一个Promist对象,不同的是require()是同步加载,import()是异步加载
import('./model.js').then(module => {
console.log(module.e1) // 输出"测试1"
setTimeout(() => {
console.log(module.e1) // 输出"测试1"
}, 600)
// setTimeout(() => {
console.log(module.e1) // 会报错,进入下面的catch,此时的模块还没有加载完成,异步获取不到报错
}, 400)
}).catch(err => {
console.log(err) // 异步加载没有完成报错进入这里
})
备注: import由于静态编译的特性在编译的时候会提升到模块的头部,理论可以放在其他位置进行引入,但是还是建议一开始放在头部,例如下面情况
const x = 1
// 报错,由于import的提升特性
if( x === 1) {
import {e1} from './model'
}