Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【JavaScript】模块化 #57

Open
Tracked by #6
swiftwind0405 opened this issue May 19, 2020 · 1 comment
Open
Tracked by #6

【JavaScript】模块化 #57

swiftwind0405 opened this issue May 19, 2020 · 1 comment

Comments

@swiftwind0405
Copy link
Owner

swiftwind0405 commented May 19, 2020

模块化规范::AMD/CMD/CommonJS/es6模块等等规范,规范了如何来组织你的代码。一般这种方式写的代码浏览器不认,需要用模块化构建工具来打包编译成浏览器可以识别的文件。

CommonJS(同步加载模块)

允许模块通过 require 方法来同步加载所要依赖的其他模块,然后通过 exportsmodule.exports 来导出需要暴露的接口。

只能使用在服务器端,无法使用在浏览器端。

使用案例:

// 导入
require("module");
require("../app.js");
// 导出
exports.getStoreInfo = function() {};
module.exports = someValue;

优点:

  • 简单容易使用
  • 服务器端模块便于复用

缺点:

  • 同步加载方式不适合在浏览器环境中使用,同步意味着阻塞加载,浏览器资源是异步加载的
  • 不能非阻塞的并行加载多个模块

AMD(异步加载模块)

Asynchronous Module Definition:异步模块定义

采用异步方式加载模块,模块的加载不影响后面语句的运行。所有依赖模块的语句,都定义在一个回调函数中,等到加载完成之后,回调函数才执行。

使用案例:

// 定义
define("module", ["dep1", "dep2"], function(d1, d2) {...});
// 加载模块
require(["module", "../app"], function(module, app) {...});

加载模块require([module], callback)

  • [module] : 是一个数组,里面的成员就是要加载的模块
  • callback : 是加载成功之后的回调函。

优点:

  • 适合在浏览器环境中异步加载模块
  • 可以并行加载多个模块

缺点:

  • 提高了开发成本,代码的阅读和书写比较困难,模块定义方式的语义不顺畅
  • 不符合通用的模块化思维方式,是一种妥协的实现

实现 AMD 规范代表 require.js

RequireJS 对模块的态度是预执行。由于 RequireJS 是执行的 AMD 规范, 因此所有的依赖模块都是先执行;即 RequireJS 是预先把依赖的模块执行,相当于把require提前了

RequireJS 执行流程:

  1. require函数检查依赖的模块,根据配置文件,获取js文件的实际路径
  2. 根据js文件实际路径,在dom中插入script节点,并绑定onload事件来获取该模块加载完成的通知。
  3. 依赖script全部加载完成后,调用回调函数

CMD(异步加载模块)

CMD规范和AMD很相似,简单,并与CommonJS和Node.js的 Modules 规范保持了很大的兼容性;在CMD规范中,一个模块就是一个文件。

使用实例:

define(function(require, exports, module) {
  var a = require('./a');
  a.doSomething();
  // 依赖就近书写,什么时候用到什么时候引入
  var b = require('./b');
  b.doSomething();
});

定义模块使用全局函数define,其接收 factory 参数,factory 可以是一个函数,也可以是一个对象或字符串;
factory 是一个函数:

  • require 是一个方法,接受模块标识作为唯一参数,用来获取其他模块提供的接口:require(id)
  • exports 是一个对象,用来向外提供模块接口
  • module 是一个对象,上面存储了与当前模块相关联的一些属性和方法

优点:

  • 依赖就近,延迟执行
  • 可以很容易在 Node.js 中运行

缺点:

  • 依赖 SPM 打包,模块的加载逻辑偏重

实现规范库 sea.js

SeaJS 对模块的态度是懒执行, SeaJS 只会在真正需要使用(依赖)模块时才执行该模块。
与 requireJS 类似,推崇依赖后置。社区衰微,不建议入坑。

UMD(AMD 和 CommonJS 的糅合)

AMD 以浏览器第一原则发展异步加载模块。CommonJS 模块以服务器第一原则发展,选择同步加载,它的模块无需包装。
UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式;在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。
使用案例:

(function (window, factory) {
    if (typeof exports === 'object') {
    
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
    
        define(factory);
    } else {
    
        window.eventUtil = factory();
    }
})(this, function () {
    //module ...
});

ES6 模块

ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
ES6 模块设计思想:尽量的静态化、使得编译时就能确定模块的依赖关系,以及输入和输出的变量(CommonJS和AMD模块,都只能在运行时确定这些东西)。

使用案例:

// 导入
import "/app";
import React from “react”;
import { Component } from “react”;
// 导出
export function multiply() {...};
export var year = 2018;
export default ...

优点:

  • 容易进行静态分析
  • 面向未来的 EcmaScript 标准

缺点:

  • 原生浏览器端还没有实现该标准
  • 全新的命令字,新版的 Node.js才支持。

import 优先执行

由于 import 是静态执行,所以 import 具有提升效果即 import 命令在模块中的位置并不影响程序的输出。

动态 import()

  • 动态的 import() 提供一个基于 Promise 的 API
  • 动态的 import() 可以在脚本的任何地方使用
  • import() 接受字符串文字,可以根据需要构造说明符

ES6 模块跟 CommonJS 模块的区别

  • ES6 模块输出的是值的引用,CommonJS 模块输出的是值的拷贝
  • ES6 是在预编译阶段去加载模块的,而 CommonJS 是在运行阶段去加载模块的

CommonJS

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

ES6 模块

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

require/exports 是必要通用且必须的;因为事实上,目前编写的 import/export 最终都是编译为 require/exports 来执行的。

参考资料

@swiftwind0405
Copy link
Owner Author

  • CommonJS 和 ES6 module
  • AMD 与 CMD 区别(比较旧可以忽略)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant