Open muwoo opened 6 years ago
Node.js是commonJS规范的主要实践者,它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global。实际使用时,用module.exports定义当前模块对外输出的接口,用require加载模块。
commonJS
module、exports、require、global
module.exports
require
// 定义模块 area.js function area(radius) { return Math.PI * radius * radius; } // 在这里写上需要向外暴露的函数、变量 module.exports = { area: area } // 引用自定义的模块时,参数包含路径 var math = require('./math'); math.area(2);
但是我们并没有直接定义 module、exports、require这些模块,以及 Node 的 API 文档中提到的__filename、__dirname。那么是从何而来呢?其实在编译的过程中,Node 对我们定义的 JS 模块进行了一次基础的包装:
module、exports、require
__filename、__dirname
(function(exports, require, modules, __filename, __dirname)) { ... })
这样我们便可以访问这些传入的arguments以及隔离了彼此的作用域。CommonJS 的一个模块,就是一个脚本文件。require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。
arguments
CommonJS
{ id: '...', exports: { ... }, loaded: true, ... }
以后需要用到这个模块的时候,就会到exports属性上面取值。即使再次执行require命令,也不会再次执行该模块,而是到缓存之中取值。commonJS用同步的方式加载模块,只有在代码执行到require的时候,才回去执行加载。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。
exports
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。AMD 规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。说了这么多,来看一下一个AMD规范的RequireJS 是如何定义的:
RequireJS
// 定义 moduleA 依赖 a, b模块 define(['./a','./b'],function(a,b){ a.doSomething() b.doSomething() }) // 使用 require(['./moduleA'], function(moduleA) { // ... })
CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。比如require.js在申明依赖的模块时会在第一之间加载并执行模块内的代码,而CMD则是在使用的时候就近定义:
SeaJS
require.js
define(function(require, exports, module) { var a = require('./a') a.doSomething() var b = require('./b') b.doSomething() })
代码在运行时,首先是不知道依赖的,需要遍历所有的require关键字,找出后面的依赖。具体做法是将function toString后,用正则匹配出require关键字后面的依赖。显然,这是一种牺牲性能来换取更多开发便利的方法。而 AMD 是依赖前置的,换句话说,在解析和执行当前模块之前,模块作者必须指明当前模块所依赖的模块。代码在一旦运行到此处,能立即知晓依赖。而无需遍历整个函数体找到它的依赖,因此性能有所提升,缺点就是开发者必须显式得指明依赖——这会使得开发工作量变大,比如:当你写到函数体内部几百上千行的时候,忽然发现需要增加一个依赖,你不得不回到函数顶端来将这个依赖添加进数组。
function toString
ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
import a from './a' import b from './b' a.doSomething() b.doSomething() function c () {} export default c
ES6 Modules不是对象,import命令会被 JavaScript 引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。也正因为这个,使得静态分析成为可能。
import
JavaScript
ES6 Modules
CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
前端模块化:CommonJS,AMD,CMD,ES6
通俗易懂。
讲的最清楚的文档
学习了。
(function(exports, require, modules, __filename, __dirname)) {
是module
CommonJS 模块输出的也是值的引用,模块内部的变化会影响到这个值的!nodejs里加载过的模块会缓存到Module._cache里面的,楼主可以看看node的require的实现。
CommonJS
Node.js是
commonJS
规范的主要实践者,它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global
。实际使用时,用module.exports
定义当前模块对外输出的接口,用require
加载模块。但是我们并没有直接定义
module、exports、require
这些模块,以及 Node 的 API 文档中提到的__filename、__dirname
。那么是从何而来呢?其实在编译的过程中,Node 对我们定义的 JS 模块进行了一次基础的包装:这样我们便可以访问这些传入的
arguments
以及隔离了彼此的作用域。CommonJS
的一个模块,就是一个脚本文件。require
命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。以后需要用到这个模块的时候,就会到
exports
属性上面取值。即使再次执行require
命令,也不会再次执行该模块,而是到缓存之中取值。commonJS用同步的方式加载模块,只有在代码执行到require
的时候,才回去执行加载。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。AMD
AMD 是
RequireJS
在推广过程中对模块定义的规范化产出。AMD 规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。说了这么多,来看一下一个AMD规范的RequireJS
是如何定义的:CMD
CMD 是
SeaJS
在推广过程中对模块定义的规范化产出。AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。比如require.js
在申明依赖的模块时会在第一之间加载并执行模块内的代码,而CMD则是在使用的时候就近定义:代码在运行时,首先是不知道依赖的,需要遍历所有的
require
关键字,找出后面的依赖。具体做法是将function toString
后,用正则匹配出require
关键字后面的依赖。显然,这是一种牺牲性能来换取更多开发便利的方法。而 AMD 是依赖前置的,换句话说,在解析和执行当前模块之前,模块作者必须指明当前模块所依赖的模块。代码在一旦运行到此处,能立即知晓依赖。而无需遍历整个函数体找到它的依赖,因此性能有所提升,缺点就是开发者必须显式得指明依赖——这会使得开发工作量变大,比如:当你写到函数体内部几百上千行的时候,忽然发现需要增加一个依赖,你不得不回到函数顶端来将这个依赖添加进数组。ES6 Module
ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
ES6 Modules不是对象,
import
命令会被JavaScript
引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。也正因为这个,使得静态分析成为可能。ES6 模块与 CommonJS 模块的差异
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
CommonJS
模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。ES6 Modules
的运行机制与CommonJS
不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的 import 有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
参考文章
前端模块化:CommonJS,AMD,CMD,ES6