ES6
在ES6中,我们可以使用 import 关键字引入模块,通过 exprot 关键字导出模块,功能较之于前几个方案更为强大,也是我们所推崇的。
变量/对象/方法/类-导出/导入
导出
1 | export const A = 42; |
导入
导入变量/方法的时候变量和方法名必须和导出时一致
1 | import { A,api,myfunc,myapi,MyClass } from './A' |
import导入的变量都是只读的,加载后不能修改
1 | import { m } from 'my_module'; |
默认-导出/导入
注意default
一个文件只能有一个
导出
1 | export default "http://www.psvmc.cn"; |
导入
导入默认时变量和方法名可自定义
1 | import A from './A' |
全部导入
1 | let m1 = 1; let m2 = 2; export {m1, m2}; |
导入
1 | import * as mynum from '@/assets/js/Test' |
注意
导出的默认不能直接通过
console.info(mynum);
打印。
直接打印的化结果如下:
全部导出
1 | export * from "module" |
export * from "module"
不包含default导出,但export * as ns from "module"
(在ns
上)是包含default导出的。
页面中使用
报错
Uncaught SyntaxError: Cannot use import statement outside a module
方式1(不建议 学习的时候可以这样)
HTML中直接用要添加type="module"
1 | <script type="module"> |
方式2 通过webpack打包
但是由于ES6目前无法在浏览器中执行,所以,我们只能通过babel将不被支持的import编译为当前受到广泛支持的 require
解决方法:在项目中配置webpack即可
Babel转换ES6
ES6 的导出模块写法有
1 | export default 123; |
Babel 会将这些统统转换成 CommonJS 的 exports
。
1 | exports.default = 123; |
Babel 转换 ES6 的模块输出逻辑非常简单,即将所有输出都赋值给 exports
,并带上一个标志 __esModule
表明这是个由 es6 转换来的 CommonJS 输出。
调用
1 | const { a } = require("./a.js"); |
如果我们使用了 ES6 的模块系统,如果借助 babel 的转换,ES6 的模块系统最终还是会转换成 CommonJS 的规范。
所以我们如果是使用 Babel 转换 ES6 模块,混合使用 ES6 的模块和 CommonJS 的规范是没有问题的,因为最终都会转换成 CommonJS 。
推荐一篇文章写的比较详细
https://blog.csdn.net/gwdgwd123/article/details/104626274
CommonJS(NodeJS)
前端浏览器不支持,用于服务器,Nodejs中使用的是这个规范
1 | exports.area = function(r) { |
CommonJS的核心思想就是通过 require 方法来同步加载所要依赖的其他模块,然后通过 exports 或者 module.exports 来导出需要暴露的接口。
使用
1 | const { area } = require("area"); |
AMD
浏览器端的模块,不能采用后端使用的CommonJS的”同步加载”(synchronous),只能采用”异步加载”(asynchronous),这就是AMD规范诞生的背景。
AMD是RequireJS在推广过程中对模块定义的规范化产出。
AMD规范则是非同步加载模块,允许指定回调函数。
AMD标准中,定义了下面两个API:
- require([module], callback)
- define(id, [depends], callback)
即通过define来定义一个模块,然后使用require来加载一个模块。
require还支持CommonJS的模块导出方式。
test.js
1 | define(['package/lib',...], function(lib) { |
CMD
CMD是SeaJS在推广过程中对模块定义的规范化产出。
CMD是同步模块定义。
1 | //所有模块都通过define来定义 |
二者的区别是前者是对于依赖的模块提前执行,而后者是延迟执行。 前者推崇依赖前置,而后者推崇依赖就近,即只在需要用到某个模块的时候再require。
AMD和CMD区别
规范
- AMD 规范在这里:https://github.com/amdjs/amdjs-api/wiki/AMD
- CMD 规范在这里:https://github.com/seajs/seajs/issues/242
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
CMD 是 SeaJS 在推广过程中对模块定义的规范化产s出。
类似的还有 CommonJS Modules/2.0 规范,是 BravoJS 在推广过程中对模块定义的规范化产出。
这些规范的目的都是为了 JavaScript 的模块化开发,特别是在浏览器端的。
目前这些规范的实现都能达成浏览器端模块化开发的目的。
区别:
定位有差异。
RequireJS 想成为浏览器端的模块加载器,同时也想成为 Rhino / Node 等环境的模块加载器。
Sea.js 则专注于 Web 浏览器端,同时通过 Node 扩展的方式可以很方便跑在 Node 环境中。遵循的规范不同。
RequireJS 遵循 AMD(异步模块定义)规范,Sea.js 遵循 CMD (通用模块定义)规范。规范的不同,导致了两者 API 不同。
Sea.js 更贴近 CommonJS Modules/1.1 和 Node Modules 规范。
CMD 推崇依赖就近,AMD 推崇依赖前置。
看代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b') // 依赖可以就近书写
b.doSomething()
// ...
})
// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()
//...
})虽然 AMD 也支持 CMD 的写法,同时还支持将 require 作为依赖项传递,但 RequireJS 的作者默认是最喜欢上面的写法,也是官方文档里默认的模块定义写法。
推广理念有差异。
RequireJS 在尝试让第三方类库修改自身来支持 RequireJS,目前只有少数社区采纳。
Sea.js 不强推,采用自主封装的方式来“海纳百川”,目前已有较成熟的封装策略。对开发调试的支持有差异。
Sea.js 非常关注代码的开发调试,有 nocache、debug 等用于调试的插件。
RequireJS 无这方面的明显支持。插件机制不同。
RequireJS 采取的是在源码中预留接口的形式,插件类型比较单一。
Sea.js 采取的是通用事件机制,插件类型更丰富。执行机制不同。
对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。
不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。
CMD 推崇 as lazy as possible.API差异。
AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一。
比如 AMD 里,require 分全局 require 和局部 require,都叫 require。
CMD 里,没有全局 require,而是根据模块系统的完备性,提供 seajs.use 来实现模块系统的加载启动。
CMD 里,每个 API 都简单纯粹。
require和import
require 和 import 分别是不同模块化规范下引入模块的语句,下文将介绍这两种方式的不同之处。
1.出现的时间、地点不同
年份 | 出处 | |
---|---|---|
require/exports | 2009 | CommonJS |
import/export | 2015 | ECMAScript2015(ES6) |
2.不同端(客户端/服务器)的使用限制
require/exports | import/export | |
---|---|---|
Node.js | 所有版本 | Node 9.0+(启动需加上 flag –experimental-modules) Node 13.2+(直接启动) |
Chrome | 不支持 | 61+ |
Firefox | 不支持 | 60+ |
Safari | 不支持 | 10.1+ |
Edge | 不支持 | 16+ |
CommonJS 模块化方案 require/exports 是为服务器端开发设计的。服务器模块系统同步读取模块文件内容,编译执行后得到模块接口。(Node.js 是 CommonJS 规范的实现)。
在浏览器端,因为其异步加载脚本文件的特性,CommonJS 规范无法正常加载。所以出现了 RequireJS、SeaJS 等(兼容 CommonJS )为浏览器设计的模块化方案。
两种方案各有各的限制,需要注意以下几点:
原生浏览器不支持 require/exports,可使用支持 CommonJS 模块规范的 Browsersify、webpack 等打包工具,它们会将 require/exports 转换成能在浏览器使用的代码。
import/export 在浏览器中无法直接使用,我们需要在引入模块的
<script>
元素上添加type="module
属性。即使 Node.js 13.2+ 已经支持
import/export
,Node.js官方不建议在正式环境使用。.目前可以使用 babel 将 ES6 的模块系统编译成 CommonJS 规范(注意:语法一样,但具体实现还 是require/exports)。
3.require/exports 是运行时动态加载,import/export 是静态编译
CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
4.require/exports 输出的是一个值的拷贝,import/export 模块输出的是值的引用
require/exports 输出的是值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
import/export 模块输出的是值的引用。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
若文件引用的模块值改变,require 引入的模块值不会改变,而 import 引入的模块值会改变。
5.用法不一致
(1). require/exports 的用法
1 | const fs = require('fs') |
exports 是对 module.exports 的引用,相当于
1 | exports = module.exports = {}; |
在不改变 exports 指向的情况下,使用 exports 和 module.exports 没有区别;如果将 exports 指向了其他对象,exports 改变不会改变模块输出值。示例如下:
1 | //utils.js |
(2). import/export 的写法
1 | import fs from 'fs' |
建议:
建议明确列出我们要引用的内容。
使用 * 虽然很方便,但是不利于现代的构建工具检测未被使用的函数,影响代码优化。
同时需要注意
- 引入 export default 导出的模块不用加 {},引入非 export default 导出的模块需要加 {}。
1 | import fileSystem, {readFile} from 'fs' |
一个文件只能导出一个 default 模块。
在验证代码的时候遇到如下报错
access to script from origin ‘null’ has been blocked by CORS policy
前面提到过,浏览器引入模块的 <script>
元素要添加 type="module
属性,但 module 不支持 FTP 文件协议(file://),只支持 HTTP 协议,所以本地需要使用 http-server 等本地网络服务器打开网页文件。
(3). import/export 不能对引入模块重新赋值/定义
当我尝试给 import 的模块重新赋值时
1 | import {e1} from './webUtils.js'; |
浏览器显示
Uncaught TypeError: Assignment to constant variable.
当我重新定义引用的模块
1 | import {e1} from './webUtils.js'; |
浏览器显示
(index):17 Uncaught SyntaxError: Identifier ‘e1’ has already been declared
(4). ES6 模块可以在 import 引用语句前使用模块,CommonJS 则需要先引用后使用
ES6 模块
1 | //webUtils.js |
CommonJS
1 | //utils.js |
程序报错
ReferenceError: a is not defined
(5) import/export 只能在模块顶层使用,不能在函数、判断语句等代码块之中引用;require/exports 可以。
1 | import fs from './webUtils.js'; |
程序报错
Uncaught SyntaxError: Unexpected token ‘{‘
前面提到过 import/export 在代码静态解析阶段就会生成,不会去分析代码块里面的 import/export,所以程序报语法错误,而不是运行时错误。
6.是否采用严格模式
严格模式是采用具有限制性JavaScript变体的一种方式
import/export 导出的模块默认调用严格模式。
1 | var fun=()=>{ |
require/exports 默认不使用严格模式,可以自定义是否使用严格模式。 例如
1 | exports.fun = ()=>{ |
7.其他模块化方法
import(modulePath) 表达式加载模块并返回一个 promise,该 promise resolve 为一个包含其所有导出的模块对象。
我们可以在代码中的任意位置动态地使用它。例如:
1 | import('/modules/my-module.js') //动态导入 |
建议: 请不要滥用动态导入 import()(只有在必要情况下采用)。静态框架能更好的初始化依赖,而且更有利于静态分析工具和 tree shaking 发挥作用