浅谈ES6模块化

一、什么是ES6模块化

在ES6之前,JS一直没有模块体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法将他们拼装起来。尽管社区制定了一些模块加载方案,主要有CommonJS和AMD两种,但是这两种方案都有自己的局限性,前者主要用于服务器,后者主要用于浏览器。

ES6在语言规格的层面上实现了模块功能,而且实现的相当简单,完全可以取代现有的CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。

ES6模块的设计思想是尽量静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS和AMD都只能在运行时确定这些东西。(运行时加载的缺陷是只有在运行时才能得到整个对象,导致无法在编译时进行静态优化

import {start,exists,readFile} from 'fs';
//上面代码的实质是从fs模块加载这3个方法,而不加载其他方法,这种加载方式称为'编译时加载'或者
//静态加载,即ES6在编译时即可完成模块加载,显然这个效率比CommonJS高

注:ES6的模块自动采用严格模式,不管有没有在模块头部加上'use strict'

二、ES6模块的使用

ES6模块的功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块的功能

export命令

一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取。如果希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。export命令除了输出变量,还可以输出函数或类。

通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。例如:

function v1(){}
function v2(){}
export {
    v1 as streamV1,
    v2 as streamV2,
    v2 as streamLatestVersion
}

上面的代码使用as关键字重命名了函数v1和v2的对外接口,重命名后v2可以用不同的名字输出两次。

需要特别注意的是:export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。

//报错,不能直接输出1
export 1;
//报错,依然直接输出1,1只是一个值,不是一个接口
let num = 1;
export num;

可以改成以下写法

export let m = 1;
const num = 1;
export {
    m,
    num as age
}

他们的实质是,在接口名与模块内部变量之间建立了一一对应的关系。

注意事项:

  • export语句输出的接口与其对应的值是动态绑定关系,即通过该接口可以取到模块内部实时的值,而CommonJS模块输出的是值的缓存,不存在动态更新
  • export命令可以出现在模块顶层任意位置,如果处于块级作用域内就会报错,import命令也是如此。因为处于条件代码块中就无法做静态优化,这违背了ES6模块的设计初衷。

import命令

使用export命令定义了模块的对外接口以后,其他JS文件就可以通过import命令加载这个模块了。例如:

import {year,month,date} from './date'
import 命令接受 一个对象(用大括号表示〉,里面指定要从其他模块导入的变量名。大括号中的变量名必须与被导 入模块 对外接口的名称相同。
如果想为输入的变量名重新取一个名字,要使用as关键字,将输入的变量重新命名。
import命令具有提升效果,会提升到整个模块的头部并首先执行。这种行为的本质是,import命令是编译阶段执行的,在代码运行之前。
由于import是静态执行,所以不能使用表达式和变量等只有在运行时才能得到结果的语法结构。所以以下写法是错误的
//报错
impourt {'f'+'oo'} from './my_module'

//报错
let module = 'my_module';
import {foo} from module;

注意:import会执行所加载的模块,如果多次重复执行同一句import语句,那么只会执行一次。

模块的整体加载

除了指定加载某个输出值,还可以使用整体加载(即星号*)来指定一个对象,所有输出值都加载在这个对象上。

import * as circle from './circle'

注意:模块整体加载所在的对象应该是可以静态分析的,所以不允许运行时改变。

import * as circle from './circle'
//下面两行是不允许的
circle.foo = 'foo';
circle.area = function (){}

export default 命令

使用import命令时用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档去了解模块有哪些属性和方法。为了方便用户,使其不阅读文档就能加载模块,可以使用export default命令为模块制定默认输出。

export default function(){
    console.log('foo')
}

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

需要注意的是,这时import后面不需要使用花括号。

export default命令用于指定模块的默认输出,显然,一个模块只能有一个默认输出,因此export default命令在一个模块内只能使用一次。本质上,export default就是输出一个叫default的变量或方法,然后系统允许我们为它取任意名字。

三、ES6模块与CommonJS模块的差异

ES6模块与CommonJS模块主要有两大差异:

  1. CommonJS模块输出的是一个值的复制,ES6模块输出的是值的引用;
  2. CommonJS模块是运行时加载,ES6模块时静态加载(编译时输出接口)

CommonJS输出的是值的复制,也就是说一旦输出一个值,模块内部的变化就影响不到这个值。例如:

//lib.js
var count = 3;
function add(){
    count++;
}
module.exports = {
    count:count,
    add:add,
}

//main.js
var mod = require('./lib.js);
console.log(mod.count);//3
mod.add();
console.log(mod.count);//3

上面的代码说明lib.js加载后它的内部变化就影响不到输出的mod.count了。这是因为mod.count是一个原始类型的值,会被缓存。

而如果使用ES6模块的话,第二次控制台会打印出4,这是因为ES6的import命令会生成一个只读引用。由于ES6输入的模块变量是一个只读引用,所以对其重新赋值会报错。

//lib.js
export obj = {}

//main.js
import {obj} from './lib.js'
obj.foo = 'foo';//OK
obj = {};//TypeError

 

你可能感兴趣的:(ES6)