Simonzhangs' blog Simonzhangs' blog
首页
  • 前端文章

    • HTML
    • CSS
    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • JS设计模式总结
  • 《Vue》
  • 《React》
  • 《TypeScript 从零实现 axios》
  • TypeScript
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • apple music
  • extension
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Simonzhangs

前端学习探索者
首页
  • 前端文章

    • HTML
    • CSS
    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • JS设计模式总结
  • 《Vue》
  • 《React》
  • 《TypeScript 从零实现 axios》
  • TypeScript
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • apple music
  • extension
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 技术文档

  • GitHub技巧

  • Nodejs

    • 《Node教程》笔记
    • Node基础
    • Koa框架基础
    • Nunjucks模板入门
    • nunjucks模板语法
    • 基于koa与nunjucks实现cookie与session
    • nodejs递归读取所有文件
    • moduleexports和exports区别
      • 一、模块化
      • 二、CommonJS
        • commonjs 使用与原理
        • exports 导出
        • module.exports
        • require 模块的加载顺序
      • 三、ES Module
        • 导出 export 和导入 import
        • 1. export 正常导出,import 导入
        • 2. 默认导出 export default
        • 3. 混合导入|导出
        • ES Module 特性
        • 1. 静态语法
        • 2. 执行特性
        • 3. 导出绑定
        • 4. import()动态导入
  • 博客搭建

  • Ajax

  • 计算机网络

  • 计算机编译原理

  • 涨知识

  • 技术
  • Nodejs
simonzhangs
2022-05-17
目录

moduleexports和exports区别

Node 应用是有模块组成的,一个文件就是一个模块,有单独的作用域,文件中声明的变量、方法和类都是私有的,如果需要在外部能访问,则需要使用 module.exports 或则 exports 暴露出去。这里 Node 采用的是 CommonJS 模块规范。

# 一、模块化

提到 CommonJS 模块化,自然就要说起 ES Module 模块化规范。为什么需要模块化规范呢?

早期 JavaScript 开发很容易存在全局污染和依赖管理混乱问题,这些问题在多人开发前端应用的情况下变得更加棘手。

# 二、CommonJS

Commonjs 的提出,弥补 Javascript 对于模块化,没有统一标准的缺陷。nodejs 借鉴了 Commonjs 的 Module ,实现了良好的模块化管理。

目前 commonjs 广泛应用于以下几个场景:

  • Node 是 CommonJS 在服务器端一个具有代表性的实现;
  • Browserify 是 CommonJS 在浏览器中的一种实现;
  • Webpack 打包工具对 CommonJS 的支持和转换;也就是前端应用也可以在编译之前,尽情使用 CommonJS 进行开发。

# commonjs 使用与原理

在使用规范下,有几个显著的特点:

  • 在 commonjs 中每一个 js 文件都是一个单独的模块,我们可以称之为 module;
  • 该模块中,包含 CommonJS 规范的核心变量: exports、module.exports、require;
  • exports 和 module.exports 可以负责对模块中的内容进行导出;
  • require 函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容;

# exports 导出

exports 本质是一个对象,我们可以在这个对象中添加很多个属性,添加的属性会导出。

bar.js 文件导出:

const name = "simonzhangs";
const age = 18;

function sayHello(name) {
  console.log("hello" + name);
}

exports.name = name;
exports.age = age;
exports.sayHello = sayHello;
1
2
3
4
5
6
7
8
9
10

main.js 文件导入:

const bar = require("./bar");

console.log(bar.name); // simonzhangs
console.log(bar.age); // 18
1
2
3
4

在这里,main.js 中的 bar 变量等于 exports 对象;所以我们通过 bar.xxx 来使用导出文件内的变量,比如 name,age;

require 其实是一个函数,返回值是一个对象,值为导出文件的 exports 对象。

