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

第 26 题: 介绍模块化发展历程 #28

Open
cleverboy32 opened this issue Mar 5, 2019 · 15 comments
Open

第 26 题: 介绍模块化发展历程 #28

cleverboy32 opened this issue Mar 5, 2019 · 15 comments
Labels

Comments

@cleverboy32
Copy link

答案

@cleverboy32 cleverboy32 changed the title 前端中的模块化开发 第26 题: 前端中的模块化开发 Mar 5, 2019
@wenJonSnow
Copy link

很cool

@lvtraveler
Copy link

参考:模块化

@mengsixing
Copy link

模块化主要是用来抽离公共代码,隔离作用域,避免变量冲突等。

IIFE: 使用自执行函数来编写模块化,特点:在一个单独的函数作用域中执行代码,避免变量冲突

(function(){
  return {
	data:[]
  }
})()

AMD: 使用requireJS 来编写模块化,特点:依赖必须提前声明好

define('./index.js',function(code){
	// code 就是index.js 返回的内容
})

CMD: 使用seaJS 来编写模块化,特点:支持动态引入依赖文件

define(function(require, exports, module) {  
  var indexCode = require('./index.js');
});

CommonJS: nodejs 中自带的模块化。

var fs = require('fs');

UMD:兼容AMD,CommonJS 模块化语法。

webpack(require.ensure):webpack 2.x 版本中的代码分割。

ES Modules: ES6 引入的模块化,支持import 来引入另一个 js 。

import a from 'a';

@LouisaNikita
Copy link

es6,amd,smd,commonjs 思维导图

@ghost
Copy link

ghost commented Mar 14, 2019

(function(){
  return {
	data:[]
  }
})()
define('./index.js',function(code){
	// code 就是index.js 返回的内容
})
define(function(require, exports, module) {  
  var indexCode = require('./index.js');
});

var fs = require('fs'):

import a from 'a';

@yygmind yygmind changed the title 第26 题: 前端中的模块化开发 第 26 题: 前端中的模块化开发 Apr 26, 2019
@cathy1024
Copy link

es6,amd,smd,commonjs 思维导图

太棒了!

@wind4gis
Copy link

wind4gis commented Nov 1, 2019

模块化

模块化的作用

模块化是为了处理全局污染和依赖管理混乱的问题

模块化

因为一开始js本身没有提供模块化的机制,所以才会衍生出commonJS、AMD、CMD和UMD这么多模块化规范。js在ES6时原生提供了import和export模块化机制

commonJS

定义

文件即模块,每个文件通过module来表示,用require来引用其他依赖,用module.exports来导出自身

机制

通过require去引用文件时,会将文件执行一遍后,将其执行结果通过浅克隆的方式,写入全局内存。后续再require该路径,就直接从内存里取出,不需要重新执行对应的文件

特点

commonJS是服务器编程范式,因为服务器上所有文件都在硬盘里,通过同步加载的方式即可,所以该规范是同步加载规范。同时它是在运行时加载,也就是你可以在require里拼接变量,在加载时会自动识别出最终的实际路径

AMD

定义

define(module, [dep1, dep2], callback)

机制

通过require加载时,它会先加载对应的依赖,等依赖资源加载完之后,会执行回调函数,将依赖作为入参,执行对应的业务逻辑

特点

AMD机制是浏览器编程范式,它是在客户端使用的,由于资源都是在服务器上,所以它是异步加载。同时,它最大的特点是强调依赖前置。

CMD

定义

机制和AMD类似,最大的区别就是CMD强调延迟加载,对应的依赖等到回调函数里执行具体依赖语句,才会去加载,但是AMD在后续版本里也支持了延迟加载的写法

机制

同上

特点

同上

UMD

定义

CommonJS、AMD、CMD并行的状态下,就需要一种方案能够兼容他们,这样我们在开发时,
就不需要再去考虑依赖模块所遵循的规范了,而UMD的出现就是为了解决这个问题。

ES6

定义

通过import引入依赖,通过export导出依赖

机制

ES6的模块机制在依赖模块时并不会先去预加载整个脚本,而是生成一个只读引用,并且静态解析依赖,等到执行代码时,再去依赖里取出实际需要的模块

特点

编译时加载,不允许在里边引用变量,必须为真实的文件路径。可以通过调用import()语句,会生成一个promise去加载对应的文件,这样子就是运行时加载,可以在路径里边编写变量

@Reaper622
Copy link

AMD,CMD用的不多,主要讲一下CommonJS和ESModule

