TOC
ECMAScript 6 简介
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
Module 的语法
概述
- 基本用法:
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
CommonJS
和AMD
模块,都只能在运行时确定这些东西。// CommonJS模块 let { stat, exists, readFile } = require('fs'); // ES6模块 import { stat, exists, readFile } from 'fs';
严格模式
- 基本用法:
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上
"use strict";
。 - 严格模式限制:
- 变量必须声明后再使用
- 函数的参数不能有同名属性
- 不能使用
with
语句 - 不能对只读属性赋值
- 不能使用前缀 0 表示八进制数
- 不能删除不可删除的属性
- 不能删除变量
delete prop
,会报错,只能删除属性delete global[prop]
eval
不会在它的外层作用域引入变量eval
和arguments
不能被重新赋值arguments
不会自动反映函数参数的变化- 不能使用
arguments.callee
和arguments.caller
- 禁止
this
指向全局对象 - 不能使用
fn.caller
和fn.arguments
获取函数调用的堆栈 - 增加了保留字(比如
protected
、static
和interface
)
export 命令
- 基本用法:
模块功能主要由两个命令构成:
export
和import
。export
命令用于规定模块的对外接口,export
语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
//输出一个变量 export var firstName = 'Michael'; export var lastName = 'Jackson'; export var year = 1958; //输出一组变量 var firstName = 'Michael'; var lastName = 'Jackson'; var year = 1958; export { firstName, lastName, year as date }; //export命令除了输出变量,还可以输出函数或类(class)。 //输出一个函数 export function multiply(x, y) { return x * y; }; //输出一组函数 function v1() { ... } function v2() { ... } export { v1 as streamV1, v2 as streamV2, v2 as streamLatestVersion };
import 命令
- 基本用法:
import
命令用于输入其他模块提供的功能,import
命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。import { firstName, lastName as surname, year } from './profile.js'; function setName(element) { element.textContent = firstName + ' ' + surname; }
模块的整体加载
- 基本用法:
除了指定加载某个输出值,还可以使用整体加载,即用星号(
*
)指定一个对象,所有输出值都加载在这个对象上面。//a.js function v1() { ... } function v2() { ... } export { v1, v2}; //b.js import * as circle from './a.js'; circle.v1(); circle.v2();
export default 命令
- 基本用法:
export default
命令,为模块指定默认输出。其他模块加载该模块时,import
命令可以为该输出指定任意名字。// 默认输出 export default function crc32() { ... } // 输出 import crc32 from 'crc32'; // 输入 // 正常输出 export function crc32() { ... };// 输出 import {crc32} from 'crc32'; // 输入
export 与 import 的复合写法
- 基本用法:
如果在一个模块之中,先输入后输出同一个模块,
import
语句可以与export
语句写在一起。export { foo, bar } from 'my_module'; // 可以简单理解为 import { foo, bar } from 'my_module'; export { foo, bar };
写成一行以后,foo和bar实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foo和bar。
跨模块常量
- 基本用法:
const
声明的常量只在当前代码块有效。如果想设置跨模块的常量(即跨多个文件),或者说一个值要被多个模块共享,可以采用下面的写法。// constants.js 模块 export const A = 1; export const B = 3; // test1.js 模块 import * as constants from './constants'; console.log(constants.A); // 1 console.log(constants.B); // 3
如果要使用的常量非常多,可以建一个专门的
constants
目录,将各种常量写在不同的文件里面,保存在该目录下。然后,将这些文件输出的常量,合并在index.js
里面。使用的时候,直接加载index.js
就可以了。// constants/db.js export const db = { url: 'http://my.couchdbserver.local:5984', admin_username: 'admin', admin_password: 'admin password' }; // constants/user.js export const users = ['root', 'admin', 'staff', 'ceo', 'chief', 'moderator']; // constants/index.js export {db} from './db'; export {users} from './users'; // script.js import {db, users} from './constants/index';
Module 的加载实现
浏览器加载
- 传统方法:HTML 网页中,浏览器通过
<script>
标签加载JavaScript
脚本。默认情况下,浏览器是同步加载JavaScript
脚本,即渲染引擎遇到<script>
标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。<!-- 页面内嵌的脚本 --> <script type="application/javascript"> // module code </script> <!-- 外部脚本 --> <script type="application/javascript" src="path/to/myModule.js"> </script>
浏览器允许脚本异步加载:
defer
: 要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行async
: 一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染
<script src="path/to/myModule.js" defer></script> <script src="path/to/myModule.js" async></script>
- 加载规则:
浏览器加载 ES6 模块,也使用
<script>
标签,但是要加入type="module"
属性。浏览器对于带有type="module"
的<script>
,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>
标签的defer
属性。有多个<script type="module">
,它们会按照在页面出现的顺序依次执行。<script type="module" src="./foo.js"></script> <!-- 等同于 --> <script type="module" src="./foo.js" defer></script>
ES6 模块与 CommonJS 模块
- 差异:
CommonJS
模块输出的是一个值的拷贝,ES6
模块输出的是值的引用:ES6
模块的运行机制与CommonJS
不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import
,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
CommonJS
模块是运行时加载,ES6
模块是编译时输出接口:- 因为
CommonJS
加载的是一个对象(即module.exports
属性),该对象只有在脚本运行完才会生成。 - 而
ES6
模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
- 因为
Node.js 加载
- 基本用法:
Node.js
对ES6
模块的处理比较麻烦,因为它有自己的CommonJS
模块格式,与ES6
模块格式是不兼容的。目前的解决方案是,将两者分开,ES6
模块和CommonJS
采用各自的加载方案。Node.js
要求ES6
模块采用.mjs
后缀文件名。- 如果不希望将后缀名改成
.mjs
,可以在项目的package.json
文件中,指定type
字段为module
。 - 如果这时还要使用
CommonJS
模块,那么需要将CommonJS
脚本的后缀名都改成.cjs
。 - 如果没有
type
字段,或者type
字段为commonjs
,则.js
脚本会被解释成CommonJS
模块。{ "type": "module" }
.mjs
文件总是以ES6
模块加载,.cjs
文件总是以CommonJS
模块加载,.js
文件的加载取决于package.json
里面type
字段的设置。
main 字段
-
基本用法:
package.json
文件有两个字段可以指定模块的入口文件:main
和exports
。// ./node_modules/es-module-package/package.json { "type": "module", "main": "./src/index.js" } // ./my-app.mjs import { something } from 'es-module-package'; // 实际加载的是 ./node_modules/es-module-package/src/index.js
exports字段
- 基本用法:
exports
字段的优先级高于main
字段。- 子目录别名:
package.json
文件的exports
字段可以指定脚本或子目录的别名。// ./node_modules/es-module-package/package.json { "exports": { "./features/": "./src/features/" } } import feature from 'es-module-package/features/x.js'; // 加载 ./node_modules/es-module-package/src/features/x.js
- main 的别名:
exports
字段的别名如果是.
,就代表模块的主入口,优先级高于main
字段,并且可以直接简写成exports
字段的值。{ "exports": { ".": "./main.js" } } // 等同于 { "exports": "./main.js" }
由于
exports
字段只有支持ES6
的Node.js
才认识,所以可以用来兼容旧版本的Node.js
。{ "main": "./main-legacy.cjs", "exports": { ".": "./main-modern.cjs" } }
- 条件加载: 利用
.
这个别名,可以为ES6
模块和CommonJS
指定不同的入口。目前,这个功能需要在Node.js
运行的时候,打开--experimental-conditional-exports
标志。//别名.的require条件指定require()命令的入口文件(即 CommonJS 的入口),default条件指定其他情况的入口(即 ES6 的入口)。 { "type": "module", "exports": { ".": { "require": "./main.cjs", //CommonJS 的入口 "default": "./main.js" //ES6 的入口 } } } // 等同于 { "exports": { "require": "./main.cjs", //CommonJS 的入口 "default": "./main.js" //ES6 的入口 } }
- 子目录别名:
「下次一定」
下次一定
使用微信扫描二维码完成支付