当下, 我们几乎所有的项目都是基于 webpack、rollup 等构建工具进行开发的,模块化已经是常态。
我们对它并不陌生,今天,我们就再系统的回顾一下ES6的模块机制, 并总结下常用的操作和最佳实践, 希望对你有所帮助。
随用随取, 是一种我们都希望实现的机制。
在 Javascript 中也一样,把一个大的 Javascript 程序分割成不同的部分, 哪个部分要被用到,就取那一部分。
在很长一段时间内, NodeJS 拥有这样的能力, 后来, 越来越多的库和框架也拥有了模块化的能力, 比如 CommonJS, 或者基于AMD模型的实现(比如RequireJs),还有后续的Webpack, babel等。
到2015年,一个标准的模块化系统诞生了,这就是我们今天要说的主角 - ES6 模型系统。
一眼看上去, 我们不发现, ES6的模型系统和CommonJS语法非常的相似,毕竟ES6 的模型系统是从CommonJS时代走过来的, 深受CommonJS 影响。
看个简单的例子,比如在CommonJs中: (https://flaviocopes.com/commo...
//file.js
module.exports = value;
// 引入value
const value = require('file.js')
而在ES6中:
// const.js
export const value = 'xxx';
import { value } from 'const.js'
语法是非常相似的。
下面我们就主要看 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抛错。
至于抛错的具体原因和其他细节,不是本文讨论的重点, 感兴趣的可以阅读如下链接了解详情。
'use strict' 声明我们都不陌生, 在es5 时代我们也经常使用, 一般是在文件顶部加这个声明,目的就是禁用Javascript中不太友好的一部分,有助于我们写更严谨的代码。
这个特性,在es6语法中是默认开启的, 如果代码里面有不太严格的代码,则会报错,例如:
下面是我从MDN中摘取的一些在严格模式中被禁用的部分:
ES6模块只支持静态导出,你只可以在模块的最外层作用域使用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 用法, 但这远远不是它的全部, 篇幅有限,如果想了解更多, 可以看下面的延伸阅读部分(质量都还不错, 可以看看)。
更多 css-loader 的配置建议前往 github_css-loader 查看,因为版本更新后,配置可能会有变。配置完 webpack,写 css 时要使用相关语法,因为是通过 webpack 打包时进行编译,重新生成新的 css 类名来防止全局变量名污染的。
Node.js 前不久发布了v13.2.0,宣布开始支持ES modules。在此之前,想要在node中使用ES modules,需要添加--experimental-module。v13.2.0版本后,可以直接使用ES modules了。
在node.js中modules(模块)与文件是一一对应的,也就是说一个node.js文件就是一个模块,文件内容可能是我们封装好的一些JavaScript方法、JSON数据、编译过的C/C++拓展等,在关于node.js的误会提到过node.js的架构
node_modules是安装node后用来存放用包管理工具下载安装的包的文件夹。比如webpack、gulp、grunt这些工具。在node.js中模块与文件是一一对应的,也就是说一个node.js文件就是一个模块。
所有的 class 的名称和动画的名称默认属于本地作用域的 CSS 文件。所以 CSS Modules 不是一个官方的规范,也不是浏览器的一种机制,它是一种构建步骤中的一个进程。
虽然已在最新的 LTS v12.17.0 中支持,但是目前仍处于 Stability: 1 - Experimental 实验阶段,如果是在生产环境使用该功能,还应保持谨慎,如果在测试环境可以安装 n install v12.17.0 进行尝试。
有时候使用 npm 上的包,发现有 bug ,我们知道如何修改,但是别人可能一时半会没法更新,或者是我们特殊需求,别人不愿意修改,这时候我们只能自己动手丰衣足食。那么我们应该如何修改别人的源码呢?
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!