模块的特性

  • 为创建一个内部作用域而调用了一个包装函数。
  • 包装函数的返回值至少包含一个对内部函数的引用,这样才会创建涵盖整个包装函数内部作用域的闭包。

CommonJS

特点: requiremodule.exportsexports
CommonJS 一般用在服务端或者Node用来同步加载模块,它对于模块的依赖发生在代码运行阶段,不适合在浏览器端做异步加载。
exports实际上是一个对module.exports的引用:

    exports.add = function add () {/* 方法 */}
    // 等同于
    module.exports.add = function add () {/* 方法 */}

但注意,不能给exports赋值,否则会断开与module.exports的连接。

ES6 Module

特点: importexport
ES6模块化不是对象,import会在JavaScript引擎静态分析,在编译时就引入模块代码,而并非在代码运行时加载,因此也不适合异步加载。
在HTML中如果要引入模块需要使用

    <script type="module" src="./module.js"></script>

ESModule的优势:

  • 死代码检测和排除。我们可以用静态分析工具检测出哪些模块没有被调用过。比如,在引入工具类库时,工程中往往只用到了其中一部分组件或接口,但有可能会将其代码完整地加载进来。未被调用到的模块代码永远不会被执行,也就成为了死代码。通过静态分析可以在打包时去掉这些未曾使用过的模块,以减小打包资源体积。
  • 模块变量类型检查。JavaScript属于动态类型语言,不会在代码执行前检查类型错误(比如对一个字符串类型的值进行函数调用)。ES6 Module的静态模块结构有助于确保模块之间传递的值或接口类型是正确的。
  • 编译器优化。在CommonJS等动态模块系统中,无论采用哪种方式,本质上导入的都是一个对象,而ES6 Module支持直接导入变量,减少了引用层级,程序效率更高。

二者的差异

CommonJS模块引用后是一个值的拷贝,而ESModule引用后是一个值的动态映射,并且这个映射是只读的。

  • CommonJS 模块输出的是值的拷贝,一旦输出之后,无论模块内部怎么变化,都无法影响之前的引用。
  • ESModule 是引擎会在遇到import后生成一个引用链接,在脚本真正执行时才会根据这个引用链接去模块里面取值,模块内部的原始值变了import加载的模块也会变。

CommonJS运行时加载,ESModule编译阶段引用。

  • CommonJS在引入时是加载整个模块,生成一个对象,然后再从这个生成的对象上读取方法和属性。
  • ESModule 不是对象,而是通过export暴露出要输出的代码块,在import时使用静态命令的方法引用指定的输出代码块,并在import语句处执行这个要输出的代码,而不是直接加载整个模块。

@yygmind yygmind changed the title 第 26 题: 前端中的模块化开发 第 26 题: 介绍模块化发展历程 Dec 16, 2019
@yygmind
Copy link
Contributor

yygmind commented Dec 16, 2019

可从IIFE、AMD、CMD、CommonJS、UMD、webpack(require.ensure)、ES Module、<script type="module"> 这几个角度考虑。

@iceycc
Copy link

iceycc commented Jan 2, 2020

很酷

@lllwwwaaa727
Copy link

@soraly
Copy link

soraly commented Jun 16, 2020

第一阶段——无模块化

将所有JS文件都放在一块,代码执行顺序就按照文件的顺序执行。
缺点是污染全局作用域。每一个模块都是暴露在全局中的,容易产生命名冲突。
还有要手动处理各代码的依赖关系。
image

第二阶段——commonJS规范

是一个JavaScript模块化的规范,一个文件就是一个模块,内部定义的变量就属于这个模块里的,不会对外暴露,所以不会污染全局变量。

  1. 通过require引入模块
  2. 通过module.exports导出模块
//a.js
var num = 100;
var add = function(val){
   return val + num
}
module.exports.num = num;
module.exports.add = add ;
//b.js
var moduleA = require('./a.js')
var fn = moduleA.add;
  1. 同步加载模块,等当前模块加载完成了才进行下一步,服务器端文件都是保存在硬盘上,所以同步加载没问题。但是浏览器上,需要把文件从服务器端请求过来,比较慢,所以同步加载不适合用在浏览器上

第三阶段——AMD规范

因为commonJS规范不适用于浏览器,因为要从服务器加载文件,不能用同步模式,所以有了AMD规范,该规范的实现,就是requireJs了。

