CommonJS是服务器模块的规范,Node.js采用了这个规范。
根据 CommonJS 规范,一个单独的文件就是一个模块,每一个模块都是一个单独的作用域,在一个文件定义的变量(还包括函数和类),都是私有的,对其他文件是不可见的。
CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。
CommonJS 中,加载模块使用 require 方法。该方法读取一个文件并执行,最后返回文件内部的 exports 对象。
Node.js 主要用于服务器编程,加载的模块文件一般都已经存在本地硬盘,加载起来较快,不用考虑异步加载的方式,所以 CommonJS 的同步加载模块规范是比较适用的。但如果是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。所以就有了 AMD,CMD 等解决方案。
var x = 5;
var addX = function(value) {
return value + x;
};
module.exports.x = x;
module.exports.addX = addX;
// 也可以改写为如下
module.exports = {
x: x,
addX: addX,
};
let math = require('./math.js');
console.log('math.x',math.x);
console.log('math.addX', math.addX(4));
//规范 API
define(id?, dependencies?, factory);
define.amd = {};
// 定义无依赖的模块
define({
add: function(x,y){
return x + y;
}
});
// 定义有依赖的模块
define(["alpha"], function(alpha){
return {
verb: function(){
return alpha.verb() + 1;
}
}
});
require([module], callback) 中 callback 为模块加载完成后的回调函数
//加载 math模块,完成之后执行回调函数
require(['math'], function(math) {
math.add(2, 3);
});
RequireJS 是一个前端模块化管理的工具库,遵循 AMD 规范,RequireJS 是对 AMD 规范的阐述。
RequireJS 基本思想为,通过一个函数来将所有所需的或者所依赖的模块装载进来,然后返回一个新的函数(模块)。后续所有的关于新模块的业务代码都在这个函数内部操作。
RequireJS 要求每个模块均放在独立的文件之中,并使用 define 定义模块,使用 require 方法调用模块。
按照是否有依赖其他模块情况,可以分为 独立模块 和 非独立模块。
define({
method1: function(){},
method2: function(){}
});
//等价于
define(function() {
return {
method1: function(){},
method2: function(){}
}
});
define([ 'module1', 'module2' ], function(m1, m2) {
...
});
//等价于
define(function(require) {
var m1 = require('module1');
var m2 = require('module2');
...
});
require(['foo', 'bar'], function(foo, bar) {
foo.func();
bar.func();
});
CMD = Common Module Definition,即 通用模块定义。CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。
CMD规范和AMD类似,都主要运行于浏览器端,写法上看起来也很类似。主要是区别在于 模块初始化时机
//AMD
define(['./a','./b'], function (a, b) {
//依赖一开始就写好
a.test();
b.test();
});
//CMD
define(function (requie, exports, module) {
//依赖可以就近书写
var a = require('./a');
a.test();
...
//软依赖
if (status) {
var b = requie('./b');
b.test();
}
});
使用Sea.js,在书写文件时,需要遵守CMD(Common Module Definition)模块定义规范。一个文件就是一个模块。
例如,对于下述util.js代码
var org = {};
org.CoolSite = {};
org.CoolSite.Utils = {};
org.CoolSite.Utils.each = function (arr) {
// 实现代码
};
org.CoolSite.Utils.log = function (str) {
// 实现代码
};
可以采用SeaJS重写为
define(function(require, exports) {
exports.each = function (arr) {
// 实现代码
};
exports.log = function (str) {
// 实现代码
};
});
通过 exports 就可以向外提供接口。通过 require('./util.js') 就可以拿到 util.js 中通过 exports 暴露的接口。这里的 require 可以认为是 Sea.js 给 JavaScript 语言增加的一个语法关键字,通过 require 可以获取其他模块提供的接口。
define(function(require, exports) {
var util = require('./util.js');
exports.init = function() {
// 实现代码
};
});
二者区别主要表现在模块初始化时机
从规范上来说,AMD 更加简单且严谨,适用性更广,而在RequireJS强力的推动下,在国外几乎成了事实上的异步模块标准,各大类库也相继支持AMD规范。
但从SeaJS与CMD来说,也做了很多不错东西:1、相对自然的依赖声明风格 2、小而美的内部实现 3、贴心的外围功能设计 4、更好的中文社区支持。
AMD 模块以浏览器第一的原则发展,异步加载模块。
CommonJS 模块以服务器第一原则发展,选择同步加载。它的模块无需包装(unwrapped modules)。
这迫使人们又想出另一个更通用的模式 UMD(Universal Module Definition),实现跨平台的解决方案。
(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 ...
});
CommonJS 模块输出的是值的拷贝(类比于基本类型和引用类型的赋值操作)。对于基本类型,一旦输出,模块内部的变化影响不到这个值。对于引用类型,效果同引用类型的赋值操作。
// lib.js
var counter = 3;
var obj = {
name: 'David'
};
function changeValue() {
counter++;
obj.name = 'Peter';
};
module.exports = {
counter: counter,
obj: obj,
changeValue: changeValue,
};
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
console.log(mod.obj.name); // 'David'
mod.changeValue();
console.log(mod.counter); // 3
console.log(mod.obj.name); // 'Peter'
// Or
console.log(require('./lib').counter); // 3
console.log(require('./lib').obj.name); // 'Peter'
也可以借助取值函数(getter),将 counter 转为引用类型值,效果如下。
在类的内部,可以使用 get 和 set 关键字,对某个属性设置存执函数和取值函数,拦截该属性的存取行为。 —— class | 阮一峰
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
get counter() {
return counter
},
incCounter: incCounter,
};
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 4
ES6 模块是动态关联模块中的值,输出的是值得引用。原始值变了,import 加载的值也会跟着变。
ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析时,遇到模块加载命令 import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。ES6 模块中,原始值变了,import 加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值。 —— ES6 Module 的加载实现 | 阮一峰
// lib.js
export let counter = 3;
export function incCounter() {
counter++;
}
// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
这是因为,CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
ES6 模块是编译时输出接口,因此有如下2个特点
在文件中的任何位置引入 import 模块都会被提前到文件顶部
// a.js
console.log('a.js')
import { foo } from './b';
// b.js
export let foo = 1;
console.log('b.js 先执行');
// 执行结果:
// b.js 先执行
// a.js
虽然 a 模块中 import 引入晚于 console.log('a'),但是它被 JS 引擎通过静态分析,提到模块执行的最前面,优于模块中的其他部分的执行。
由于 import 和 export 是静态执行,所以 import 和 export 具有变量提升效果。即 import 和 export 命令在模块中的位置并不影响程序的输出。
// a.js
import { foo } from './b';
console.log('a.js');
export const bar = 1;
export const bar2 = () => {
console.log('bar2');
}
export function bar3() {
console.log('bar3');
}
// b.js
export let foo = 1;
import * as a from './a';
console.log(a);
// 执行结果:
// { bar: undefined, bar2: undefined, bar3: [Function: bar3] }
// a.js
a 模块引用了 b 模块,b 模块也引用了 a 模块,export 声明的变量也是优于模块其它内容的执行的。但具体对变量赋值需要等到执行到相应代码的时候。
重复引入某个相同的模块时,模块只会执行一次。
CommonJS 模块的重要特性是加载时执行,即脚本代码在 require 的时候,就会全部执行。一旦出现某个模块被“循环加载”,就只输出已经执行的部分,还未执行的部分不会输出。
//a.js
exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done);
exports.done = true;
console.log('a.js 执行完毕');
上面代码之中,a.js 脚本先输出一个 done 变量,然后加载另一个脚本文件 b.js。注意,此时 a.js 代码就停在这里,等待 b.js 执行完毕,再往下执行。
再看 b.js 的代码。
//b.js
exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('b.js 执行完毕');
上面代码之中,b.js 执行到第二行,就会去加载 a.js,这时,就发生了“循环加载”。系统会 a.js 模块对应对象的 exports 属性取值,可是因为 a.js 还没有执行完,从 exports 属性只能取回已经执行的部分,而不是最后的值。
a.js 已经执行的部分,只有一行。
exports.done = false;
因此,对于 b.js来说,它从 a.js 只输入一个变量 done,值为 false。
然后,b.js 接着往下执行,等到全部执行完毕,再把执行权交还给 a.js。于是,a.js 接着往下执行,直到执行完毕。我们写一个脚本 main.js,验证这个过程。
var a = require('./a.js');
var b = require('./b.js');
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
执行 main.js,运行结果如下。
$ node main.js
在 b.js 之中,a.done = false
b.js 执行完毕
在 a.js 之中,b.done = true
a.js 执行完毕
在 main.js 之中, a.done=true, b.done=true
上面的代码证明了2点
exports.done = true;
总之,CommonJS 输入的是被输出值的拷贝,不是引用。
另外,由于 CommonJS 模块遇到循环加载时,返回的是当前已经执行的部分的值,而不是代码全部执行后的值,两者可能会有差异。所以,输入变量的时候,必须非常小心。
var a = require('a'); // 安全的写法 导入整体,保证module已经执行完成
var foo = require('a').foo; // 危险的写法
exports.good = function (arg) {
return a.foo('good', arg); // 使用的是 a.foo 的最新值
};
exports.bad = function (arg) {
return foo('bad', arg); // 使用的是一个部分加载时的值
};
上面代码中,如果发生循环加载,require('a').foo 的值很可能后面会被改写,改用 require('a') 会更保险一点。
// a.js
console.log('a starting');
exports.done = false;
const b = require('./b');
console.log('in a, b.done =', b.done);
exports.done = true;
console.log('a done');
// b.js
console.log('b starting');
exports.done = false;
const a = require('./a');
console.log('in b, a.done =', a.done);
exports.done = true;
console.log('b done');
// node a.js
// 执行结果:
// a starting
// b starting
// in b, a.done = false
// b done
// in a, b.done = true
// a done
从上面的执行过程中,可以看到,在 CommonJS 规范中,当遇到 require() 语句时,会执行 require 模块中的代码,并缓存执行的结果,当下次再次加载时不会重复执行,而是直接取缓存的结果。正因为此,出现循环依赖时才不会出现无限循环调用的情况。
跟 CommonJS 模块一样,ES6 不会再去执行重复加载的模块,又由于 ES6 动态输出绑定的特性,能保证 ES6 在任何时候都能获取其它模块当前的最新值。
ES6 模块在编译时就会静态分析,优先于模块内的其他内容执行,所以导致了我们无法写出像下面这样的代码
if(some condition) {
import a from './a';
}else {
import b from './b';
}
// or
import a from (str + 'b');
因为编译时静态分析,导致了我们无法在条件语句或者拼接字符串模块,因为这些都是需要在运行时才能确定的结果在 ES6 模块是不被允许的,所以 动态引入import() 应运而生。
import() 允许你在运行时动态地引入 ES6 模块,想到这,你可能也想起了 require.ensure 这个语法,但是它们的用途却截然不同的。
require.ensure 的出现是 webpack 的产物,它是因为浏览器需要一种异步的机制可以用来异步加载模块,从而减少初始的加载文件的体积,所以如果在服务端的话, require.ensure 就无用武之地了,因为服务端不存在异步加载模块的情况,模块同步进行加载就可以满足使用场景了。 CommonJS 模块可以在运行时确认模块加载。
而 import() 则不同,它主要是为了解决 ES6 模块无法在运行时确定模块的引用关系,所以需要引入 import()。
先来看下它的用法
// a.js
const str = './b';
const flag = true;
if(flag) {
import('./b').then(({foo}) => {
console.log(foo);
})
}
import(str).then(({foo}) => {
console.log(foo);
})
// b.js
export const foo = 'foo';
// babel-node a.js
// 执行结果
// foo
// foo
当然,如果在浏览器端的 import() 的用途就会变得更广泛,比如 按需异步加载模块,那么就和 require.ensure 功能类似了。
因为是基于 Promise 的,所以如果你想要同时加载多个模块的话,可以是 Promise.all 进行并行异步加载。
Promise.all([
import('./a.js'),
import('./b.js'),
import('./c.js'),
]).then(([a, {default: b}, {c}]) => {
console.log('a.js is loaded dynamically');
console.log('b.js is loaded dynamically');
console.log('c.js is loaded dynamically');
});
还有 Promise.race 方法,它检查哪个 Promise 被首先 resolved 或 reject。我们可以使用 import() 来检查哪个 CDN速度更快:
const CDNs = [
{
name: 'jquery.com',
url: 'https://code.jquery.com/jquery-3.1.1.min.js'
},
{
name: 'googleapis.com',
url: 'https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js'
}
];
console.log(`------`);
console.log(`jQuery is: ${window.jQuery}`);
Promise.race([
import(CDNs[0].url).then(()=>console.log(CDNs[0].name, 'loaded')),
import(CDNs[1].url).then(()=>console.log(CDNs[1].name, 'loaded'))
]).then(()=> {
console.log(`jQuery version: ${window.jQuery.fn.jquery}`);
});
当然,如果你觉得这样写还不够优雅,也可以结合 async/await 语法糖来使用。
async function main() {
const myModule = await import('./myModule.js');
const {export1, export2} = await import('./myModule.js');
const [module1, module2, module3] =
await Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
]);
}
动态 import() 为我们提供了以异步方式使用 ES 模块的额外功能。
根据我们的需求动态或有条件地加载它们,这使我们能够更快,更好地创建更多优势应用程序。
Webpack允许使用不同的模块类型,但是底层必须使用同一种实现。所有的模块可以直接在盒外运行。
import MyModule from './MyModule.js';
var MyModule = require('./MyModule.js');
define(['./MyModule.js'], function (MyModule) {
});
Javascript模块的写法-AMD, CMD, CommonJS和UMD。JavaScript中出现了一些非传统模块开发方式的规范 CommonJS的模块规范,AMD,CMD等。
在写组件库时用vue-cli 3.x的vue-cli-service build --target lib --name index [entry]可以直接构建npm包,npm publish后再通过npm install [name]安装引用一点问题都没有
有这样一个场景,客户端运行很久,但是法务部和数据部需要收集用户的一些信息,这些信息收集好之后需要进行相应的数据处理,之后上报到服务端。客户端提供一个纯粹的 JS 执行引擎
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!