ES6的Module模块

9 篇文章 0 订阅
设计思想

ES6模块的设计思想是尽可能的静态化,使得编译时就能确定模块间的依赖关系。这也是与CommonJS和AMD运行时才能确定模块间的关系的主要区别。
以CommonJS来说明一下运行时加载的原理。

// CommonJS模块
let { beef, wine, glass } require(material.js);

//等价于
let materials = require(material.js);
let beef = materials.beef;
let wine = materials.wine;
let glass = materials.glass;

以上代码实质上是先整体加载material模块(运行material方法),该模块返回一个对象,然后读取对象上的三个属性。这种加载称为’运行时加载‘,因为只有在运行时执行material方法才能得到material对象。

有时候这种方式会带来性能上的浪费,因为我们不想执行material方法的全部内容,我们只想拿到我们需要的那三个属性。就好比:我们想要吃牛排和红酒,我们只需要牛肉、红酒和被子就够了,但是现在我们却拿来了番茄、啤酒等我们不需要的东西。

ES6模块不是对象,而是通过export命令输出,import命令输入。

let { beef, wine, glass } from 'material.js'

上面的代码只加载了 beef、wine、glass三个方法,而不加载其他的方法,这个过程在编译阶段就完成了,所以效率比CommonJS高。

疑惑
有些人会有疑惑:这种方式导致在还没使用这些方法前就加载了它们,而且加载后也可能不会用到,这样会不会浪费性能?

解答
其实关于这个问题可以这么理解,加载哪些方法是由开发者决定的,我们可以在一开始就不加载哪些无用的方法。第二,如果确定了加载的方法都是必须的,那么这种编译时加载的效率肯定比运行时加载效率高,就好比生火前先把食材准备好再做饭要比生完火再准备食材做饭效率高一样。

export

export命令用于规定模块的对外输出。
一个文件就是一个独立的模块,模块内的变量和方法外部无法访问,如果希望外部能够读取模块内的变量或方法,就需要使用export输出该变量。

1. 可以单个输出某个变量也可集中输出多个变量
// 单独输出
export var a = 10;
export var b  =20;
export function exp(){} ;

//集中输出
var a = 10;
var b 20;
function exp(){};
export { a, b, exp }

应优先考虑第二种输出方式,因为能很清晰的看出输出的所有内容。

2. 别名

我们可以通过 as 关键字来给输出的内容取别名。 import也是如此

function method(){};
export { method as util }
3. 一一对应

export规定的是对外的接口而不是具体的值,必须与模块内的变量或方法一一对应。
我对这句话的理解是:导出时要用花括号包裹,或者在变量或方法声明时导出。

错误的写法❌

export 1;

var a = 10;
export a;

以上两种方法都是错误的,他们都直接输出了具体的值,而没有提供对外的接口。第二种方式虽然通过变量a输出,但还是直接输出10,而10是个具体的值。

正确的写法✅

//声明时导出
export var a = 10;
export function exp(){};

//花括号包裹
export { a, exp }
4. 动态绑定

export导出的接口,与其对应的值是动态绑定的,通过该接口,可以获取模块内部实时的值。

export var a = 10;
setTimeout( ()=>{ a++ },1000 )

在使用接口a时一开始得到10,一秒后得到11

5. 处于模块最外层

export命令可以出现在模块的任何位置,但一定存在于整个模块的最外层,而不能存在局部作用域内。这是因为如果存在局部作用域内违背了编译时加载的原理。 import命令也是如此

var a = 10;
function foo(){
    export { a }
}
foo()  //只有在运行时才能导出a,违背了编译时加载原理
export default
1. 任意名字

导入模块时,可以给使用export default输出的接口指定任意名字

// export.js
export default function foo(){}

import anyName from 'export.js'

export default用于指定一个模块的默认输出,且只能唯一存在,所以import后不需要使用花括号,因为只可能唯一对应export default命令。

2. 本质

本质上,export default是输出一个名叫default的变量,并且允许你给它任意命名。所以以下写法有效

//export.js
function foo(){};
export {foo as default}//等同于 export default foo

import {default as foo} from 'export.js'
// 等同于 import foo from 'export.js'

因为export default命令实质是输出一个变量,所以不能在变量声明时使用

//正确
export var a = 10;  

//正确
var a = 10;
export { a }

//错误
export default var a = 10;

同样地,因为export default命令的本质是将后面的值,赋给default变量,所以可以直接将一个值写在export default之后。

//正确
export default 10;

//错误
export 10;
import
1. 名称相同
//输出 export.js
export { foo, bar }

//输入
import { foo, bar } from 'export.js'

import命令接受一对花括号,花括号里的变量名要与被导入文件(export.js)里的输出保持一致。

2. 只读

通过import命令输入的变量都是只读的。所以不能改写输入的接口

import { bar } from 'util.js'
bar = 10; // 报错

如果bar是一个对象则可以修改bar的属性。

import { bar } from 'util.js'
bar.name = 'tom'  // 合法操作

虽然上述代码修改bar的属性成功,但是其他模块也能读取修改后的值,这样很容易导致代码混乱出错,而且这种错误很不容易被发现。所以建议永远不要修改import输入的变量。

3. import提升

import具有提升效果,会提升到代码顶部率先执行。

foo();
import { foo } from 'util.js' 

以上代码会正常运行,因为import的执行早于foo方法的调用。这种现象的实质是,import是编译时加载的,在代码运行之前。

4. 只执行一次

如果多次重复执行同一句import语句,那么只会执行一次,而不会执行多次。

import 'lodash';
import 'lodash';

上面的代码加载了两次lodash,但是只会执行一次。

import { foo } from 'util.js'
import { bar } from 'util.js'
//等同于
import { foo, bar } from 'util.js'
import()

前面介绍过 import命令是在编译时加载,所以它只能被JavaScript引擎静态分析,而不能包裹在函数内或代码块内随着运行时动态解析。所以以下代码会报错。

if(num == 100){
    import { foo } from 'util.js'
}

在编译阶段并不会分析或者执行if语句,所以上述写法毫无意义。这样的设计导致无法在运行时加载模块,所以仅从语法上就否定了条件加载的可能性。
所以有提案引入import(),来实现动态加载。

import(specifier)

参数specifier指要加载模块的位置,import命令接受什么样的参数,import()就接受什么样的参数。后者实现动态加载。

import()返回一个Promise对象,所以它是异步加载。

let path = './util.js'
import(path)
    .then((module)=>{
        module.default  // 获取要加载模块的默认输出
        //...
    }).catch((err)=>{
        //...
    })

因为import()的这些特性我们可以在方法中加载模块或者实现条件加载。

input.addEventListener('blur',()=>{
    import('util.js')
        .then((module)=>{
        
        }).catch((err)=>{
        
        })
})
if(condition){
    import('moduleA.js')
        .then((module)=>{
        
        }).catch((err)=>{
        
        })
}else {
    import('moduleB.js')
        .then((module)=>{
        
        }).catch((err)=>{
        
        })
}

import()的参数允许动态生成

import( buildPath() ).then(...)

根据buildPath()返回值不同,加载不同模块。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值