JavaScript模块的导出和导入之export和module.exports的区别

export和module.exports

(需要前面的export没有“s”,后面的module.exports 有“s”)

使用两者根本区别是

**exports **返回的是模块函数

**module.exports **返回的是模块对象本身,返回的是一个类

使用上的区别是
exports的方法可以直接调用
module.exports需要new对象之后才可以调用


 

在js编程中经常会有模块的导出导入,涉及到一些导入导出关键字

  • 导出关键字
    • module.exports
    • exports
    • export
    • export default
  • 导入关键字
    • require
      • const xxx = require("模块名")
    • import
      • import { xxx } from "模块名"
      • import xxx from "模块名"
      • import xxx1, {xxx2, xxx3,...} from "模块名"
      • import * from "模块名"

因为在实际开发中经常会混淆这些用法,所以想要弄清楚这些的区别,让自己明白自己到底在写什么。本文作为学习笔记输出。

模块规范

JS模块化编程分了两种规范:CommonJS模块规范和ES6模块规范。

  • CommonJS模块规范 —— CommonJS规范中,以module.exports导出接口,以require引入模块
  • ES6模块规范 —— ES6标准规范中,以export指令导出接口,以import引入模块

在Node.js编程中,Node模块系统遵循的是CommonJS规范。

CommonJS模块规范

CommonJS规范规定: 每个js文件就是一个模块,有自己的作用域。
在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
如果要暴露给其他程序,需要以module.exports导出接口,以require引入模块。

module.exports 和 exports

module.exports / exports: 只有 Node 支持的导出
模块导出的时候,导出的是module.exports,不是exports
module.exports可以导出所有的类型。对象,函数,字符串、数值等。

每一个js文件通过node执行时,都自动创建一个module变量和一个exports变量。
module变量代表当前模块。这个变量是一个对象,同时,module对象会创建一个叫exports的属性(即module.exports),该属性初始化的值是 {},是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。

//logtes.js
console.log("我是外部js,没有使用export")
console.log(module)

执行node test1.js的打印结果:

 path: 'C:\\Users\\xxwang\\Documents\\pmms\\TS\\tsdemo\\day01',               
  exports: {},                                                                    
  filename: 'C:\\Users\\xxwang\\Documents\\pmms\\TS\\tsdemo\\day01\\logtes.js',
  loaded: false,                                                                  
  children: [],                                                                   
  paths: [                                                                        
    'C:\\Users\\xxwang\\Documents\\pmms\\TS\\tsdemo\\day01\\node_modules',     
    'C:\\Users\\xxwang\\Documents\\pmms\\TS\\tsdemo\\node_modules',            
    'C:\\Users\\xxwang\\Documents\\pmms\\TS\\node_modules',                    
    'C:\\Users\\xxwang\\Documents\\pmms\\node_modules',                        
    'C:\\Users\\xxwang\\Documents\\node_modules',                              
    'C:\\Users\\xxwang\\node_modules',                                         
    'C:\\Users\\node_modules',                                                    
    'C:\\node_modules'                                                            
  ]                                                                               
}                                                                                 

Process finished with exit code 0

默认exports变量是对module.exports的引用,即exports和module.exports指向同一个内存块。 这等同在每个模块头部,有一行这样的命令。

var exports = module.exports;
  • 当通过exports去改变内存块里内容时,module.exports的值也会改变
  • 当通过module.exports去改变内存块里内容时,exports的值也会改变
  • 当module.exports被改变的时候,exports不会被改变
  • 当exports被改变的时候,module.exports不会被改变

所以,exports属性的出现应该可以直接向exports对象添加方法,从而方便对外输出模块接口。不过当module.exports改变时,exports与module.exports也就断开了链接,所以最好不要采用这种方式,统一采用module.exports方式。

// module_export_demo.js
module.exports.a = 100
console.log("log1: " + exports.a)  // log1: 100

exports.a = 200; 
console.log("log2: " + module.exports.a)  // log2: 200

module.exports = "hello"
console.log("log3: " + JSON.stringify(exports)) // log3: {"a":200}
复制代码

module.exports可以导出所有的类型。对象,函数,字符串、数值等。 语法示例:

// module_export_demo2.js
var x = 5

var str = "hello"

var addX = function (value) {
  return value + x
};

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

// module.exports.x = x
// module.exports.str = str
// module.exports.addX = addX
// module.exports.Point = Point

module.exports = {
  x: x,
  str: str,
  addX: addX,
  Point: Point
}

// require时对比下两种方式x的值到底取哪个
exports = {
  x: 10,
}
复制代码

require

requirer用于加载模块,是node的一个全局方法,使用非常简单

const xxx = require("模块名")
复制代码

读入并执行一个JavaScript文件,返回模块的exports对象。如果没有发现指定模块,会报错。