从内存角度分析,在 node 中有一个特殊的全局变量,exports 是其中一个。如果在文件内,不使用 exports.xxx 的形式导出某个变量的话,其实 exports 就是一个空对象。

  • 当我们在 main.js 中 require 导入的时候,它会去自动查找特殊的全局对象 exports,并且把 require 函数的执行结果赋值给 bar;
  • bar 和 exports 指向同一个引用(引用地址相同);
  • 如果发现 exports 上有变量,则会放到 bar 对象上,正因为这样我们才能从 bar 上读取想用的变量;

提示

CommonJS 规范的本质就是对象的引用赋值(浅拷贝本质)。

# module.exports

CommonJS 中是没有 module.exports 的概念的;

  • 但是为了实现模块的导出,Node 中使用的是 Module 的类,每一个模块都是 Module 的一个实例 module;
  • 所以在 Node 中真正用于导出的其实根本不是 exports,而是 module.exports;
  • exports 只是 module 上的一个对象。

但是为什么 exports 也可以导出呢?

因为 module 对象的 exports 属性是 exports 对象的一个引用。真正导出的模块内容的核心其实是 module.exports,只是为了实现 CommonJS 的规范,刚好 module.exports 对 exports 对象使用的是同一个引用而已。

如果使用 module.exports 后,对 exports 有什么影响呢?

如果使用 module.exports = { xxx }这样的形式,会在堆内存中开辟出一块内存空间,会生成一个新的对象,用它取代之前的 exports 对象的导出。

# require 模块的加载顺序

  1. 模块在被第一次引入时,模块中的 js 代码会被运行一次;
  2. 模块被多次引入时,会缓存,最终只加载(运行)一次;

为什么只会加载运行一次呢?

因为每个模块对象 module 都有一个属性,loaded:为 false 表示还没有加载,为 true 表示已经加载。

  1. 如果有循环引入,那么加载顺序是按照图结构中的深度优先算法。

# 三、ES Module

Nodejs 借鉴了 Commonjs 实现了模块化 ,从 ES6 开始, JavaScript 才真正意义上有自己的模块化规范, Es Module 的产生有很多优势,比如:

借助 Es Module 的静态导入导出的优势,实现了 tree shaking。 Es Module 还可以 import() 懒加载方式实现代码分割。

在 Es Module 中用 export 用来导出模块,import 用来导入模块。但是 export 配合 import 会有很多种组合情况

# 导出 export 和导入 import

# 1. export 正常导出,import 导入

导出模块:a.js

const name = " ";
const author = " ";
export { name, author };
export const say = function () {
  console.log("hello , world");
};
1
2
3
4
5
6

导入模块:main.js

// name , author , say 对应 a.js 中的  name , author , say
import { name, author, say } from "./a.js";
1
2
  • export { }, 与变量名绑定,命名导出。
  • import { } from 'module', 导入 module 的命名导出 ,module 为如上的 ./a.js
  • 这种情况下 import { } 内部的变量名称,要与 export { } 完全匹配。

# 2. 默认导出 export default

导出模块:a.js

const name = " ";
const author = " ";
const say = function () {
  console.log("hello , world");
};
export default {
  name,
  author,
  say,
};
1
2
3
4
5
6
7
8
9
10

导入模块:main.js

import mes from "./a.js";
console.log(mes); //{ name: ' ',author:' ', say:Function }
1
2
  • export default anything 导入 module 的默认导出。 anything 可以是函数,属性方法,或者对象。
  • 对于引入默认导出的模块,import anyName from 'module', anyName 可以是自定义名称。

# 3. 混合导入|导出

ES6 module 可以使用 export default 和 export 导入多个属性。 导出模块:a.js

export const name = " ";
export const author = " ";

export default function say() {
  console.log("hello , world");
}
1
2
3
4
5
6

导入模块:main.js 中有几种导入方式: 第一种:

