3.0 Commonjs,CMD,AMD
Commonjs规范
CommonJS定义的模块分为:{模块引用(require)} {模块定义(exports)} {模块标识(module)};
require()用来引入外部模块;exports对象用于导出当前模块的方法或变量,唯一的导出口;module对象就代表模块本身。 commonjs是服务器模块的规范,nodejs实现了规范
单独的文件就是一个模块,每个模块拥有自己的作用域;
通过module.exports来将对象和方法抛出去;
通过require来引入,就可以访问挂载的对象,并且直接调用变量;
每个模块
- 每个都有个module对象,这个对象不是全局变量,而是本地变量
- exports = module.exports,可以放弃使用exports,只使用module.exports
CommonJS就是个同步加载方案,因为服务器一般文件都在本地硬盘,
require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。
第一次加载某个模块时,Node会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的module.exports属性。
CommonJS模块的特点如下:
所有代码都运行在模块作用域,不会污染全局作用域。
模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,
以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
模块加载的顺序,按照其在代码中出现的顺序。
require的内部处理流程
require命令是CommonJS规范之中,用来加载其他模块的命令。
它其实不是一个全局命令,而是指向当前模块的module.require命令,而后者又调用Node的内部命令Module._load。
Module._load = function(request, parent, isMain) {
// 1. 检查 Module._cache,是否缓存之中有指定模块
// 2. 如果缓存之中没有,就创建一个新的Module实例
// 3. 将它保存到缓存
// 4. 使用 module.load() 加载指定的模块文件,
// 读取文件内容之后,使用 module.compile() 执行文件代码
// 5. 如果加载/解析过程报错,就从缓存删除该模块
// 6. 返回该模块的 module.exports
};
上面的第4步,采用module.compile()执行指定模块的脚本,逻辑如下。
Module.prototype._compile = function(content, filename) {
// 1. 生成一个require函数,指向module.require
// 2. 加载其他辅助方法到require
// 3. 将文件内容放到一个函数之中,该函数可调用 require
// 4. 执行该函数
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
上面的第1步和第2步,require函数及其辅助方法主要如下
require函数及其辅助方法主要如下。
require(): 加载外部模块
require.resolve():将模块名解析到一个绝对路径
require.main:指向主模块
require.cache:指向所有缓存的模块
require.extensions:根据文件的后缀名,调用不同的执行函数
2
3
4
5
++++++++++++++++++++++++++++++++++++++++++++++++++++
AMD
意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行
代表:require.js
推崇依赖前置,提前执行
require.js的诞生,就是为了解决这两个问题:
(1)实现js文件的异步加载,避免网页失去响应;
(2)管理模块之间的依赖性,便于代码的编写和维护。
+++++++++++++++++++++++++++++++++++++++++++++++++++
CMD
推崇依赖就近原则,延迟执行
代表: sea.js
sea.js 与 RequireJS 异同:
模块加载器,倡导模块化开发理念
+++++++++++++++++++++++++++++++++++++++++++
ES6
CommonJS 与 ES6 有两个重大差异
- 1.CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- 2.CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。
而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
ES6 模块的运行机制与 CommonJS 不一样。
JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。
等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。
因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块
import 和 require
运行时加载: CommonJS 模块就是对象;
- 即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。
编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import时采用静态命令的形式。
即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。import 命令会被 JavaScript 引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。
也正因为这个,使得静态分析成为可能
CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。
而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
想起来 JavaScript 的词法作用域和动态作用域
词法作用域 又称 静态作用域
作用域是指程序源代码中定义变量的区域。
作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。
与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
猜猜两段代码各自的执行结果是多少?
两段代码都会打印:local scope。
原因也很简单,因为JavaScript采用的是词法作用域,函数的作用域基于函数创建的位置。
两个函数 执行上下文栈 不一样:
当执行一个函数的时候,就会创建一个执行上下文,并且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。
而引用《JavaScript权威指南》的回答就是:
JavaScript 函数的执行用到了作用域链,这个作用域链是在函数定义的时候创建的。
嵌套的函数 f() 定义在这个作用域链里,其中的变量 scope 一定是局部变量,
不管何时何地执行函数 f(),这种绑定在执行 f() 时依然有效。