本文主要学习四种模块加载规范:
其中AMD,CMD,CommonJs不会详细学习,只是学习下它们的概念,做一些比较。
想要详细学习的同学可以点击下面的链接:
AMD
CMD
阮一峰CommonJS规范学习
首先AMD和CMD是用于浏览器的模块规范:
1、AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块
2、CMD推崇就近依赖,只有在用到某个模块的时候再去require
AMD和CMD最大的区别是对依赖模块的执行时机处理不同,注意不是加载的时机或者方式不同:
AMD 是将需要使用的模块先加载完再执行代码,而 CMD 是在 require 的时候才去加载模块文件,加载完再接着执行。
引用阮一峰老师的《JavaScript 标准参考教程(alpha)》:
CommonJS 规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。
AMD规范则是非同步加载模块,允许指定回调函数。
由于 Node.js 主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以 CommonJS 规范比较适用。
但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用 AMD 规范。
在阮一峰老师的ECMAScript6 教程 中有解释:
它们有三个重大差异。
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
CommonJS 模块的require()
是同步加载模块,ES6 模块的import
命令是异步加载,有一个独立的模块依赖的解析阶段。
第二个差异是因为 CommonJS 加载的是一个对象(即module.exports
属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
下面重点了解的是第一个差异:
CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
example1:
let current = 1;
function addCurrent() {
current++
}
module.exports = {
current: current,
addCurrent: addCurrent
}
//在文件中引入
let num = require('./Democlass.js');
console.log(num.current) //1
num.addCurrent();
console.log(num.current);//1
从上面的例子中我们看到,两次输出的结果是一致的,current的值并未改变,这是为什么呢?
这是因为num.current是一个基本类型的值,会被缓存。
如果按照这种写法我们该怎么获取它改变之后的值呢?
把它写成一个函数,每次返回它的值。
//这里就直接写改变之后的导出对象了
module.exports = {
get current() {
return current
},
addCurrent: addCurrent
}
那么我们这时就会有一个疑问,如果是引用类型的值呢?是不是还是相同的值,不受影响?
let current = {
name:'白茶'
};
function addCurrent() {
current.name='爱喝茶'
}
module.exports = {
current: current,
addCurrent: addCurrent
}
let num = require('./Democlass');
console.log(num.current) //{name: "白茶"}
num.addCurrent();
console.log(num.current);//{name: "爱喝茶"}
上面的结果是会发生改变的,这是因为对于基本数据类型而言是 "值的拷贝",只是对于引用类型而言,值指的其实是引用。
下面我们将这个例子改为ES6的引入方式看下:
let current = 1;
let obj = {
name: '白茶' };
function addCurrent() {
current++;
obj.name = '爱喝茶';
}
export {
current,
obj,
addCurrent
}
import {
current, obj, addCurrent } from './data.js';
console.log(current);//1
console.log(obj);//{name: "白茶"}
addCurrent();
console.log(current);//2
console.log(obj);//{name: "爱喝茶"}
可以看出上面的结果都发生了变化,这是为什么呢?
ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import
,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import
有点像 Unix 系统的“符号连接”,原始值变了,import
加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
上面讲了下es6和CommonJS模块输出的不同,下面就主要的学习下ES6中的知识:
传送门:阮一峰大大ES6学习指南
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";
严格模式主要有以下限制:
with
语句delete prop
,会报错,只能删除属性delete global[prop]
eval
不会在它的外层作用域引入变量eval
和arguments
不能被重新赋值arguments
不会自动反映函数参数的变化arguments.callee
arguments.caller
this
指向全局对象fn.caller
和fn.arguments
获取函数调用的堆栈protected
、static
和interface
)上面这些限制,模块都必须遵守。由于严格模式是 ES5 引入的,不属于 ES6,所以请参阅相关 ES5 书籍,本书不再详细介绍了。
其中,尤其需要注意this
的限制。ES6 模块之中,顶层的this
指向undefined
,即不应该在顶层代码使用this
。
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个 JS 文件,里面使用export命令输出变量。
几种常见的写法:
//输出多个变量
export let a = 1;
export let b = 2;
//等同于下方
export {
a,b};
//输出函数
export function demo(){
return 1};
//等同于
function demo(){
return 1};
export {
demo};
as关键字重命名
function demo(){
return 1};
export {
demo};
//如果我们想要把对外输出的命名修改下,就可以使用as进行操作
function demo(){
return 1};
export {
demo as demo2};//demo 是我们的函数名称,demo2是我们修改后的名称
使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。
下面介绍几种常用的写法:
function demo(){
return 1};
let a =1;
let b =2;
export {
demo,a,b};
import {
demo} from './demo.js';
//import中也有as关键字,同样也是修改命名
import {
demo as demo2} from './demo.js';
//修改全部命名
import * as num from './demo.js';
console.log(num.a);//1
//这里的num就是导入的全部模块变量,是一个对象
使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载,这里的export default命令,就是为模块指定默认输出。
example1:
//第一种写法
export default function () {
return 'demo';
};
//第二种写法
function demo(){
return 'demo'};
export default demo;
//引入函数引用
import foo from './demo.js';
console.log(foo()) // demo
//这种写法是错误的
export default const a = 1;
从上面的例子中我们看到,我在使用export命令导出的demo函数,在引用的时候,使用的命名是foo但是还是可以执行函数,并且输出正确的值。这是为什么呢?
原因:
export default
就是输出一个叫做default
的变量或方法,然后系统允许你为它取任意名字。export default
命令的本质是将后面的值,赋给default
变量
赋给default变量这句话让我们举例子解释下,更清楚一点:
function demo(){
return 'demo'};
export default demo;
下面是export default 命令的几个注意的地方:
export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default命令只能使用一次。
因为export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句。
参考文章:
ES6 入门教程
https://github.com/mqyqingfeng/Blog/issues/108