require方法接受以下几种参数的传递:

  • 原生模块,如http、fs、path等
  • 相对路径的文件模块,如./mod或../mod
  • 绝对路径的文件模块,如 /pathtomodule/mod
  • 第三方模块,如koa等

在模块目录中通常有一个package.json文件,并且将入口文件写入main字段

    // package.json
    { "name" : "some-library",
      "main" : "./lib/some-library.js" }
复制代码

require发现参数字符串指向一个目录以后,会自动查看该目录的package.json文件,然后加载main字段指定的入口文件。
如果package.json文件没有main字段,或者根本就没有package.json文件,则会加载该目录下的index.js文件或index.node文件

因为模块导出的实际是module.exports,所以require只能看到通过 module.exports 导出的内容,看不到通过exports导出的内容。它相当于module.exports的传送门,module.exports后面的内容是什么,require的结果就是什么,对象、数字、字符串、函数...再把require的结果赋值给某个变量。 针对上面的 module_export_domo2.js 文件,引入模块示例

// node require_demo2.js
const demo2 = require("./module_export_demo2")

console.log(demo2.x)  // 5 也证明了引入的是module.export的内容
console.log(demo2.str)  // hello
console.log(demo2.addX(15)) // 20  = 5 + 15 

let point = new demo2.Point(3, 4)
console.log(point.toString()) //  (3, 4)
复制代码

require 是运行时的,其参数可以是表达式

let value = 2
const demo2 = require("./module_export" + "_demo" + value)
复制代码

require函数加载模块

  1. require函数加载模块顺序按照其在代码中出现的顺序

  2. require函数加载模块是同步的,只有加载完成,才能执行后面的操作

  3. require函数加载的模块是被输出的值的拷贝,不会受到模块内容变化影响

     // module_export_demo3.js
     var counter = 3;
     function incCounter() {
       counter++;
     }
     module.exports = {
       counter: counter,
       incCounter: incCounter,
     };
    复制代码
    
     const demo3 = require("./module_export_demo3")
     console.log(demo3.counter);  // 3
     demo3.incCounter();
     console.log(demo3.counter); // 3
    复制代码
    

    counter输出结果说明module_export_demo3模块内部的变化就影响不到counter了

  4. 模块第一次被加载时会执行一次,后续被加载时不会再执行,都是从缓存中获取的

    // module_export_demo3.js
    console.log("hello")
    module.exports = "wrold"
    复制代码
    
    const demo3 = require("./module_export_demo3")
    const demo3_1 = require("./module_export_demo3")
    const demo3_2 = require("./module_export_demo3")
    const demo3_3 = require("./module_export_demo3")
    const demo3_4 = require("./module_export_demo3")
    const demo3_5 = require("./module_export_demo3")
    console.log(demo3)
    console.log(demo3_1)
    
    // hello
    // wrold
    // wrold
    复制代码
    

    hello只会被打印一次说明console.log("hello")语句只执行了一次,module_export_demo3.js只被加载了一次。

ES6模块规范

ES6发布的module并没有直接采用CommonJS,甚至连require都没有采用,也就是说require仍然只是node的一个私有的全局方法,module.exports也只是node私有的一个全局变量属性,跟标准什么关系都没有。
ES6模块规范是,在创建JS模块时,export 语句用于从模块中导出函数、对象或原始值,以便其他程序可以通过 import 语句使用它们。

export 和 export default

ES6模块导出有两种方式:export(命名导出) 和 export default(默认导出)。 在导出多个值时,命名导出非常有用。在导入时,必须使用相应对象的相同名称。但是,可以使用任何名称导入默认导出。

export语法介绍

// 导出单个特性
export let name1, name2, …, nameN; // also var, const
export let name1 = …, name2 = …, …, nameN; // also var, const
export function FunctionName(){...}
export class ClassName {...}

// 导出列表
export { name1, name2, …, nameN };

// 重命名导出
export { variable1 as name1, variable2 as name2, …, nameN };

// 默认导出
export default expression;
export default function (…) { … } // also class, function*
export default function name1(…) { … } // also class, function*
export { name1 as default, … };

// 导出"引入模块的导出值"
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;
export * from …; // 导出"引入模块的所有导出值",不包括模块的默认导出值
export { default } from …; // 导出"引入模块的默认导出值"
复制代码
  • export与export default均可用于导出常量、函数、文件、模块等
  • 通过export方式导出,在导入时要加{ },export default则不需要
  • 在一个文件中,export可以有多个,export default仅有一个
  • 大部分风格建议,模块中最好在末尾用一个export导出所有的接口
export 1 // 这种写法错误

// 正确的写法
const value = 1
export { value }

// 或者
export const value = 1

// 或者
const value = 1
export default value

// 或者
export default 1
复制代码

export default是别名的语法糖,这个语法糖的好处在是

  • import的时候,可以省去花括号{}。
  • import的时候, 可以起任何变量名表示引入变量

