# 模块化

# 没有模块化带来的问题

  1. 命名冲突问题。

  2. 不利于开发大型项目。

  3. 维护成本高。

# 早期的模块化思想

早期是没有模块作用域的,只能利用函数作用域,来使得每一个 js 文件都看作为一个模块的思想。

必须记住每一个模块中返回对象的命名,才能够再其他模块中使用正确。

并且写起来混乱,都得包裹在 IIFE 中。

在没有合适的规范下,每个人、公司的 的 模块名字可能出现相同的情况。

# 我们真正需要的模块化样子

虽然 IIFE 简单的实现了模块化,但却是没有规则可言的。

我们真正需要的是指定一定的规范来约束每个人都按照这个规范去编写模块化的代码。

模块本身可以导出暴漏出去的东西,也可以导入自己需要的属性方法。

js 社区为了解决上述问题,涌现出一系列好用的规范。

# AMD

AMD 是 异步模块定义. 具体的实现的库是 require.js 和 curl.js 。

运用于浏览器的模块化规范,采用异步加载。

主要还是运用的是函数作用域来解决的模块化,使用回调来异步模块。

主要采用异步的方式加载模块, 模块的加载不影响后面代码的执行.所有依赖这个模块的语句都写在一个回调函数中,模块加载完毕,再执行回调函数.

# CMD

CMD (通用模块定义) ,CMD 相对于使用的更少了,也是用来实现浏览器的异步模块。而且 CMD 吸收了 CommonJS 的优点。

seajs 规范

# ES Module

虽然社区涌现出不少的模块规范,但是 JavaScript 一直没有模块化是一大痛点。

ES Module 使用 import 和 export 关键字。它采用编译期的静态分析,并且也加入了动态引用的方式。

使用 ES Module 将会自动开启严格模式。

const name = 'heihei';
const age = 21;
const sayHello = function () {
    console.log('hello ', name);
}
 
export {
    name,
    age,
    sayHello
}
export 后面的 {} 并不是对象,只能算是一个导出列表,并且是不能够写入对象形式的。
const name = 'heihei';
const age = 21;
const sayHello = function () {
    console.log('hello ', name);
}
 
 
export {
    name as fName,
    age as fAge,
    sayHello as fSayHello
}
可以采用别名的方式,最终导出的是 fName、fAge、fSayHello

export default function () {
    console.log('默认导出');
}

es module 有一个模块环境记录, 用来保存导出文件的环境变量, 会在模块记录中创建 const 常量,当我们在 foo.js 文件中修改 foo.js 文件的变量值,在模块环境记录中会重新创建一个新的常量。在 import 文件中只能用来读最新的值,不可以修改。

在 es module 中采用编译期的静态分析,并且也加入了动态引用的方式。

在解析阶段是会分析 import 关键字的,从而找出依赖.ES Module 在 被 js引擎解析的时候就要知道依赖关系.

# CommonJs

CommonJS 是一个规范,最初提出来实在浏览器以外的地方使用,并且当时被命名为 ServerJS。我们从名字就可以直观的看出来,一开始诞生的目的就是用在服务器上。

后来为了体现广泛性,修改为 CommonJS ,也叫做 CJS。

每一个 js 文件可以看作是一个模块,在这个模块中包含 CommonJS 规范的核心变量。也是 Node 中的特殊全局对象(模块变量)。exports 、module.exports 、require

exports 和 module.exports 可以负责模块中的内容进行导出。

require 函数可以帮助我们 导入其他的模块 (自定义模块、系统模块、第三方模块)中的内容。

exports 是一个模块全局对象,既然是对象,我们只需要给对象添加属性就可以挂载属性,从而导出变量。

既然是同步加载的,就会引发下列的问题。 Node 是如何做到的,在加载模块的时候,每一个模块上会有一个 loaded 属性,代表是否已经加载过此模块。false 表示没有加载, true 代表已经加载过。

这么做有什么好处呢?

可以避免重新加载同一个模块,可以节约资源。

如果存在循环引用,不会造成卡死。加载过后,即使还存在引用关系,但也不会加载。

Node 采用的是深度优先算法。所以加载顺序为 index -> a -> c -> d -> e -> b,index -> b ,所以在 执行完 e -> b ,开始执行 require('b.js') ,因为 b.js 文件已经加载过,loaded 为 true,所以就不会继续加载。

# exports 和 module.exports

exports 并不是 Node 自身的,而是 commonJS 的规范中要求必须有一个 exports 作为导出,所以 Node 为了迎合 commonJS ,所以才新增了一个 exports 。

既然 module.exports 和 exports 都能够用于导出,那么二者的区别是什么?

在 Node 中在是将 module.exports = exports 的,所以,二者属于同一个对象的引用。指向一块地址 0x100。

我们可以看出来这个时候 exports 导出的话,是在堆内存中存开辟了一块 0x100 的地址,exports 导出的对象指向 0x100 这块地址,index.js 文件在 require 导入的时候,同样引用了 0x100 这块地址。

如果是 module.exports 导出呢?因为 module.exports 是赋值给一个新的对象。

Node 中导出是依赖着 module.exports 为导出的,exports 只是为了迎合 CommonJs ,才让 module.exports = exports 的,如果 module.exports 指向一个新的对象,将断开于 exports 的连接,所以最终导出的还是 module.exports 来控制的。

# CommonJS 的缺点

  1. 同步加载模块,只有等到对应的模块加载完毕,当前模块中的内容才能被运行
  2. 服务器不会有什么问题,服务器加载的js文件都是本地文件,加载速度非常快。
  3. 浏览器加载 js 文件需要先从服务器将文件下载下来, 之后在加载运行, 那么采用同步的意味着后续的 js 代码都无法正常运行,即使是一些简单的 DOM 操作。
  4. 早期的话,会使用 AMD 和 CMD 来解决。

# ES6和commonjs的区别

  1. commonjs模块输出的是值的拷贝, ES6输出的值是值的引用.
  2. commonjs是在运行时加载,是一个对象,ES6是在编译时加载,是一个代码块
  3. commonjs的this指向当前模块,ES6的this指向undefined.

CommonJS - 对于基本数据类型,属于复制。即会被模块缓存。同时,在另一个模块可以对该模块输出的变量重新赋值。 - 对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。 - 当使用require命令加载某个模块时,就会运行整个模块的代码。 - 当使用require命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。 - 循环加载时,属于加载时执行。即脚本代码在require的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。

ES6模块 - ES6模块中的值属于【动态只读引用】。 - 对于只读来说,即不允许修改引入变量的值,import的变量是只读的,不论是基本数据类型还是复杂数据类型。当模块遇到import命令时,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。 - 对于动态来说,原始值发生变化,import加载的值也会发生变化。不论是基本数据类型还是复杂数据类型。 - 循环加载时,ES6模块是动态引用。只要两个模块之间存在某个引用,代码就能够执行。