ES6躬行记(7)——代码模块化
在ES6之前,由于ECMAScript不具备模块化管理的能力,因此往往需要借助第三方类库(例如遵守AMD规范的RequireJS或遵循CMD规范的SeaJS等)才能实现模块加载。而自从ES6引入了模块化标准后,就不需要再特地加载一次外部脚本了。模块化的语法不仅让JavaScript代码的组织变得更有条理,还包含封装、按需导出或导入等实用功能,可轻松应对日益复杂和庞大的前端工程。但有一点要注意,模块中的代码默认运行在严格模式中。
一、导出
一个模块就是一个独立的JavaScript文件,如果要读取文件内的变量、函数或类(ES6新增的概念),那么必须先将它们用export关键字导出,因为它们默认都是私有的。导出的方式有多种,下面会依依列举。
1)第一种
将export关键字放在变量、函数等声明之前,常被称为命名导出(named export),如下所示。注意,命名导出的变量或函数都需要有名称,否则会抛出语法错误。
export let name = "strick";
export function getName() {
return "strick";
}
export class people {
getName() {
return "strick";
}
}
以上述代码中的name变量为例,本质上,export导出的是变量本身的引用,也就是为该变量在两个模块之间建立一种关联(即绑定)。如果变量在模块内部实时更新了,那么导出的变量的值也会随之改变。
2)第二种
第二种也叫命名导出,只是形式不同,声明和导出会分成两步,要导出的标识符会用花括号包裹起来,如下代码所示。此时,还能通过as关键字为导出的变量、函数等设置别名。
let age = 28;
function getAge() {
return 28;
}
export { age, getAge };
export { age as myAge, getAge as getMyAge }; //设置别名
3)第三种
第三种用于导出模块的全部或部分成员(例如变量、函数或类等),此时需要包含四部分,分别是导出标识符、模块路径(也叫模块说明符,module specifier)以及两个关键字:export和from。如果要导出全部,那么导出标识符得用星号(*)表示;而如果只要导出部分,那么导出标识符可以像第二种命名导出那么写,具体如下代码所示。注意,模块路径不能简写,需要以“/”、“./”或“../”开头,千万不要因为文件在同级就省略相应的字符。
export * from "./1.js"; //导出全部
export { name, age } from "./1.js"; //导出部分
export { getAge as getMyAge } from "./1.js"; //导出部分并设置别名
上面代码的第二条导出语句,其实可以分解成下面两条语句,第三条也有类似的分解。
import { name, age } from "./1.js";
export { name, age };
二、导入
如果想导入某个模块的成员,可以使用import关键字。它的语法与前面第三种导出方式类似,也包含四个部分,分别是导入标识符、模块路径以及两个关键字:import和from,其中模块路径也不能简写,如下代码所示。
import * as people from "./1.js"; //导入全部
import { name, age } from "./1.js"; //导入部分
import { getAge as getMyAge } from "./1.js"; //导入部分并设置别名
注意上面的第一行代码,使用了命名空间导入(Namespace Import)。与导出模块的全部成员不同,在导入时,除了要与星号组合之外,还必须为其设置别名。这是由于加载的整个模块会被当成一个对象,而此对象需要一个名称,它的属性就是该模块所有的导出。另外两行使用了命名导入(Named Import),花括号内的导入标识符要与模块的导出标识符一一对应。
import语句在内部实现了单例模式,尽管上面代码对同一个模块执行了三次导入,但该模块只会被实例化一次。
1)只读变量
用import导入的变量都是只读的,相当于为它添加了const限制,如果在模块中为其重新赋值,那么必会引起类型错误。想要更新导入的变量的值,有一种间接的实现办法,如下代码所示,先在要导出的1.js模块内定义name变量和setName()函数。
export let name = "strick";
export function setName(str) {
name = str;
}
然后在另一个模块中导入刚刚的两个成员,接着将新的name值通过setName()函数传入到1.js模块内部进行更新,如下代码所示,name变量最终输出的结果正是那个新值。
import { name, setName } from "./1.js";
console.log(name); //"strick"
setName("freedom");
console.log(name); //"freedom"
2)成员提升
从模块中导入的成员默认都会被提升至当前模块作用域的顶部,类似于声明提升。因此,像下面这样的写法都是正确的。
console.log(age);
getAge();
import { age, getAge } from "./1.js";
3)简洁导入
import语句中的导入标识符和from关键字都是可选的,但要注意,只有当两者一起省略时,语句才能被正确执行,如下所示。
import "./jquery.js";
由于import加载的模块都会被执行一次,因此可以用上面这种简洁导入来实现脚本的预加载。而这些脚本既可以是自己封装的代码段,也可以是jQuery、Zepto等第三方类库。
三、模块的默认值
ES6中的default关键字可指定模块的默认值(例如变量、函数或类等),即为模块指定默认的导出和导入。
1)默认导出
一个模块只能存在一个默认导出,下面会列出默认导出的四种写法,为了便于比较,将它们放在了一起。
let name = "strick";
export default name; //写法一
export default function getName() { //写法二
return "strick";
}
export default function() { //写法三
return "strick";
}
export { name as default }; //写法四
export语句中的default其实就是要导出的模块成员,它的名称就叫default,而default后面能够跟一个表达式、命名函数或匿名函数,注意观察上面代码的前三种写法。第四种写法比较特殊,是在命名导出时,将标识符重命名成default。
默认导出可以简单的理解为给default赋值,因此下面的前两条语句都能被正确执行,而第三条语句由于包含了声明变量的关键字(let),所以会引起语法错误。
export default name = "strick";
export default "strick";
export default let name = "strick"; //语法错误
2)默认导入
如果要导入模块的默认值,那么可以像下面这样写,同样,为了便于比较,将它们放在了一起。
import name from "./1.js"; //写法一
import name, { age } from "./1.js"; //写法二
import name, * as people from "./1.js"; //写法三
import { default as myName } from "./1.js"; //写法四
因为模块只能有一个默认导出,所以对应的导入标识符可以不用花括号包裹(注意观察前三种写法),不仅如此,还能像第四种写法那样通过default关键字为其重命名。但有一点要注意,当同时使用默认和非默认的导入标识符时,必须把默认的写在前面。
四、限制
1)模块路径
由于ES6中的模块被设计成了静态的,因此需要在编译阶段就明确模块之间的依赖关系,而不是在运行过程中动态计算,像下面这样将模块路径设为变量或表达式都是错误的写法。
let path = "./1.js";
export * from path; //变量
export * from "./" + "1.js"; //表达式
import * as people from path; //变量
import * as people from "./" + "1.js"; //表达式
2)作用域
export和import语句都是静态的,无法动态导出和导入。因此只能出现在模块的顶层作用域中,而不能出现在块级或函数作用域中,下面的写法都会引起语法错误。
//函数作用域
function getName() {
export * from "./1.js";
import * as people from "./1.js";
}
//块级作用域
if(true) {
export * from "./1.js";
import * as people from "./1.js";
}
3)标识符
导出和导入语句中的标识符如果重复,那么也会引起语法错误,如下所示。
export { name, name } from "./1.js";
import { name, name } from "./1.js";
五、用<script>标签加载模块
在浏览器中,无论是以外部还是内联的方式嵌入模块文件,都需要将它的type属性设为“module”,如下代码所示。并且在加载模块时为了避免脚本阻塞,会自动应用布尔属性defer,即HTML文档的解析和模块文件的下载是同时进行的,待到解析完后才会执行模块。
<script src="1.js" type="module"></script>
<script type="module">
import { name } from "./1.js";
console.log(name);
</script>
<script src="2.js" type="module"></script>
上面代码会被依次执行,先执行第一个外部模块,再执行内联模块,最后执行第二个外部模块。注意,在每个模块中用import导入的其它模块也会被解析和下载,并且同一个模块每次只能被加载一次。
ES6躬行记(7)——代码模块化的更多相关文章
- ES6躬行记(1)——let和const
古语云:“纸上得来终觉浅,绝知此事要躬行”.的确,不管看了多少本书,如果自己不实践,那么就很难领会其中的精髓.自己研读过许多ES6相关的书籍和资料,平时工作中也会用到,但在用到时经常需要上搜索引擎中查 ...
- ES6躬行记 笔记
ES6躬行记(18)--迭代器 要实现以下接口## next() ,return,throw 可以用for-of保证迭代对象的正确性 例如 var str = "向
- ES6躬行记(21)——类的继承
ES6的继承依然是基于原型的继承,但语法更为简洁清晰.通过一个extends关键字,就能描述两个类之间的继承关系(如下代码所示),在此关键字之前的Man是子类(即派生类),而在其之后的People是父 ...
- ES6躬行记(13)——类型化数组
类型化数组(Typed Array)是一种处理二进制数据的特殊数组,它可像C语言那样直接操纵字节,不过得先用ArrayBuffer对象创建数组缓冲区(Array Buffer),再映射到指定格式的视图 ...
- ES6躬行记(3)——解构
解构(destructuring)是一种赋值语法,可从数组中提取元素或从对象中提取属性,将其值赋给对应的变量或另一个对象的属性.解构地目的是简化提取数据的过程,增强代码的可读性.有两种解构语法,分别是 ...
- ES6躬行记(4)——模板字面量
模板字面量(Template Literal)是一种能够嵌入表达式的格式化字符串,有别于普通字符串,它使用反引号(`)包裹字符序列,而不是双引号或单引号.模板字面量包含特定形式的占位符(${expre ...
- ES6躬行记(15)——箭头函数和尾调用优化
一.箭头函数 箭头函数(Arrow Function)是ES6提供的一个很实用的新功能,与普通函数相比,不但在语法上更为简洁,而且在使用时也有更多注意点,下面列出了其中的三点: (1)由于不能作为构造 ...
- ES6躬行记(5)——对象字面量的扩展
一.简洁属性和方法 当创建对象字面量时,如果属性值是与属性同名的已定义的标识符(例如变量.常量等),那么ES6允许省略冒号和属性值,这样就能避免冗余的初始化.下面分别用传统的键值对和最新的简写方式创建 ...
- ES6躬行记(14)——函数
在前面的章节中,已陆陆续续介绍了ES6为改良函数而引入的几个新特性,本章将会继续讲解ES6对函数的其余改进,包括默认参数.元属性.块级函数和箭头函数等. 一.默认参数 在ES5时代,只能在函数体中定义 ...
随机推荐
- 《Linux就该这么学》第十五天课程
本次课所学习的是DNS域名解析服务! 下面提供一些DNS有关的内容 如需进一步学习,请前往https://www.linuxprobe.com/chapter-13.html 工作模式: 1.主服务器 ...
- 破解某普通话测试app会员
设备要求 已root的Android手机 软件要求 反编译工具 jeb.APK改之理(APK IDE) hook工具 frida.xposed. 布局分析工具 Android Device Monit ...
- 解决ORA-30036:无法按8扩展段(在还原表空间‘XXXX’中)
在update一数据量很大的表时,提示“ORA-30036:无法按8扩展段” 度娘了下原因与解决办法: 1.查询了一下undo表空间的使用,发现已经超过了80% SELECT a.tablespa ...
- 在idea中,mavne项目使用mybatis-generator-maven-plugin自动生成实体了的时候,在maven插件里面始终不显示
最近想学习mybatis的知识,自己搭了个简单的ssm框架,想通过插件自动生成实体类,发现想要的插件一直都没显示出来,着实很郁闷: pom.xm中的配置: <!--mybatis-generat ...
- Codeforces Round #485 (Div. 2) F. AND Graph
Codeforces Round #485 (Div. 2) F. AND Graph 题目连接: http://codeforces.com/contest/987/problem/F Descri ...
- 【转】python3 内循环中遍历map,遍历一遍后再次进入内循环,map为空
今天在使用python map的过程中,发现了一个奇怪问题,map遍历完成后,再次访问map,发现map为空了,特记录下来,以备日后查看. 如下代码,期望的结果是每次从外循环进入内循环,map都从头开 ...
- eclipse 带sts插件
https://pan.baidu.com/s/1c1M11ss 密码:ucjl
- access纯jdbc连接
Class.forName("com.hxtt.sql.access.AccessDriver"); String url = "jdbc:Access:///c:/a/ ...
- 安装Nodejs、npm、Less
最近学习到Bookstrap的时候需要用到less这门预处理的CSS语言.其中需要将less文件转换成css文件.记录一下,方便自己以后查找. 安装Nodejs和npm 1:到https://node ...
- L'opzione di luce del puntatore laser
Prima di tutto, sono di buone dimensioni, non i 'mini' puntatori laser che altri stanno vendendo. È ...