es6躬行记——代码模块化(代码片段)

strick strick     2023-03-04     163

关键词:

  在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躬行记——数字(代码片段)

一、进制  ES6不仅完善了数字的八进制形式,还补充了一种十六进制形式,并且添加了全新的二进制形式。下面的三个变量分别表示八进制、十六进制和二进制的10,注释中给出了该进制的另一种写法。varoctal=0o12,//或0O12hex=0xa,... 查看详情

es6躬行记(16)——set(代码片段)

  ES6引入了两种新的数据结构:Set和Map。Set是一组值的集合,其中值不能重复;Map(也叫字典)是一组键值对的集合,其中键不能重复。Set和Map都由哈希表(HashTable)实现,并可按添加时候的顺序枚举。一、Set  Set类似于Arr... 查看详情

es6躬行记——扩展运算符和剩余参数(代码片段)

  扩展运算符(SpreadOperator)和剩余参数(RestParameter)的写法相同,都是在变量或字面量之前加三个点(...),并且只能用于包含Symbol.iterator属性的可迭代对象(iterable)。虽然两者之间有诸多类似,但它们的功能和应用场景... 查看详情

react躬行记(16)——react源码分析(代码片段)

  React可大致分为三部分:Core、Reconciler和Renderer,在阅读源码之前,首先需要搭建测试环境,为了方便起见,本文直接采用了网友搭建好的环境,React版本是16.8.6,与最新版本很接近。一、目录结构  React采用了由Lerna维护mon... 查看详情

typescript躬行记——接口(代码片段)

  在传统的面向对象语言中,接口(Interface)好比协议,它会列出一系列的规则(即对行为进行抽象),再由类来实现这些规则。而TypeScript中的接口更加灵活,除了包含常规的作用之外,它还能扩展其它的类、为对象的类型... 查看详情

es6-类(class)(代码片段)

ES6躬行记(20)——类  ES6正式将类(Class)的概念在语法层面标准化,今后不必再用构造函数模拟类的行为。而ES6引入的类本质上只是个语法糖(即代码更为简洁、语义更为清晰),其大部分功能(例如继承、封装和复... 查看详情

vue躬行记——指令(代码片段)

  Vue不仅内置了各类指令,包括条件渲染、事件处理等,还能注册自定义指令。一、条件渲染  条件渲染的指令包括v-if、v-else、v-else-if和v-show。1)v-if  该指令的功能和条件语句中的if类似,可根据表达式的计算结果,判... 查看详情

vue躬行记——样式和表单(代码片段)

  Vue对DOM元素的class和style两个特性做了专门的增强,即对CSS类和内联样式做了一层封装,通过v-bind指令来处理它们,而接收的表达式既可以是简单的字符串、对象或数组,也可以是复杂的计算属性。不仅如此,Vue还为表单设... 查看详情

css躬行记——合成

  在图形编辑软件中,可以按特定地方式处理不同图层的合成,最新的CSS规范也引入了该功能,并提供了mix-blend-mode和background-blend-mode两个属性。混合模式(blendingmode)是一种数学算法,可计算元素重叠部分的颜色值,目前已... 查看详情

css躬行记——css基础拾遗

一、box-decoration-break  CSS3新增的box-decoration-break属性可指定行内非替换元素在跨行、跨列或跨页时的样式渲染,它包含两个值:  (1)slice:默认值,盒子会被分割成多部分。  (2)clone:断开的各个盒子会单独渲染。 ... 查看详情

css躬行记——伪类和伪元素

一、伪类选择器  伪选择器弥补了常规选择器的不足,能够实现一些特殊情况下的样式,例如在鼠标悬停时或只给字符串中的第一个字符指定样式。与类选择器类似,可以从HTML元素的class属性中查看到,但伪选择器不会出现在... 查看详情

es6模块exportimport(代码片段)

概述在ES6前,实现模块化使用的是RequireJS或者seaJS(分别是基于AMD规范的模块化库, 和基于CMD规范的模块化库)。ES6引入了模块化,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。ES6的模块化分为... 查看详情

es6模块(代码片段)

ES6引入了原生的模块化机制,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。ES6使用export和import来导出和导入模块。ES6的模块自动开启严格模式。export导出模块a.js//导出数摒exportletmyName="T... 查看详情

es6模块(代码片段)

ES6引入了原生的模块化机制,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。ES6使用export和import来导出和导入模块。ES6的模块自动开启严格模式。export导出模块a.js//导出数摒exportletmyName="T... 查看详情

es6模块化(代码片段)

在es6出来之前,javascript还不支持模块化,想要实现模块化,只能用requrie.js(国外)和seajs(国内)之类相关的库。随着大前端的工作越来越繁杂,系统越来越庞大,更好的分工使其模块化就显得很重要。在复习之前有一个很重... 查看详情

es6模块化(代码片段)

目录1、默认导出/导入1.1、默认导出1.2、默认导入2、按需导出/导入2.1、按需导出2.2、按需导入3、直接运行模块中的代码3.1、需要运行的代码文件3.2、运行代码的文件1、默认导出/导入1.1、默认导出letn1=10;letn2=20;functionshow();... 查看详情

es6模块化-babel转换es6(代码片段)

模块化-babel转换ES6<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>模块化-babel转换ES6</title></head><body><scriptsrc="src/js/app.js"type="modul 查看详情

es6模块化规范(代码片段)

ES6模块化规范ES6在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口import命令用于输入... 查看详情