模块化
JavaScript用“共享一切”的方法加载代码,这是该语言中最容易出错且容易令人感到困惑的地方。其他语言使用诸如包这样的概念来定义代码作用域,但在ECMAScript6以前,在应用程序的每一个JavaScript中定义的一切都共享一个全局作用域。随着Web应用程序变得更加复杂,JavaScript代码的使用量也开始增长,这一做法会引起问题,如命名冲突和安全问题。ECMAScript6的一个目标是解决作用域问题,也为了使JavaScript应用程序显得有序,于是引进了模块。
什么是模块
模块是自动运行在严格模式下并且没有办法退出运行的JavaScript代码。与共享一切架构相反的是,在模块顶部创建的变量不会自动被添加到全局共享作用域,这个变量仅在模块的顶级作用域中存在,而且模块必须导出一些外部代码可以访问的元素,如变量或函数。模块也可以从其他模块导入绑定。
js模块系统简化史
没有模块 -> CMD(CommonJS Moudle Definition-> AMD(Asynchronous Moudle Definition) -> 语言提供模块支持
使用ES6的模块
1.导出
2.导入
3.webpack编译
//mod1.js -定义模块
export let a=12;
export let b=5;
export let c=6;
//index.js -使用模块
let b=8;
import {a,b as name2} from './mod1';
console.log(a,b,name2);
//wenpack.config.js -编译配置
const path=require('path');
module.exports={
mode: 'development',
entry: './js/index.js',
output: {
path: path.resolve(__dirname, 'build'),
filename: 'bundle.js'
}
};
导出(export)
除了export之外,每一个声明都和原来一模一样,因为导出类和函数需要有一个名称,所以代码中的每一个类和函数都要有名称。除非使用default关键字,否则不能用export导出匿名函数和类
//变量
export let a=12;
export const a=12;
//一堆
let a,b,c=...;
export {a, b, c, ...};
//导出函数
export function show(){
...
}
//导出class
export class Person{
...
}
导入(import)
从模块中导出的功能可以通过import关键字在另一个块中访问
import { x } from "./xxx" //导入单个绑定
import { x , x } from "./xxx" //导入多个绑定
import * as xxx from './xxx'; //导入整个模块
//这种导入被称作命名空间导入,模块中得值将被当做模块的属性进行访问
import mod1 from 'xxx'; //引入default成员
模块语法的限制
export和import的一个重要的限制是,它们必须在其他语句和函数之外使用。例如,下面代码会给出一个语法错误:
if(flag){
export flag;
//语法错误
}
export语句不允许出现在if语句中,不能有条件导出或以任何方式动态导出。模块语法存在的一个原因是要让JavaScript
引擎静态地确定哪些可以导出。因此,只能在模块顶部使用export。
同样,不能在一条语句中使用import,只能在顶部使用它。
导入绑定的一个微妙怪异之处
ES6的import语句为变量、函数利类创建的是只读绑定,而不是正常变量一样简单地引用原始绑定。标识符只有在被导出的模块中可以修改,即便是导入绑定的模块也无法更改绑定的值。例如,假设我们想使用这个模块;
//mod.js
export var name = "yutingbai";
export function setName(newName){
name = newName;
}
当导入这两个绑定后,setNameO函数可以改变name的值:
import {name ,setName} from './mod'
console.log(name); //yutingbai
setName('baoxiaohai')
console.log(name) //baoxiaohai
调用setName(“xxx”)时会回到导出setName()的模块中去执行,并将name设置为“xxx”。请注意,此更改会自动在导入的name绑定上体现。其原因是,name是导出的name标识符的本地名称。本段代码中所使用的name和模块中导入的name不是同一个。
导入和导出时的重命名
导出时指定函数的名称
function fun1(x,x){
...
}
export {fun1 as fun2};
import { fun2 } from './xx'
引入时指定函数的名称
import { fun2 as fun3 } from './xx'
模块中的默认值
由于在诸如CommonJS的其他模块系统中,从模块中导出和导入默认值是一个常见的做法,该语法被进行了优
化。模块的默认值指的是通过default关键字指定的单个变量、函数或类,只能为每个模块设置一个默认的导出值,导出时多次使用default关键字是一个语法错误。
导出
//导出一个函数作为模块的默认值
export default function(){
...
}
//也可以在default之后添加默认导出的模块
function lala(x,x){
...
}
export default lala;
//也可以用重命名的方法为默认导出值指定标识符
function lala(x,x){
...
}
export {lala as default};
引入
import xx from './xx'
无绑定导入
某些模块可能不导出任何东西,相反,它们可能只修改全局作用域中的对象。尽管模块中的顶层变量、函数和类不会自动地出现在全局作用域中,但这并不意味着模块无法访问全局作用域。内建对象(如Array和object)的共享定义可以在模块中访问,对这些对象所做的更改将反映在其他模块中。
加载模块
虽然ECMAScript6定义了模块的语法,但它并没有定义如何加载这些模块。这正是规范复杂性的一个体现,应由不同的实现环境来决定。ECMAScript6没有尝试为所有JavaScript环境创建一套统一的标准,它只规定了语法,并将加载机制抽象到一个未定义的内部方法HostResolveImportedModule中。Web浏览器和Node.js开发者可以通过对各自环境的认知来决定如何实现HostResolveImportedModule。
在web浏览器中使用模块
- 在
<script>
元素中通过src属性指定一个加载代码的地址来加载JavaScript代码文件。 - 将JavaScript代码内嵌到没有src属性的
<script>
元素中。 - 通过Web Worker或Service Worker的方法加载并执行JavaScript代码文件。