当下, 咱们几乎全部的项目都是基于 webpack、rollup 等构建工具进行开发的,模块化已是常态。javascript
咱们对它并不陌生,今天,咱们就再系统的回顾一下ES6的模块机制, 并总结下经常使用的操做和最佳实践, 但愿对你有所帮助。html
随用随取, 是一种咱们都但愿实现的机制。前端
在 Javascript 中也同样,把一个大的 Javascript 程序分割成不一样的部分, 哪一个部分要被用到,就取那一部分。java
在很长一段时间内, NodeJS 拥有这样的能力, 后来, 愈来愈多的库和框架也拥有了模块化的能力, 好比 CommonJS, 或者基于AMD模型的实现(好比RequireJs),还有后续的Webpack, Babel等。webpack
到2015年,一个标准的模块化系统诞生了,这就是咱们今天要说的主角 - ES6 模型系统。 es6
一眼看上去, 咱们不发现, ES6的模型系统和CommonJS语法很是的类似,毕竟ES6 的模型系统是从CommonJS时代走过来的, 深受CommonJS 影响。 web
看个简单的例子,好比在CommonJs中: (https://flaviocopes.com/commo...express
//file.js module.exports = value; // 引入value const value = require('file.js')
而在ES6中:segmentfault
// const.js export const value = 'xxx'; import { value } from 'const.js'
语法是很是类似的。promise
下面咱们就主要看 import 和 export,和几个相关的特性,了解ES6 Modules的更多方面。
模块化的好处主要是两点:
1. 避免全局变量污染 2. 有效的处理依赖关系
随着时代的演进, 浏览器原生也开始支持es6 import 和 export 语法了。
先看个简单的例子:
<script type="module"> import { addTextToBody } from '/util.js'; addTextToBody('Modules are pretty cool.'); </script> // util.js export function addTextToBody(text) { const div = document.createElement('div'); div.textContent = text; document.body.appendChild(div); }
若是要处理事件,也是同样, 看个简单的例子:
<button id="test">Show Message</button> <script type="module" crossorigin src="/showImport.js"></script> // showImport.js import { showMessage } from '/show.js' document.getElementById('test').onclick = function() { showMessage(); } // show.js export function showMessage() { alert("Hello World!") }
若是你想跑这个demo, 注意要起个简单的服务:
$ http-server
不然,你会看到一个CORS抛错。
至于抛错的具体缘由和其余细节,不是本文讨论的重点, 感兴趣的能够阅读以下连接了解详情。
https://jakearchibald.com/201...
https://developer.mozilla.org...
'use strict' 声明咱们都不陌生, 在es5 时代咱们也常用, 通常是在文件顶部加这个声明,目的就是禁用Javascript中不太友好的一部分,有助于咱们写更严谨的代码。
这个特性,在es6语法中是默认开启的, 若是代码里面有不太严格的代码,则会报错,例如:
下面是我从MDN中摘取的一些在严格模式
中被禁用
的部分:
Variables can’t be left undeclared
Function parameters
must have unique names
(or are considered syntax errors)with
is forbiddenread-only properties
Octal numbers
like 00840 are syntax errors
delete undeletable properties
throw an errordelete prop
is a syntax error, instead of assuming delete global[prop]eval
doesn’t introduce new variables into its surrounding scopeeval
and arguments can’t be bound or assigned toarguments
doesn’t magically track changes to method parametersarguments.callee
throws a TypeError, no longer supportedarguments.caller
throws a TypeError, no longer supportedfn.caller
and fn.arguments to access the JavaScript stackReserved words
(e.g protected, static, interface, etc) cannot be boundES6模块只支持静态导出,你只能够在模块的最外层做用域使用export,不可在条件语句中使用,也不能在函数做用域中使用。
从分类上级讲, exports 主要有三种:
// Exporting individual features export let name1, name2, …, nameN; // also var, const export let name1 = …, name2 = …, …, nameN; // also var, const export function functionName(){...} export class ClassName {...} // Export list export { name1, name2, …, nameN }; // Renaming exports export { variable1 as name1, variable2 as name2, …, nameN }; // Exporting destructured assignments with renaming export const { name1, name2: bar } = o; // Default exports export default expression; export default function (…) { … } // also class, function* export default function name1(…) { … } // also class, function* export { name1 as default, … }; // Aggregating modules export * from …; // does not set the default export export * as name1 from …; export { name1, name2, …, nameN } from …; export { import1 as name1, import2 as name2, …, nameN } from …; export { default } from …;
下面我就介绍一下常见的 exports用法。
具名导出,这种方式导出多个函数,通常使用场景好比 utils、tools、common 之类的工具类函数集,或者全站统一变量等。
只须要在变量或函数前面加 export
关键字便可。
//------ lib.js ------ export const sqrt = Math.sqrt; export function square(x) { return x * x; } export function diag(x, y) { return sqrt(square(x) + square(y)); } //------ main.js 使用方式1 ------ import { square, diag } from 'lib'; console.log(square(11)); // 121 console.log(diag(4, 3)); // 5 //------ main.js 使用方式2 ------ import * as lib from 'lib'; console.log(lib.square(11)); // 121 console.log(lib.diag(4, 3)); // 5
咱们也能够直接导出一个列表,例如上面的lib.js能够改写成:
//------ lib.js ------ const sqrt = Math.sqrt; function square(x) { return x * x; } function add (x, y) { return x + y; } export { sqrt, square, add }
这种方式比较简单,通常用于一个类文件,或者功能比较单一的函数文件使用。
一个模块中只能有一个export default默认输出。
export default与export的主要区别有两个:
不须要知道导出的具体变量名, 导入(import)时不须要{}.
//------ myFunc.js ------ export default function () {}; //------ main.js ------ import myFunc from 'myFunc'; myFunc();
导出一个类
//------ MyClass.js ------ class MyClass{} export default MyClass; //------ Main.js ------ import MyClass from 'MyClass';
注意这里默认导出不须要用{}。
混合导出,也就是 上面第一点和第二点结合在一块儿的状况。比较常见的好比 Lodash,都是这种组合方式。
//------ lib.js ------ export var myVar = ...; export let myVar = ...; export const MY_CONST = ...; export function myFunc() { // ... } export function* myGeneratorFunc() { // ... } export default class MyClass { // ... } // ------ main.js ------ import MyClass, { myFunc } from 'lib';
再好比lodash例子:
//------ lodash.js ------ export default function (obj) { // ... }; export function each(obj, iterator, context) { // ... } export { each as forEach }; //------ main.js ------ import _, { forEach } from 'lodash';
通常状况下,export输出的变量就是在原文件中定义的名字,但也能够用 as 关键字来指定别名,这样作通常是为了简化或者语义化export的函数名。
//------ lib.js ------ export function getUserName(){ // ... }; export function setName(){ // ... }; //输出别名,在import的时候能够同时使用原始函数名和别名 export { getName as get, //容许使用不一样名字输出两次 getName as getNameV2, setName as set }
有时候为了不上层模块导入太多的模块,咱们可能使用底层模块做为中转,直接导出另外一个模块的内容以下:
//------ myFunc.js ------ export default function() {...}; //------ lib.js ------ export * from 'myFunc'; export function each() {...}; //------ main.js ------ import myFunc, { each } from 'lib'; export 只支持在最外层静态导出、只支持导出变量、函数、类,以下的几种用法都是错误的。 `错误`的export用法: //直接输出变量的值 export 'Mark'; // 未使用中括号 或 未加default // 当只有一个导出数,需加default,或者使用中括号 var name = 'Mark'; export name; //export不要输出块做用域内的变量 function () { var name = 'Mark'; export { name }; }
import的用法和export是一一对应的,可是import支持静态导入和动态导入两种方式,动态import支持晚一些,兼容性要差一些。
下面我就总结下import的基本用法:
当export有多个函数或变量时,如文中export的第一点,可使用 * as 关键字来导出全部函数及变量,同时 as 后面跟着的名称作为 该模块的命名空间。
//导出lib的全部函数及变量 import * as lib from 'lib'; //以 lib 作为命名空间进行调用,相似于object的方式 console.log(lib.square(11)); // 121
从模块文件中导入单个或多个函数,与 * as namepage 方式不一样,这个是按需导入。以下例子:
//导入square和 diag 两个函数 import { square, diag } from 'lib'; // 只导入square 一个函数 import { square } from 'lib'; // 导入默认模块 import _ from 'lodash'; // 导入默认模块和单个函数,这样作主要是简化单个函数的调用 import _, { each } from 'lodash';
和 export 同样,也能够用 as 关键字来设置别名,当import的两个类的名字同样时,可使用 as 来重设导入模块的名字,也能够用as 来简化名称。
好比:
// 用 as 来 简化函数名称 import { reallyReallyLongModuleExportName as shortName, anotherLongModuleName as short } from '/modules/my-module.js'; // 避免重名 import { lib as UserLib} from "alib"; import { lib as GlobalLib } from "blib";
有时候咱们只想import一个模块进来,好比样式,或者一个类库。
// 导入样式 import './index.less'; // 导入类库 import 'lodash';
静态import在首次加载时候会把所有模块资源都下载下来.
咱们实际开发时候,有时候须要动态import(dynamic import)。
例如点击某个选项卡,才去加载某些新的模块:
// 当动态import时,返回的是一个promise import('lodash') .then((lodash) => { // Do something with lodash. }); // 上面这句实际等同于 const lodash = await import('lodash');
es7的新用法:
async function run() { const myModule = await import('./myModule.js'); const { export1, export2 } = await import('./myModule.js'); const [module1, module2, module3] = await Promise.all([ import('./module1.js'), import('./module2.js'), import('./module3.js'), ]); } run();
以上, 我总结了ES6 Module 的简单背景和 常见的import , export 用法, 但这远远不是它的所有, 篇幅有限,若是想了解更多, 能够看下面的延伸阅读部分(质量都还不错, 能够看看)。
若是以为内容有帮助,能够关注个人公众号 「 前端e进阶 」,掌握最新资讯,一块儿学习, 一块儿成长!
ECMAScript 6 modules: the final syntax
Node中没搞明白require和import,你会被坑的很惨
https://www.zhangxinxu.com/wordpress/2018/08/browser-native-es6-export-import-module/