乐趣区

关于javascript:Node-笔记2模块化

模块化

什么是模块化开发?

  • 事实上模块化开发最终的目标是将程序 划分成一个个小的构造
  • 这个构造中编写属于 本人的逻辑代码,有本人的作用域,不会影响到其余的构造;
  • 这个构造能够将本人心愿裸露的 变量、函数、对象等导出 给其构造应用;
  • 也能够通过某种形式,导入另外构造中的 变量、函数、对象 等;

下面说提到的构造,就是模块;依照这种构造划分开发程序的过程,就是模块化开发的过程;

CommonJS 和 Node

咱们须要晓得 CommonJS 是一个标准,最后提出来是在浏览器以外的中央应用,并且过后被命名为 ServerJS,起初为了 体现它的广泛性,批改为 CommonJS,平时咱们也会简称为 CJS。

  • Node 中对CommonJS 进行了反对和实现
  • 在 Node 中 每一个 js 文件都是一个独自的模块
  • 这个模块中包含CommonJS 标准的外围变量:exports、module.exports、require;

exports 导出

exports 是一个对象,咱们能够在这个对象中增加很多个属性,增加的属性会导出;

exports.name = 'name';
exports.age = 'age';
exports.sayHello = 'sayHello';

另外一个文件中能够导入:

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

下面这行实现了什么操作呢?

  • 意味着 main 中的 bar 变量等于 exports 对象;
  • 也就是 require 通过各种查找形式,最终找到了 exports 这个对象;
  • 并且将这个 exports 对象赋值给了 bar 变量;
  • bar 变量就是 exports 对象了;

它们实际上是一个浅层拷贝

  • bar 对象是 exports 对象的浅拷贝(援用赋值);
  • 浅拷贝的实质就是一种援用的赋值而已;

module.exports 又是什么?

在 Node 中真正用于导出的其实基本不是 exports,而是 module.exports;CommonJS 中是没有 module.exports 的概念的;然而为了实现模块的导出,Node 中应用的是 Module 的类,每一个模块都是 Module 的一个实例,也就是 module;

为什么 exports 也能够导出呢?

这是因为 module 对象的 exports 属性是 exports 对象的一个援用;

也就是说 module.exports = exports = main 中的 bar

模块的加载过程

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

    这是因为每个模块对象 module 都有一个属性:loaded。为 false 示意还没有加载,为 true 示意曾经加载;

  • 如果有循环引入,那么加载程序是采取深度优先算法

main -> aaa -> ccc -> ddd -> eee ->bbb

CommonJS 标准毛病

CommonJS 加载模块是同步的:

  • 同步的意味着只有等到对应的模块加载结束,以后模块中的内容能力被运行;
  • 这个在服务器不会有什么问题,因为服务器加载的 js 文件都是本地文件,加载速度十分快;

如果将它利用于浏览器呢?

  • 浏览器加载 js 文件须要先从服务器将文件下载下来,之后在加载运行;
  • 那么采纳同步的就意味着后续的 js 代码都无奈失常运行,即便是一些简略的 DOM 操作;所以在浏览器中,咱们通常不应用 CommonJS 标准;

在晚期为了能够在浏览器中应用模块化,通常会采纳 AMD 或 CMD

  • 然而目前一方面古代的浏览器曾经反对 ES Modules,另一方面借助于 webpack 等工具能够实现对 CommonJS 或者 ES Module 代码的转换;

AMD 标准

CMD 标准

ES Module

export 关键字

export 关键字将一个模块中的变量、函数、类等导出;

咱们心愿将其余中内容全副导出,它能够有如下的形式:

  • 形式一:在语句申明的后面间接加上 export 关键字
  • 形式二:将所有须要导出的标识符,放到 export 前面的 {}中
  • 形式三:导出时给标识符起一个别名

import 关键字

导入内容的形式也有多种:

  • import {标识符列表} from ‘ 模块 ’;
  • 形式二:导入时给标识符起别名
  • 形式三:通过 * 将模块性能放到一个模块性能对象(a module object)上