所以如果import的时候,你发现某个变量没有花括号括起来(除了* 号),是因为该变量是通过export default 导出的。

// d.js
// 导出函数
export default function() {}

// 等效于:
// function a() {};
// export {a as default};

复制代码
import a from "d.js" // a 是 {defalut as a}的替代写法。
复制代码

所以使用export default命令,为模块指定默认输出,这样就不需要知道所要加载模块的变量名。

// a.js
let sex = "boy";
export default sex //sex不能加大括号 等价于 export {sex as default}
复制代码

本质上,a.js文件的export default输出一个叫做default的变量,然后系统允许你为它取任意名字。 自然default只能有一个值,所以一个文件内不能有多个export default。

// b.js
import any from "./a.js"
import any12 from "./a.js" 
console.log(any, any12)   // boy,boy
复制代码

import

require 和 import是完全不同的两种概念。require是赋值过程,import是解构过程 const xxx = require("模块名") import { xxx } from "模块名"

  • import是编译时的, 必须放在文件开头,否则会报错
  • import后面跟上花括号的形式是最基本的用法,花括号里面的变量与export后面的变量一一对应。
import {a} from ..
复制代码
  • 支持给变量取别名。因为有的时候不同的两个模块可能有相同的接口,可以给这个变量取一个别名,方便在当前的文件里面使用。
import {a as a_a} from ..
复制代码

import介绍语法

import defaultExport from "module-name";
import * as name from "module-name";
import { export1 } from "module-name";
import { export1 as alias1 } from "module-name";
import { export1 , export2 } from "module-name";
import { foo , bar } from "module-name/path/to/specific/un-exported/file";
import { export1 , export2 as alias2 , [...] } from "module-name";
import defaultExport, { export1 [ , [...] ] } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name";
var promise = import("module-name"); // 动态模块加载,返回的是一个promise对象
复制代码

Node为何支持export / import

我们经常会看到在node中也会用export / import,这是什么呢? 我们在node中使用babel支持ES6,仅仅是将ES6转码为ES5再执行,import语法会被转码为require。因为目前所有的引擎都还没有实现export / import。
如何让Node.js支持ES6的语法具体参考在node环境中支持ES6代码

// ES6语法
import {a} from "./demo.js"
// 转码ES5后
var _demo = require("./demo.js")
复制代码

这也是为什么在使用module.exports模块导出时,在引入模块时使用import仍然起效,因为本质上,import会被转码为require去执行。

总结

CommonJS规范中,建议尽量都用 module.exports 导出,然后用require导入 ES6规则中,大部分风格建议,模块中最好在末尾用一个export导出所有的接口

  • module.exports / exports: 只有 Node 支持的导出
  • require: Node 和 ES6 都支持的引入
  • export / import : 只有ES6 支持的导出引入

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/12866.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Verilog | 二进制与格雷码

一、格雷码简介 格雷码是一个叫弗兰克格雷的人在 1953 年发明的,最初用于通信。格雷码是一种循环二进制码或者叫作反射二进制码。格雷码的特点是从一个数变为相邻的一个数时,只有一个数据位发生跳变,由于这种特点,就可以避免二进…

HttpServletRespon

1、HttpServletRespon对象 在Servlet API中,定义了一个HttpResponse接口,它继承于ServletResponse接口,专门用于封装HTTP响应消息 HTTP响应消息分为响应状态行、响应消息头、响应消息体三部分,所以HttpResponse接口中定义了向客…

Qt模型视图结构

一.模型视图介绍 1.Model/View(模型/视图结构) 视图(View)是显示和编辑数据的界面组件, 模型(Model)是视图和原始数据之间的接口 2.视图组件有:QListView QTreeView QTableView,QColumnView,QHeaderView 模型组件有:QStringListM…

设计模式-原型模式

原型模式 文章目录 原型模式什么是原型模式为什么要用原型模式原型模式的实现方式浅拷贝深拷贝 如何实现深拷贝递归拷贝对象序列化与反序列化优化浅拷贝与深拷贝 总结 什么是原型模式 如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分…

java基于mvc的停车收费系统mysql

系统需要解决的主要问题有: (1)车位管理模块 添加车位、查看车位状态、车位信息查询等。 (2)客户信息管理模块 客户基本信息录入、客户信息查询等。 (3)卡业务办理 添加卡信息、查余额查询、卡充值。 (4)车辆信息管理模块 车牌信息录入等。 (5)收费管理 可以调整相应…

通过工具生成指定 类型 大小 文件

今天给大家介绍一个神器 首先 大家在开发过程中或许经常需要涉及到文件上传类的功能 需要测试文件过大 空文件等等清空 不同大小的文件 而这种文件大小是比较不好控制的 但大家可以下载我的资源 文件生成工具(可生成指定大小 类型文件) 下载下来里面就有一个 fileGeneration…

