【ES6】Moudle

概述

ES6之前JS没有模块系统,社区方案有CommonJS(用于服务器)和AMD(用于浏览器)

// CommonJS模块,运行时加载,只有运行时才能得到这个对象,导致完全无法在编译时做静态优化
let fs = require('fs');

ES6模块的设计思想是尽量静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。而上述两者方案只有在运行时才能确定。

// ES6模块,编译时已确定,效率更好
import { stat, exists, readFile } from 'fs';

当然,这也导致了没法引用 ES6 模块本身,因为它不是对象 

ES6模块采用严格模式

严格模式主要有以下限制。

  • 变量必须声明后再使用
  • 函数的参数不能有同名属性,否则报错
  • 不能使用with语句
  • 不能对只读属性赋值,否则报错
  • 不能使用前缀 0 表示八进制数,否则报错
  • 不能删除不可删除的属性,否则报错
  • 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
  • eval不会在它的外层作用域引入变量
  • evalarguments不能被重新赋值
  • arguments不会自动反映函数参数的变化
  • 不能使用arguments.callee
  • 不能使用arguments.caller
  • 禁止this指向全局对象,ES6模块中this指向undefined
  • 不能使用fn.callerfn.arguments获取函数调用的堆栈
  • 增加了保留字(比如protectedstaticinterface

export命令

export命令用于规定模块的对外接口

// 输出变量、函数或类
export let a = 'lpr'
export function fn() {}

let a = 111
function fn(){}
export {a,fn}

可用as关键字重命名

function v1() { ... }

export {
  v1 as streamV1,
}
  • export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系
  • export语句输出的接口与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
  • export命令可以出现在模块的任何位置,只要处于模块顶层就可以 
  • commonJS模块输出的是值得缓存,不存在动态更新

import命令

import命令用于输入其他模块提供的功能

import { a,b,c } from './profile.js';

为输入的变量重命名使用as关键字 

import { lastName as surname } from './profile.js';
  • import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面改写接口
  • import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js后缀可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。
  • import命令具有提升效果,会提升到整个模块的头部,首先执行
  • import是静态执行,所以不能使用表达式和变量

模块的整体加载

import * as circle from './circle';

export default 命令

为模块指定默认输出。

// 该处默认输出一个匿名函数
export default function () { console.log('foo'); }

其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。 

import customName from './export-default';
customName(); // 'foo'

比较一下默认输出和正常输出。 

// 默认输出
export default function crc32() {}

import crc32 from 'crc32';

// 正常输出
export function crc32() {};

import {crc32} from 'crc32';

export 与 import 的复合写法

export { foo, bar } from 'my_module';

// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
  • 但需要注意的是,写成一行以后,foobar实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foobar
  • 可以实现模块的继承

跨模块常量

如果想设置跨模块的常量(即跨多个文件),或者说一个值要被多个模块共享,可以采用下面的写法。

// constants.js 模块
export const A = 1;
export const B = 3;
export const C = 4;

// test1.js 模块
import * as constants from './constants';
console.log(constants.A); // 1
console.log(constants.B); // 3

// test2.js 模块
import {A, B} from './constants';
console.log(A); // 1
console.log(B); // 3

浏览器加载

传统方法

<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>

deferasync的区别是:

defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;

async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。

defer是“渲染完再执行”,async是“下载完就执行”。另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序

浏览器加载 ES6 模块,也使用<script>标签,但是要加入type="module"属性。

<script type="module" src="./foo.js"></script>

异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>标签的defer属性。

ES6 模块与 CommonJS 模块的差异

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成

Node.js 加载 

有自己的 CommonJS 模块格式,与 ES6 模块格式是不兼容的。目前的解决方案是,将两者分开,ES6 模块和 CommonJS 采用各自的加载方案。从 v13.2 版本开始,Node.js 已经默认打开了 ES6 模块支持。

Node.js 要求 ES6 模块采用.mjs后缀文件名。也就是说,只要脚本文件里面使用import或者export命令,那么就必须采用.mjs后缀名。Node.js 遇到.mjs文件,就认为它是 ES6 模块,默认启用严格模式,不必在每个模块文件顶部指定"use strict"

如果不希望将后缀名改成.mjs,可以在项目的package.json文件中,指定type字段为module

posted @ 2019-12-29 23:54  把我当做一棵树叭  阅读(354)  评论(0)    收藏  举报