nodejs js 导入导出机制
1、.js模块化
1.1模块
模块是Node.js里面一个很基本也很重要的概念,各种原生类库是通过模块提供的,第三方库也是通过模块进行管理和引用的。本文会从基本的模块原理出发,到最后我们会利用这个原理,自己实现一个简单的模块加载机制,即自己实现一个require。
Node 使用 JavaScript 与 commonjs 模块,并把 npm/yarn 作为其包管理器。
1.2模块化
什么是模块?
- 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起。
- 块的内部数据/实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信。
一个模块的组成 - 数据—>内部的属性
- 操作数据的行为—>内部的函数
模块化
编码时是按照模块一个一个编码的, 整个项目就是一个模块化的项目
为什么需要模块化
当项目功能越来越多,代码量便也会越来越多,后期的维护难度会增大,此时在JS方面就会考虑使用模块化规范去管理。
2、nodejs js 导入导出机制
导入:require(xxx.js)
导出:exports,module.exports;
Node.js里面如果要导出某个内容,需要使用module.exports,使用module.exports几乎可以导出任意类型的JS对象,包括字符串,函数,对象,数组等等。
node.js中有两种模块加载方式:
CommonJS,ECMAScript modules;其中CommonJS为 NodeJS 内部实现,ES Modules 为 JS 标准加载方式。
2.1 CommonJS
在 Node.js 模块系统中,每个文件都被视为一个单独的模块。
2.1.1 exports module exports
exports 是 module.exports 的简写—exports 只能使用语法来向外暴露内部变量(建议不要使用):exports.xxx = xxx;—module.exports 既可以通过语法,也可以直接赋值一个对象。
2.1.2 exports和module.exports的区别
exports和module.exports这两个变量都是通过下面这行代码注入的。
compiledWrapper.call(this.exports, this.exports, this.require, this,
filename, dirname);
初始状态下,exports =module.exports = {},exports是module.exports的一个引用,如果你一直是这样使用的:
exports.a = 1;
module.exports.b = 2;
console.log(exports === module.exports); // true
上述代码中,exports和module.exports都是指向同一个对象{},你往这个对象上添加属性并没有改变这个对象本身的引用地址,所以exports =module.exports一直成立。
但是如果你哪天这样使用了:
exports = {
a: 1
}
或者这样使用了:
module.exports = {
b: 2
}
那其实你是给exports或者module.exports重新赋值了,改变了他们的引用地址,那这两个属性的连接就断开了,他们就不再相等了。需要注意的是,你对module.exports的重新赋值会作为模块的导出内容,但是你对exports的重新赋值并不能改变模块导出内容,只是改变了exports这个变量而已,因为模块始终是module,导出内容是module.exports。
2.2 ECMAScript modules
ES Modules 为 JS 标准加载方式。—使用 import 和 export 导入导出模块。—Node.js 默认将 JavaScript 代码视为 CommonJS 模块。可以通过.mjs 文件扩展名,package.json 'type’字段,将 JavaScript 代码将 ECMAScript 视为模块。
3 总结
require不是黑魔法,整个Node.js的模块加载机制都是JS实现的。
每个模块里面的exports, require, module, __filename, __dirname五个参数都不是全局变量,而是模块加载的时候注入的。
初始状态下,模块里面的this, exports, module.exports都指向同一个对象,如果你对他们重新赋值,这种连接就断了。
对module.exports的重新赋值会作为模块的导出内容,但是你对exports的重新赋值并不能改变模块导出内容,只是改变了exports这个变量而已,因为模块始终是module,导出内容是module.exports。
为了解决循环引用,模块在加载前就会被加入缓存,下次再加载会直接返回缓存,如果这时候模块还没加载完,你可能拿到未完成的exports。
Node.js实现的这套加载机制叫CommonJS。
参考文章:
- https://blog.csdn.net/lxmuyu/article/details/124140050?spm=1001.2014.3001.5502
- https://www.php.cn/js-tutorial-459562.html
4 扩展—变量的作用域及类型
4.1变量作用域的类型
在Node中,存在着模块作用域的概念,即默认情况下,模块彼此间无法访问对方的成员,只能通过require方法来进行模块间通信。当然,模块作用域的存在也带来了好处,使得我们可以避免变量命名冲突的情况。
- 变量的作用域(scope) ,指的是变量在脚本代码中的可读、写的有效范围,也就是脚本代码中可以使用这个变量的区域。
- NodeJS中的变量可分为三种类型:
局部变量,作用域:定义该变量的函数内。
局部全局变量,作用域:定义该变量的JS文件。
公共全局变量,作用域:程序内可访问global对象的任何一个地方。
4.2模块作用域的特点
- 模块是完全封闭的
- 外部访问不到内部
- 内部也访问不到外部
这时,我们可以使用require方法来解决这一问题,require方法默认会执行被加载模块中的代码,并得到被加载模块的exports接口对象(该对象默认为一个空对象)。注意:require方法只是加载并执行其他模块,并不会污染其他模块。默认情况下,模块彼此间无法访问对方的变量或方法。 - 全局变量声明在所有函数之外;
- 局部变量是在函数体内声明的变量或者是函数的命名参数;
- 块级变量是在块中声明的变量,只在块中有效。
变量的作用域跟声明方式有很密切的关系。使用 var 声明的变量的作用域有全局作用域和函数作用域,没有块级作用域;使用 let 和const 声明的变量有全局作用域、局部作用域和块级作用域。由于 var 支持变量提升,所以 var 变量的全局作用域是对整个页面的脚本代码有效;而 let 和 const 不支持变量提升,所以 let 和 const 变量的全局作用域指的是从声明语句开始到整个页面的脚本代码结束之间的整个区域,而声明语句之前的区域是没有效的。
同样,因为 var 支持变量提升,而 let 和 const 不支持变量提升,所以使用 var 声明的局部变量在整个函数中有效,而使用 let 和 const 声明的局部变量从声明语句开始到函数结束之间的区域有效。
需要注意的是,如果局部变量和全局变量同名,则在函数作用域中,局部变量会履盖全局变量,即在函数体中起作用的是局部变量;在函数体外,全局变量起作用,局部变量无效,此时引用局部变量将出现语法错误。
对块级变量来说,其作用域是块级变量声明语句开始到块结束之间的区域。在块开始到块级变量声明语句之间的区域为“暂时性死区”,在这个区域,块级变量没有效。
另外,在非严格运行模式中,变量可以不需要声明,这些没有声明的变量,不管在哪里使用都属于全局变量。通常不建议变量不声明而直接使用,因为这样有可能会产生一些不易发现的错误。