ES6+ 模块化
1. 前言
JavaScript 在设计之初主要用来开发 Web 页面 的交互、动画和表单验证等单一的 功能 ,而且程序的体积很小,大多数都是独立执行的。随着前端的发展 JavaScript 承接的 功能 越来越多,Node 的出现让 JavaScript 可以作为一门后端开发语言,程序的复杂度瞬间提升,所以有必要提供一种将 JavaScript 程序拆分为可按需导入的单独模块的机制。Node 是 JavaScript 的先行者,它使用了 Commo njs 的规范来实现模块化的。在 ES6 没出来之前有很多模块化的实践,比较有名的有:Commo njs 、AMD、CMD,每个规范都有自己独立的思考。
随着 ES6 模块的发布,AMD 和 CMD 慢慢地淡出了我们的视野。现在主要常见的场景是 Node 端还采用 Commo njs 规范,这是历史原因。前端使用的是 ES6 module 规范,但是不能直接在前端使用,需要通过打包工具进行编译如:Webpack、Babel、Rollup 等。本文中我们将使用 Webpack 进行模块化编译工具,源 代码 放在 GitHub 上,仅供参考。
2. 环境搭建
现在的高级浏览器还能完全地 支持 ES6 的模块化,如何在浏览器中运行 ES6 模块呢?有两种方式:
在浏览器中直接运行 ES6 的模块化,但是需 要做 一定的工作,不能像之前直接在本地浏览器中打开 一个 html 中引入 JS 文件 ; 使用 Webpack、rollup 等模块化打包工具,html 引入编译后的 js 文件 。
这两种方式各有优缺点,第一种是原生的使用方式,浏览器兼容要求比较高,第二种使用的是第三方打包编译工具可以很好地 解决 浏览器兼容问题,但是会有一定的学习成本,并且不能直接在浏览器中运行,只能使用编译后的 文件 。
2.1 浏览器运行原生 ES6 模块
使用浏览器运行原生 ES6 模块的源码在 ES6-wiki 的 mjs 文件 中,浏览器是不能直接运行 ES6 模块化的,需 要做 一些准备工作。
首先,在引入 js 文件 时需要定义 script 的类型: type="module" 。另外,js 文件 的后缀不能使用 .js 了,需要使用 .mjs 。这样还是不能在浏览器中运行,还需要最后一步。模块化会涉及到 文件 系统,而本地打开的 html 文件 是没有服务的,所以我们要使用 node 服务的方式打开 html 文件 ,这里我们使用 node 的包 http-server 可以在相应的 文件 目录中启动 node 服务。安装如下:
npm install --global http-server
安装完启动服务的工具还是会有问题,依然打不开,这是需要在浏览器中打开一些配置:浏览器地址栏输入: chrome://flags/ 然后 搜索 JavaScript 把 Experimental JavaScript 项选择 Enabled 启用状态。如下图。
到这里我们就把前期的工作做完了,如何打开 html 文件 呢?在控制台中进入对应的目录中执行:http-server 命令。本节的目录在 ES6-wiki/packages/module/mjs 下。在浏览器打开控制台返回的地址即可,本实例的地址是: http://127.0.0.1:8080/index.html
2.2 使用 Webpack
Webpack 是模块化打包工具,它兼容现在很多模块化加载方式,本节课程也主要使用 Webpack 的方式来学习 ES6 的模块化。Webpack 需要一定的学习成本可以在 官网 上学习,这里就不进行介绍了,下面给出 webpack.con fig .js 的配置如下:
let path = require ( "path" ) ; let HtmlWebpackPlugin = require ( "html-webpack-plugin" ) ; module . exports = { entry : "./src/index.js" , output : { filename : "bundle.js" , } , plugins : [ new HtmlWebpackPlugin ( { template : './public/index.html' // 模版 文件 } ) ] , module : { rules : [ { test : /\.js$/ , use : { loader : "babel-loader" , } , } , ] , } , } ;
3. 基本使用
ES6 的模块化设计思想是尽量 静态化 ,使得编译时就能确定模块的依赖关系,只能在顶级作用域。模块系统中,每个 文件 都是 一个 模块,模块之间都是相互独立。在 ES6 模块中 自动 采用 严格模式 ,不知道的同学可以去看看,对于学习 JavaScript 语言有一定的帮助。
3.1 export/import
export 是导出语法,import 是导入语法。看下面的实例:
// a.js export let x = ; export let y = ; // main.js import { x , y } from './a.js' ; console . log ( x , y )
上面 代码 中,a.js 文件 中使用 export 导出 x 和 y 两个变量,在 mian.js 文件 中使用 import 进行导入。a.js 中还可以使用对象的方式导出:
let a = ; let b = ; export { a , b , }
从上面的 main.js 文件 中可以看出,export 使用的是引用方式进行导出的,导出的是 一个 接口,所以不能直接导出 一个 值。我们如下实例:
let a = ; export a ; // 编译报错 // 正确的方式如下 Export let a = ;
虽然使用 export 不能直接导出 一个 值,但是可以使用 export default 导出 一个 特定的值:
export default ;
export 模块导出的是 一个 接口,在模块内如果数据更新,则所依赖的地方的值都是最新的。
// a.js let a = ; setInterval ( ( ) => { a ++ } ) export { a } // main.js import { a } from './a.js' ; setInterval ( ( ) => { consolog . log ( a ) } )
import 有声明的特点,类似 var 的特点,可以实现变量提升,但是不能 修改 变量对应的值。
// main.js console . log ( a ) import { a } from './a.js' ; a = ; // 这样赋值是 错误 的
使用 export + from 命令的方式,提供了一种便捷的方式在当前的模块导出其他模块的 内容 ,不能在当前模块下使用导出的变量。
// b.js let a = ; let b = ; export { a , b , } // a.js export { a , b } from './b.js' ; export c = ( ) => { } // 等价于使用import 先导入,然后再使用 export 导出 import { a , b } from './b' ; export { a , b , } // main.js import { a , b , c } from './a.js'
export 和 import 命令规定要处于模块顶层,一旦出现在块级作用域内,就会报错。
// a.js { export let a = ; } // main.js { import { a } from './a' ; } //控制台答应 错误 内容 : 'import' and 'export' may only appear at the top level
上面的 代码 中 export 和 import 都放在块级作用域中的,执行时会报错,提升你 export 和 import 只能在顶级出现。
3.2 export default 命令
export default 命令用来导出模块中 默 认变量或 方法 ,上面我们也提到了使用 export 导出的是 一个 对象不能导出 一个 值类型。
// a.js export default 'imooc' ; // main.js import name from './a.js' console . log ( name ) ; // imooc
export default 命令声明的 函数 可以是匿名的。
export default function ( ) { console . log ( 'imooc' ) ; } // 等价 function fn ( ) { console . log ( 'imooc' ) ; } export default fn ;
也可以是 一个 类:
// a.js export default class { constructor ( ) { console . log ( 'imooc' ) } // ... } // main.js import A from './a' ; const a = new A ( ) ; console . log ( a )
开可以导出的是 一个 对象:
const obj = { name : 'imooc' , getLession : function ( ) { console . log ( 'ES6 imooc' ) ; } } export default obj ;
3.3 as 命令
as 命令是用来 重命名 的,在使用 import 命令导入时可以使用 as 来更改变量名。
// a.js let a = ; let b = ; export { a , b , } // main.js import { a , b as y } from './a' ; console . log ( a , y ) ; // 1,2
如果模块中同时有 export default 导出和 export 导出时,在导入时可以使用 as 对 默 认导出进行 重命名 。
// a.js let a = ; let b = ; export { a , b , } export default let c = ; // main.js import { a , b , default as c } from './a' // 等价于下面直接在外面进行使用 import c , { a , b } from './a'
默 认导出的 内容 也可以放在 export 导出的对象中,但是需要使用 as default 命令:
// a.js let a = ; let b = ; let c = 'imooc' ; export { a , b , c as default , // 相当于 export default 'imooc',给导出的对象 增加 一个 default 属性 }
4. 小结
本节主要讲解了 ES6 Module 的使用,通过对 export、import、default、as 命令的讲解学习了 ES6 Module 的基本 用法 ,基本上涵盖了日常使用的场景。