define(function () {
    var alertName = function (str) {
      alert("I am " + str);
    }
    var alertAge = function (num) {
      alert("I am " + num + " years old");
    }
    return {
      alertName: alertName,
      alertAge: alertAge
    };
  });
//引入模块:
require(['alert'], function (alert) {
  alert.alertName('JohnZhu');
  alert.alertAge(21);
});

依赖前置,require([dep1, dep2],callback),先加载依赖再执行回调函数
优点是可以在浏览器环境中异步加载模块,而且可以并行加载多个模块

第四阶段——CMD规范

和requirejs非常类似,即一个js文件就是一个模块,但是可以通过按需加载的方式,而不是必须在模块开始就加载所有的依赖。

define(function(require, exports, module) {
  var $ = require('jquery');
  var Spinning = require('./spinning');
  exports.doSomething = ...
  module.exports = ...
})

第五阶段——ES6的模块化

使用ES6的语法,export和import实现模块化,用的比较多就不介绍了。缺点是浏览器暂不支持,需要babel编译过

特殊规范——UMD

兼容AMD,CommonJS 模块化语法。
总结:
image

@morrain
Copy link

morrain commented Apr 9, 2021

@xiaohan-123
Copy link

xiaohan-123 commented Sep 14, 2021

1、CommonJS
CommonJS 规范概述了同步声明依赖的模块定义。这个规范主要用于在服务器端实现模块化代码组 织,但也可用于定义在浏览器中使用的模块依赖。CommonJS 模块语法不能在浏览器中直接运行。
CommonJS 模块定义需要使用 require()指定依赖,而使用 exports 对象定义自己的公共 API。如下例:
CommonJS
无论一个模块在 require()中被引用多少次,模块永远是单例。模块第一次加载后会被缓存,后续加载会取得缓存的模块。
如下例,moduleA只会被打印一次,这是因为无论请求多少次,ModuleA只会被加载一次。
image
module.exports 对象非常灵活,有多种使用方式。如下例:
image
image
只导出一个实体,也可以到处多个对象。

2、异步模块定义(AMD):由于CommonJS以服务器端为目标环境,能够一次性把所有模块都加载到内存,而异步模块定义(AMD)的模块定义系统则以浏览器为目标执行环境,这需要考虑网络延迟的问题。
AMD的一般策略:让模块声明自己的依赖,而运行在浏览器中的模块系统会按需获取依赖,并在依赖加载完成后立即执行依赖它们的模块。
AMD模块实现的核心是用函数包装模块定义:这样可以防止声明全局变量,并允许加载器库控制何时加载模块。
与CommonJS不同,AMD支持可选的为模块指定字符串标识符。
例子:
image
AMD 也支持 require 和 exports 对象,通过它们可以在 AMD 模块工厂函数内部定义 CommonJS
风格的模块。这样可以像请求模块一样请求它们,但 AMD 加载器会将它们识别为原生 AMD 结构
image

3、通用模块定义UMD:为了统一CommonJS和AMD生态系统,通用模块定义(UMD,Universal Module Definition)规范应运而生。UMD可用于创建这两个系统都可以使用的模块代码。
本质上:UMD定义的模块会在启动时检测要使用哪个模块系统,然后进行适当配置,并把所有逻辑包装在一个立即调用的函数表达式(IIFE)中。
image
此模式有支持严格 CommonJS 和浏览器全局上下文的变体。不应该期望手写这个包装函数,它应该
由构建工具自动生成。开发者只需专注于模块的内由容,而不必关心这些样板代码。

4、使用ES6模块:ES6 最大的一个改进就是引入了模块规范。这个规范全方位简化了之前出现的模块加载器,原生浏
览器支持意味着加载器及其他预处理都不再必要。从很多方面看,ES6 模块系统是集 AMD 和 CommonJS
之大成者。
ECMAScript 6 模块是作为一整块 JavaScript 代码而存在的。带有 type="module"属性的<script> 标签会告诉浏览器相关代码应该作为模块执行,而不是作为传统的脚本执行。模块可以嵌入在网页中, 也可以作为外部文件引入:
image
image
嵌入的模块定义代码不能使用 import 加载到其他模块。只有通过外部文件加载的模块才可以使用import 加载。因此,嵌入模块只适合作为入口模块。

@Yangfan2016
Copy link

模块化

脚本顺序依赖
全局变量冲突 数据污染

cmd
amd

Commonjs

es6

Commmonjs 引入是值的拷贝,多次引入会缓存
es6 引入是值的引用,静态分析

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

No branches or pull requests