JavaScript模块化规范

204 阅读6分钟

应用:服务端模块、客户端模块。

同步加载 synchronous

异步加载 asynchronous

CommonJS

nodejs

同步加载

var math = require('math');
math.add(2, 3);

在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。 这种写法适合服务端,因为在服务器读取模块都是在本地磁盘,加载速度很快。

这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。

AMD

Asynchronous Module Definition

异步加载

requireJS

推崇依赖前置、提前执行

先定义所有依赖,然后在加载完成后的回调函数中执行:

require([module], callback);
require(['clock'],function(clock){
  clock.start();
});

CMD

Common Module Definition

异步加载

seaJS

依赖就近,用的时候再require。

推崇依赖就近、延迟执行

define(function(require, exports, module) {
   var clock = require('clock');
   clock.start();
});

ES6

浏览器和服务器通用

ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

各模块化规范区别

AMD和CMD最大的区别是对依赖模块的执行时机处理不同,而不是加载的时机或者方式不同,二者皆为异步加载模块。 AMD依赖前置,js可以方便知道依赖模块是谁,立即加载;而CMD就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了那些模块,这也是很多人诟病CMD的一点,牺牲性能来带来开发的便利性,实际上解析模块用的时间短到可以忽略。

AMD和CMD最大的区别是对依赖模块的执行时机处理不同,而不是加载的时机或者方式不同,二者皆为异步加载模块。 AMD依赖前置,js可以方便知道依赖模块是谁,立即加载;而CMD就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了那些模块,这也是很多人诟病CMD的一点,牺牲性能来带来开发的便利性,实际上解析模块用的时间短到可以忽略。

本次项目中,由 seajs 转向 requirejs。很大一个原因是因为 seajs社区的凋零,长时间没有人更新的文档给人的不自信感所带来的恐慌。虽然seajs 和 Commonjs 更像,但是 事实证明 AMD 更容易理解 与 使用。

  1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。 ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

  1. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。

编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。

CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

import、require

import { formatDateToDash, formatDateToSlash } from 'jscom/utils/formatter';

require('./ActivityPopOverForm.scss');

require 是赋值过程并且是运行时才执行, import 是解构过程并且是编译时执行。 require可以理解为一个全局方法,就意味着可以在任何地方执行。 而import必须写在文件的顶部。 require的性能相对于import稍低,因为require是在运行时才引入模块并且还赋值给某个变量, 而import只需要依据import中的接口在编译时引入指定模块所以性能稍高 es6 import 尽量静态化,编译就能确定模块的依赖关系,输入及输出。 因为require是运行时加载,所以import命令没有办法代替require的动态加载功能。 所以引入了import()函数。完成动态加载。

require,exports,module.exports属于CommonJS规范,import,export,export default属于ES6规范 require支持动态导入,动态匹配路径,import对这两者都不支持 require是运行时调用,import是编译时调用 require是赋值过程,import是解构过程 对于export和export default 不同的使用方式,import就要采取不同的引用方式,主要区别在于是否存在{},export导出的,import导入需要{},导入和导出一一对应,export default默认导出的,import导入不需要{} exports是module.exports一种简写形式,不能直接给exports赋值 当直接给module.exports赋值时,exports会失效.

区别requireimport
规范CommonJS规范es6标准
调用运行时编译时
实质赋值过程解构过程
引用任何位置文件头部

关于调用

require的引用可以在代码的任何地方。 import语法规范上是放在文件开头。

关于本质

require的结果就是对象、数字、字符串、函数等,再把require的结果赋值给某个变量 目前所有的引擎都还没有实现import,我们在node中使用babel支持ES6,也仅仅是将ES6转码为ES5再执行,import语法会被转码为require

module,export 和 module.export区别

module 对象

console.log(module)
modules = {
    children,
    exports,
    hot,
    id,
    loaded,
    parents,
    paths
}
module.id 模块的识别符,通常是带有绝对路径的模块文件名。
module.filename 模块的文件名,带有绝对路径。
module.loaded 返回一个布尔值,表示模块是否已经完成加载。
module.parent 返回一个对象,表示调用该模块的模块。
module.children 返回一个数组,表示该模块要用到的其他模块。
module.exports 表示模块对外输出的值。

require 方法用于加载模块,即加载的是 module.exports 对象

console.log(exports === module.exports);	//true

exports 变量

exports 是 module 里的属性,也就是 module.export的引用

为了方便,Node为每个模块提供一个 exports 变量,指向 module.exports。这等同在每个模块头部,有一行这样的命令: var exports = module.exports;,所以上面的example.js 可以改成:

// example.js
let baseNum = 1;
let addOne = function (num) {
  return baseNum + num;
}
exports.baseNum = baseNum;
exports.addOne = addOne;
console.log('example', module)

需要注意的是下面两种方式是错误的

exports = addOne; // exports 被重新赋值了,不在指向module.exports
exports.addOne = addOne;
module.exports = 'hello'; // exports 指向 module.exports, module.exports 指向了一个字符串,所以addOne不能被外部访问了