import theSay, { name, author as bookAuthor } from "./a.js";
console.log(
  theSay, // ƒ say() {console.log('hello , world') }
  name, // " "
  bookAuthor // " "
);
1
2
3
4
5
6

第二种:

import theSay, * as mes from "./a";
console.log(
  theSay, // ƒ say() { console.log('hello , world') }
  mes // { name:' ' , author: " " ,default:  ƒ say() { console.log('hello , world') } }
);
1
2
3
4
5

导出的属性被合并到 mes 属性上, export 被导入到对应的属性上,export default 导出内容被绑定到 default 属性上。 theSay 也可以作为被 export default 导出属性。

# ES Module 特性

# 1. 静态语法

ES6 module 的引入和导出是静态的,import 会自动提升到代码的顶层 ,import , export 不能放在块级作用域或条件语句中。

# 2. 执行特性

ES6 module 和 Common.js 一样,对于相同的 js 文件,会保存静态属性。

但是与 Common.js 不同的是 ,CommonJS 模块同步加载并执行模块文件,ES6 模块提前加载并执行模块文件,ES6 模块在预处理阶段分析模块依赖,在执行阶段执行模块,两个阶段都采用深度优先遍历,执行顺序是子 -> 父。

    <code-block title="main.js" active>
        ```js
        console.log('main.js开始执行')
        import say from './a'
        import say1 from './b'
        console.log('main.js执行完毕')
        ```
    </code-block>
    
    <code-block title="a.js">
        ```js
        import b from './b'
        console.log('a模块加载')
        export default  function say (){
            console.log('hello , world')
        }
        ```
    </code-block>
    
    <code-block title="b.js">
        ```js
        console.log('b模块加载')
        export default function sayhello(){
            console.log('hello,world')
        }
        ```
    </code-block>
    
    // Make sure to add code blocks to your code group

    main.js 和 a.js 都引用了 b.js 模块,但是 b 模块也只加载了一次。 执行顺序是子 -> 父:

    b模块加载
    a模块加载
    main.js开始执行
    main.js执行完毕
    
    1
    2
    3
    4

    # 3. 导出绑定

    不能修改 import 导入的属性。

    • 使用 import 被导入的模块运行在严格模式下。
    • 使用 import 被导入的变量是只读的,可以理解默认为 const 装饰,无法被赋值
    • 使用 import 被导入的变量是与原变量绑定/引用的,可以理解为 import 导入的变量无论是否为基本类型都是引用传递。

    # 4. import()动态导入

    import() 返回一个 Promise 对象, 返回的 Promise 的 then 成功回调中,可以获取模块的加载成功信息。

    • import()来动态加载

    import() 动态加载一些内容,可以放在条件语句或者函数执行上下文中。

    if (isRequire) {
      const result = import("./b");
    }
    
    1
    2
    3
    • import()懒加载:

    vue 中的路由懒加载;

    [
      {
        path: "home",
        name: "首页",
        component: () => import("./home"),
      },
    ];
    
    1
    2
    3
    4
    5
    6
    7

    参考文章:

    「万字进阶」深入浅出 Commonjs 和 Es Module (opens new window)

    「Node.js 系列」深入浅出 Node 模块化开发——CommonJS 规范 (opens new window)

    编辑 (opens new window)
    上次更新: 2022/05/17, 21:50:47
    nodejs递归读取所有文件
    解决百度无法收录搭建在GitHub上的个人博客的问题

    ← nodejs递归读取所有文件 解决百度无法收录搭建在GitHub上的个人博客的问题→

    最近更新
    01
    一些有意思的类比
    06-16
    02
    the-super-tiny-compiler解析
    06-06
    03
    计算机编译原理总概
    06-06
    更多文章>
    Theme by Vdoing | Copyright © 2021-2022
    蜀ICP备2021023197号-2
    Simonzhans | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式
    • 飙升榜
    • 新歌榜
    • 云音乐民谣榜
    • 美国Billboard榜
    • UK排行榜周榜