1.commonJS
在说 es6 模块以前,我们先来看一下后端普遍使用的打包方式,commonJS的一些特性。
- 同步加载:也就是串行执行,后面的任务要等到前面任务执行完才能继续执行。
- 语法:commonJS中使用 require 引入,module.exports 输出。
- 执行顺序:commonJS输出的是一个值得拷贝。也就是说,一旦输出一个值(该值会被缓存起来),模块内部的变化就不会影响到这个值。过程大概是,先将引入模块执行一遍,再执行后面的方法,等到内容执行完,生成一个变量存储需要输出的内容。
仔细阅读以下代码
// module.js
let a = 1;
function foo(){
a++;
}
module.exports = {foo, a};
// index.js
let {foo, a} = require('./module.js');
console.log(a); // 1
foo();
console.log(a); // 1
- 意义:commonJS的意义在于将聚类的方法和变量等限定的私有域中,同时支持导入和导出,将上下游模块无缝衔接,每个模块具有独立的空间,互不干扰。
2.import、export
ES6以后出现的import、export很好的实现了模块功能。核心思想是尽量静态化,使得编译时就能确定依赖关系,以及输出和输入的变量。而commonJS是在运行时确定这些东西。
- 因为这个原因,有产生了以下两个问题:
1.import、export都必须写在模块顶层 2.引入的模块应该是可以静态分析的,所以不允许运行时改变。也就是路径中不能使用变量。即无法实现如下代码中展示的require的功能
// commonJS require
let path = './module'
let module = require(path);
- 异步加载:也就是并发执行,模块几乎同时导入,后面模块不需要等待前面模块导入完成。
- 语法:ES6 Module中使用import引入,export输出。
- 执行顺序:ES6 Module 输出的是值的动态引用,不会缓存。还是相同的代码,对比commonJS
// module.js
let a = 1;
function foo(){
a++;
}
export {foo, a};
// index.js
let {foo, a} = require('./module.js');
console.log(a); // 1
foo();
console.log(a); // 2
- import 可以执行模块,多次import只会执行一次,import在静态解析阶段执行
import foo from './module';
import foo from './module';
以上代码中foo只会被导入一次
- import * as module from './module’引入module.js中所有的方法并存放到变量module中。
- 为模块指定默认输出,一个文件中只能有一个export default,且后面不能跟变量声明的语句
// 与普通输出的区别
export default function add(){}
import add from './add'
export function add(){}
import {add} from './add'
本质上,export default 就是输出一个叫 default 的变量或者方法,然后系统允许你为它重命名。所以下面写法也是等效的
function add(){}
export {add as default}; // 等同于export default add
import {default as foo} from './module'; // 等同于import foo from './module'
3.export 与 import 的复合写法
export {foo,bar} from 'module';
// 等同于
import {foo,bar} from 'module;
export {foo,bar}
上面代码中,export和import语句可以结合为一行代码。但是,写成一行以后,foo和bar实际上没有被导入当前模块,只是相当于当前对外转发了这两个接口,导致当前模块不能直接使用foo,bar。
4.跨模块常量
- const声明的常量只能在当前代码块中有效。如果想设置跨模块的常量,可以采用以下写法:
export const db = {
a: '1',
b: '2',
...
}
import {db} from './module';
vue项目中props应用,这样子组件就可以自己修改props中的属性,而不需要通过调用父组件的方法了。
// 父组件
<Child :userInfo="userIinfo"/>
data(){
return{
userInfo:{
name: '小明',
age:18
}
}
}
// 子组件
props:{
value(){
type: Object
}
},
created(){
this.value.name = '小红';
}
5.import()函数
- 上文我们说过,import的模块需要静态分析,所以不能用于动态加载。也就不能完成required同样的功能
const path = './' + fileName;
const myModule = required(path);
因此,引入了import()函数,返回一个Promise对象
import (path).then(res=>{
console.log(res)
}).catch(err=>{
console.log(res)
})
- 这个函数的引入起到的很好的作用,比如我们在做多语言加载的时候,我们需要引入语言包,但是我们又不想一次性将所有语言包全部引入,我们只需要引入需要的语言包就可以了,那么就用到了import()函数,下面是一个按需加载语言包的例子
export function loadLanguageAsync(lang){
if (i18n.locale !== lang){
if (!loadedLanguages.includes(lang)){
return import(/* webpackChunkName: "lang-[request]" */ '@/lang/' + lang).then(msgs=>{
i18n.setLocaleMessage(lang, msgs.default);
loadedLanguages.push(lang);
return setI18nLanguage(lang);
})
}
return Promise.resolve(setI18nLanguage(lang));
}
return Promise.resolve(lang);
}
6.浏览器加载
传统方法
- script标签默认是同步加载的,加上defer和async就会开启异步加载。区别:
defer要等到整个页面在内存中正常渲染结束,才会执行;async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。另外,如果有多个defer脚本,会按照他们在页面中出现的顺序加载,而多个async脚本,是不能保证按顺序加载
es6模块加载
<script type="module">
import {add, redis} from './module';
</script>
node.js加载
node中原本存在的commonJS与es6的模块加载并不兼容。因此node中做了限制
.mjs 文件总是以es6模块加载; .cjs 文件总是以commonJS加载, .js 文件的加载取决于 package.json 中type字段,若 type=“module” 则以es6模块加载,默认commonJS