Export 和 import 联合应用

export {sum as barSum} from './bar.js';

为什么要这样做呢?

  • 在开发和封装一个性能库时,通常咱们心愿将裸露的所有接口放到一个文件中;
  • 这样不便指定对立的接口标准,也不便浏览;
  • 这个时候,咱们就能够应用 export 和 import 联合应用;

default 用法

默认导出(default export):

  • 默认导出 export 时能够不须要指定名字;
  • 在导入时不须要应用 {},并且能够本人来指定名字;
  • 它也不便咱们和现有的 CommonJS 等标准互相操作;

留神:在一个模块中,只能有一个默认导出(default export);

import 函数

通过 import 加载一个模块,是不能够在其放到逻辑代码中的;

  • 因为 ES Module 在被 JS 引擎解析时,就必须晓得它的依赖关系; 因为这个时候 js 代码没有任何的运行,所以无奈在进行相似于 if 判断中依据代码的执行状况;甚至上面的这种写法也是谬误的:因为咱们必须到运行时能确定 path 的值

然而某些状况下,咱们确确实实心愿动静的来加载某一个模块:

  • 这个时候咱们须要应用 import() 函数来动静加载;

CommonJS 的加载过程

CommonJS 模块加载 js 文件的过程是运行时加载的,并且是同步的:

  • 运行时加载意味着是 js 引擎在执行 js 代码的过程中加载 模块;
  • 同步的就意味着一个文件没有加载完结之前,前面的代码都不会执行;

CommonJS 通过 module.exports 导出的是一个对象:

  • 导出的是一个对象意味着能够将这个对象的援用在其余模块中赋值给其余变量;
  • 然而最终他们指向的都是同一个对象,那么一个变量批改了对象的属性,所有的中央都会被批改;

ES Module 加载过程

ES Module 加载 js 文件的过程是编译(解析)时加载的,并且是异步的:

编译时(解析)时加载,意味着 import 不能和运行时相干的内容放在一起应用,比方 from 前面的门路须要动静获取,比方不能将 import 放到 if 等语句的代码块中。所以咱们有时候也称 ES Module 是动态解析的,而不是动静或者运行时解析的。

异步的意味着:JS 引擎在遇到 import 时会去获取这个 js 文件,然而这个获取的过程是异步的,并不会阻塞主线程继 续执行;也就是说设置了 type=module 的代码,相当于在 script 标签上也加上了 async 属性。如果咱们前面有一般的 script 标签以及对应的代码,那么 ES Module 对应的 js 文件和代码不会阻塞它们的执行;

ES Module 通过 export 导出的是变量自身的援用:

  • export 在导出一个变量时,js 引擎会解析这个语法,并且创立 模块环境记录(module environment record);
  • 模块环境记录会和变量进行 绑定(binding),并且这个绑定是实时的;
  • 而在导入的中央,咱们是能够实时的获取到绑定的最新值的;
  • 如果在导出的模块中批改了变动,那么导入的中央能够 实时获取最新的变量
  • 在导入的中央不能够批改变量,因为它只是被绑定到了这个变量上(其实是一个常量,或了解成动态只读援用)
  • 如果 bar.js 中导出的是一个对象,那么 main.js 中是否能够批改对象中的属性,他们指向同一块内存空间

Node 对 ES Module 的反对

形式一:在 package.json 中配置 type: module

形式二:文件以 .mjs 结尾,示意应用的是 ES Module;

CommonJS 和 ES Module 交互

通常状况下,CommonJS 不能加载 ES Module

  • 因为 CommonJS 是同步加载的,然而 ES Module 必须通过动态剖析等,无奈在这个时候执行 JavaScript 代码;Node 当中是不反对的;

少数状况下,ES Module 能够加载 CommonJS

  • ES Module 在加载 CommonJS 时,会将其 module.exports 导出的内容作为 default 导出形式来应用;
  • 这个仍然须要看具体的实现,比方 webpack 中是反对的、Node 最新的 Current 版本也是反对的;然而在最新的 LTS 版本中就不反对
退出移动版