对CommonJs和AMD,CMD的理解,AMD和CMD的区别,CommonJS与ES6的module的区别

对CommonJs和AMD,CMD的理解

是为了使js代码模块化的规范,以前的时候如果一个js模块调用另一个模块,需要在html中进行link,而且必须有严格的引入顺序,但是这样又有可能造成阻塞,使页面失去响应。
CommonJS规定一个文件是一个模块,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。内置的require(路径以/开头是绝对路径,以./开头是相对本文件所在位置的路径,不以“./“或”/“开头,则表示加载的是一个默认提供的核心模块(位于Node的系统安装目录中),或者一个位于各级node_modules目录的已安装模块(全局安装或局部安装))命令用于加载模块文件。当使用require调用该模块时,就获得了exports对象。
每个模块内部,都有一个module对象,代表当前模块。它有以下属性。
● module.id 模块的识别符,通常是带有绝对路径的模块文件名。
● module.filename 模块的文件名,带有绝对路径。
● module.loaded 返回一个布尔值,表示模块是否已经完成加载。
● module.parent 返回一个对象,表示调用该模块的模块。
● module.children 返回一个数组,表示该模块要用到的其他模块。
● module.exports 表示模块对外输出的值。
CommonJs是同步加载 JS 脚本,Node.js 使用了这一规范。这一规范和我们之前的做法比较类似,是同步加载 JS 脚本。这么做在服务端毫无问题,因为文件都存在磁盘上,然而浏览器的特性决定了 JS 脚本需要异步加载,否则就会失去响应,因此 CommonJS 规范无法直接在浏览器中使用。
CommonJS模块的特点如下。
● 所有代码都运行在模块作用域,不会污染全局作用域。
● 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
● 模块加载的顺序,按照其在代码中出现的顺序。
在Node中使用exports=module.exports,可以对exports进行返回值的设定,但是不能对exports和module.exports进行赋值,而且也不能使用exports输出,只能使用module.exports输出module.exports = function (x){ console.log(x);};。当使用require调用自身模块时就会执行自身的module.exports。CommonJS模块的加载机制是,输入的是被输出的值的拷贝。

AMD规范:

前置加载,所有前置模块异步加载结束后,才进行调用callback。require.js实现了这个规范

define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
    a.doSomething()
    // 此处略去 100 行
    b.doSomething()
    // ...
})

缓存:所有缓存的模块保存在require.cache之中,如果想删除模块的缓存,可以像下面这样写。

// 删除指定模块的缓存
delete require.cache[moduleName];
// 删除所有模块的缓存
Object.keys(require.cache).forEach(function(key) {
  delete require.cache[key];
})

注意,缓存是根据绝对路径识别模块的,如果同样的模块名,但是保存在不同的路径,require命令还是会重新加载该模块。

require.main:

require方法有一个main属性,可以用来判断模块是直接执行,还是被调用执行。直接执行的时候(node module.js),require.main属性指向模块本身。require.main === module// true。调用执行的时候(通过require加载该脚本执行),上面的表达式返回false。

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. 执行该函数
};

上面的第1步和第2步,require函数及其辅助方法主要如下。
● require(): 加载外部模块
● require.resolve():将模块名解析到一个绝对路径
● require.main:指向主模块
● require.cache:指向所有缓存的模块
● require.extensions:根据文件的后缀名,调用不同的执行函数
一旦require函数准备完毕,整个所要加载的脚本内容,就被放到一个新的函数之中,这样可以避免污染全局环境。该函数的参数包括require、module、exports,以及其他一些参数。

(function (exports, require, module, __filename, __dirname) {
  // YOUR CODE INJECTED HERE!
});

Module._compile方法是同步执行的,所以Module._load要等它执行完成,才会向用户返回module.exports的值。

CMD规范:

就近加载,在需要用到依赖的时候才申明,可同步可异步,Sea.js实现了这个规范,Sea.js 遇到依赖后只会去下载 JS 文件,并不会执行,而是等到所有被依赖的 JS 脚本都下载完以后,才从头开始执行主逻辑。因此被依赖模块的执行顺序和书写顺序完全一致。

define(function(require, exports, module) {
    var a = require('./a')
    a.doSomething()
    // 此处略去 100 行
    var b = require('./b') // 依赖可以就近书写
    b.doSomething()
    // ...
})

CommonJS与ES6的module的区别

1)ES6的模块加载的机制和CommonJS模块完全不同,CommonJS模块输出的是一个值拷贝,ES6的模块输出的是值的引用,CommonJS模块输入的是被输出的值的拷贝。
2)ES6的模块运行的机制也和CommonJS模块不同,ES6遇到模块加载命令import时不会去执行模块,只会生成一个动态只读引用,等到真的需要用到时,再去模块中取,只是一个动态引用,不会缓存结果值,模块里面的变量绑定其所在的模块。
3)ES6模块和CommonJS模块的循环加载机制也不同,CommonJS模块中一个模块就是一个脚本文件,require命令第一次加载就会运行整个脚本(加载时执行),并在内存中生成一个对象,以后需要这个模块时,就会到exports属性上取值,即使再次执行require命令,也不会再次执行该模块,而是到缓存中取值。对于循环加载,就输出已经执行的部分,未执行的部分并不会输出。ES6中模块是动态引用,需要开发者自己保证真正的取值时能够取到值。

你可能感兴趣的:(javascript)