开发常用的 Linux 命令1(文件和目录)

开发常用的 Linux 命令汇总1(文件和目录) 作为开发者,Linux是我们必须掌握的操作系统之一。因此,在编写代码和部署应用程序时,熟练使用Linux命令非常重要。这些常用命令不得不会,掌握这些命令,…

干货分享 | 如何在业余时间学习数据分析?

从社会学背景的数分小白到独揽公司市场部的数据分析,和大家简单分享我的数据分析升级打怪之路。 简单说就是两点【培养数据分析思维】【提升数据分析技能】 思维的培养主要靠书籍和课程 以下是一些对我影响较大的书籍,按照【入门、进阶、高阶】简单做了…

Vue2-黑马(十)

目录: (1)vuex-actions (2)vuex-调用actions (3)vue2实战-项目搭建 (1)vuex-actions 有这么一个需求,绿色的组件从服务器获取数据放入store,主…

图片英文翻译成中文转换器-中文翻译英文软件

您正在准备一份重要的英文资料或文件,但是您还不是很熟练地掌握英文,需要翻译才能完成您的任务吗?哪个软件能够免费把英文文档翻译成中文?让我们带您了解如何使用我们的翻译软件来免费翻译英文文档为中文。 我们的翻译软件是一款功…

grep命令详解(如何匹配多个字符串)(grep指令)(grep -q)

文章目录 20221011 grep如何匹配多个标志?20230412 grep -q(Use grep -q instead of comparing output with [ -n .. ].警告)20230421 匹配前后空格\s 参考文章:grep命令详解 | grep -c [被搜索文本] # 统计被搜索文本出现的次数…

冯诺依曼体系结构

冯诺依曼体系结构 目录 冯诺依曼体系结构引入1、冯诺依曼体系结构1.1 内存1.2 操作系统预加载 2、操作系统2.1 理解管理2.2 系统调用接口2.3 操作系统四大基本功能 引入 冯诺依曼体系结构(von Neumann architecture)是现代计算机体系结构的基础&#xf…

JavaSE学习进阶day03_03 Object类

第三章 Object类 介绍这个类之前,先回顾一下API: 3.1 概述 java.lang.Object类是Java语言中的根类,即所有类的父类。它中描述的所有方法子类都可以使用。在对象实例化的时候,最终找的父类就是Object。 如果一个类没有特别指定父…

手势控制的机器人手臂

将向你展示如何构建机械手臂并使用手势和计算机视觉来控制它。下面有一个在开发阶段的机械手臂的演示视频。 展示开发中的手臂的演示视频:https://youtu.be/KwiwetZGv0s 如图所示,该过程首先用摄像头捕捉我的手及其标志。通过跟踪特定的界标,…

C++语法(16)---- 多态

https://blog.csdn.net/m0_63488627/article/details/130106690?spm1001.2014.3001.5501https://blog.csdn.net/m0_63488627/article/details/130106690?spm1001.2014.3001.5501 目录 1. 多态的概念 2.多态的实现 1.虚函数 2.多态条件 得到的多态条件 特殊条件 3.虚函…

数据结构入门-10-AVL

文章目录 一、AVL的性质1.2 平衡二叉树定义 二、添加需达到平衡2.1 平衡因子2.1.2 平衡因子的实现 2.2 判断该二叉树是否为平衡二叉树2.3 左旋右旋2.3.1 左旋LL右旋RR基本原理2.3.2 LR RLLRRL 三、AVL中删除 一、AVL的性质 平衡二叉树 AVL树得名于它的俄罗斯发明者G. M. Adels…

被裁员了,要求公司足额补缴全部公积金,一次补了二十多万!网友兴奋了,该怎么操作?...

被裁员后,能要求公司补缴公积金吗? 一位网友问: 被裁员了,要求公司把历史公积金全部足额缴纳,现在月薪2.3万,但公司每个月只给自己缴纳300元公积金,结果一次补了二十多万,一次性取出…

Node 【Buffer 与 Stream】

文章目录 🌟前言🌟Buffer🌟 Buffer结构🌟 什么时候用Buffer🌟 Buffer的转换🌟 Buffer使用🌟 创建Buffer🌟 字符串转Buffer🌟 Buffer转字符串🌟 拼接Buffer&am…

Java每日一练(20230417)

目录 1. N 皇后 🌟🌟🌟 2. 搜索二维矩阵 🌟🌟 3. 发奖金问题 🌟 🌟 每日一练刷题专栏 🌟 Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 …

权限控制_SpringSecurity

认证-授权 认证:系统提供的用于识别用户身份的功能,通常提供用户名和密码进行登录其实就是在进行认证,认证的目的是让系统知道你是谁。 授权:用户认证成功后,需要为用户授权,其实就是指定当前用户可以操作…
最新文章