简述JavaScript模块化编程(二)

前置阅读:简述JavaScript模块化(一)

在前面一文中,我们对前端模块化所经历的三个阶段进行了了解:

  1. CommonJs,由于是同步的,所以主要应用于服务器端,以Node.js为代表。
  2. AMD,异步模块定义,预加载,推荐依赖前置。以require.js为代表。
  3. CMD,通用模块加载,懒加载,推荐依赖就近。以Sea.js为代表。

而在ES6已经大行其道的今天,ES6中所提供的模块化的方法也自然而然成了我们进行JavaScript模块化编程的标准,因此ES6模块的语法虽然在一些较为老的浏览器上不能直出,需要进行转译,但却代表着未来的JavaScript发展趋势。

ES6模块特性

在ES6中将模块认为是自动运行在严格模式下并且没有办法退出运行的JavaScript代码。在一个模块中定义的变量不会自动被添加到全局共享的作用域之中,这个变量只能作用在这个作用域中。此外模块还必须导出一些外部文件可以访问的元素,以供其他模块或代码使用。

除了这个基本特性,ES6模块还有两大特性也十分重要,需要额外注意:

  • 首先是在模块的顶部this值是undefined,这是由于在ES6中的模块的代码是在严格模式下执行的。(如果对this不是很熟悉的可以去看我的这篇文章:深入浅出this关键字
  • 其次,模块不支持HTML风格的代码注释,这是早期浏览器所遗留下的JavaScript特性,在ES6的语法里不予支持。

基本用法-模块加载

首先我们来看浏览器是如何加载模块的。其实在ES6规范出来之前,web浏览器就规定了三种方式来引入JavaScript文件:

  • 在没有src属性的<script>元素中直接内嵌JavaScript代码
  • 在<script>元素中通过src属性指定一个地址来加载JavaScript代码文件
  • 通过Web Worker或Service Worker的方法加载并执行JavaScript代码

而在浏览器中,默认的行为就是将JavaScript作为脚本来进行加载,而非模块。所以我们要告诉浏览器我们加载的是模块,方法就是在<script>元素中,将type属性指定为"module"。具体看下面的示例:

1 // 第一种方式
2 <script type=""module>
3     import { add } from "./example";
4     let num = add(1, 1);
5 </script>
6 //  第二种方式
7 <script type="module" src="example.js">
8 // 第三种方式,以脚本的方式加载example.js
9 let worker = new Worker("example.js");

当HTML解析器遇到<script>元素的type="module"的时候,模块文件就开始下载,直到文件被完全解析完成才会去执行模块内的代码。模块文件是按照他们出现在HTML文件中顺序执行的,也就是说无论用何种方式引入模块,第一个<script type=""module>总是在第二个<script type=""module>之前执行。

基本用法-导出

在ES6中我们可以使用export关键字将一部分代码暴露给其他模块,以供其他模块或代码使用。先让我们来看看export关键字在MDN的定义吧:

export语句用于在创建JavaScript模块时,从模块中导出函数、对象或原始值,以便其他程序可以通过 import 语句使用它们。(
此特性目前仅在 Safari 和 Chrome 原生实现。它在许多转换器中实现,如Traceur Compiler,Babel或Rollup。)

通过MDN的定义我们可以知道:export关键字可以将其放在任何函数、对象或原始值前面,从而将它们从模块中导出。示例如下:

 1 //   ./example.js
 2 // 导出变量
 3 export var a = 1;
 4 // 导出函数
 5 export function addA(value) {
 6     return value + a;
 7 }
 8 //导出类
 9 export class add1 {
10     constructor(value) {
11         this.value = value + a;
12     }
13 }
14 //这个函数就是这个模块所私有的,在外部不能访问它
15 function say1() {
16     console.log(‘我是不是很帅‘);
17 }
18 //这又是个函数
19 function say2() {
20     console.log(‘没错我就是很帅‘);
21 }
22 //在后面对函数进行导出,它就不是私有的了
23 export say2;

需要注意的是:使用export导出的函数和类都需要一个名称,除非使用default关键字,否则就不能用这个方法导出匿名函数或类。所以当我们需要导出匿名的函数或者类时,我们可以这么做:

 1 //   ./example.js
 2 //导出匿名函数
 3 export default function(a, b) {
 4     return a + b;
 5 }
 6 //或者导出匿名的类
 7 export default class {
 8 consturctor(value) {
 9     this.value = value + 1;
10     }
11 }

具体关于default关键字的用法我会在后面做具体介绍,现在只需记住:当我们需要导出匿名的函数或者类时要使用export default语法。

基本语法-导入

在ES6中,从模块中导入的功能可以通过import关键字。import语句由两部分组成:要导入元素的标识符和元素应当从哪个模块导入。

1 //  ./say.js
2 import { say2 } from "./example.js";
3 console.log(say2()); // ‘没错我就是很帅‘

import 后面的大括号中的say2表示从规定模块导入的元素的名称。关键字from后面的字符串则表示要导入的模块的路径,这通常是包含模块的.js文件的相对或绝对路径名,需要注意的是只允许使用单引号和双引号的字符串来包裹路径,浏览器使用的路径格式与传给<script>元素的相同,所以必须把文件的扩展名也加上。

(注:由于Node.js遵循基于文件系统前缀以区分本地文件个包的惯例,即example是一个包,而./exampple.js是一个本地文件。为了更好的兼容多个浏览器Node.js环境,我们一定要在路径前包含./或../来表示要导入的文件。)

除此之外,我们还可以导入多个元素或者直接导入整个模块:

 1 // 导入多个元素
 2 improt { a, addA, say2 } from "./example.js";
 3 console.log(a); // 1
 4 sonsole.log(addA(1); // 2
 5
 6 // 导入整个模块
 7 import * as example from "./example.js"
 8 console.log(example.a); // 1
 9 sonsole.log(example.addA(1); // 2
10 console.log(example.say2()); // ‘没错我就是很帅‘

上面的导入整个模块就是把example.js中导出的所有元素全部加载到一个叫做example的对象中,而所导出的元素就会作为example的属性被访问。因为example对象是作为example.js中所导出成员的命名空间对象而被创建的,所以这种导入方式被称为命名空间导入(name space import)。

还有一点要注意的是,不管import语句把一个模块写了多少次,该模块只执行一次。意思就是,在首次执行导入模块后,实例化的模块就会被保存在内存中,只要使用import语句引用它就可以重复使用它:

1 // 首次导入需要加载模块example.js
2 import { a } from "./example.js"
3 // 下面的两个import将无需加载example.js了
4 import { addA } from "./example.js"
5 import { say2 } from "./example.js"

当从模块中导入一个元素时,它与const是一样无法定义另一个同名变量和导入一个同名元素,也无法在import语句前使用元素或者改变导出的元素的值:

1 //接上面的代码
2
3 say2 = 1 ;  //会抛出一个错误

这是由于ES6的import语句为导入的元素创建的是只读绑定的标识符,而不是原始绑定。因此元素只有在被导出的模块中才可以被修改,即使是将该模块的全部导入也无法修改其中的元素。

 1 //   ./example.js
 2 // 这是一个函数
 3 export function setA(newA) {
 4     a = newA;
 5 }
 6 //  ./say.js
 7 import { a, setA } from "./example";
 8 console.log(a);  // 1
 9 a = 2;   //抛出错误
10
11 // 所以我们得这么做
12 setA(2);
13 console.log(a);  // 2

调用setA(2)时会返回到example.js中去执行,将a设置为2。由于say.js导入的只是a的只读绑定的标识符而已,因此会自动进行更改。

其他基本语法

1.语法限制

export和import在语法上还有一个重要的限制,那就是他们必须在条件语句和函数之外使用,例如:

1 if (ture) {
2     export var a = 1;      //语法错误
3 }
4 function imp() {
5     import a from "./example.js"; //语法错误
6 }

由于模块语法存在的其中一个原因是让JavaScript引擎可以静态地确定哪些代码是可以导出的,因此export和import语句被设计成静态的,不能进行任何形式的动态导出或导入。

2.重命名解决

有时在开发中,我们在导入一些元素后不想使用它们的原始名称了,我们就可以在导出过程或者导入过程中去改变导出元素的名称:

 1 // 导出过程
 2 function add(a, b) {
 3     return a + b;
 4 }
 5 export { add as add1 };  //在导入过程中必须使用add1作为名称
 6
 7 // 导入过程
 8 import {add as add1 } from "./example"
 9 console.log(add1(1,1));  // 2
10 console.log(typeof add); //undefined

3.模块的默认值

在CommonJS等其他的模块化规范中,从模块中导出或导入默认值是一个常见的用法,因此在ES6中也延用了这种用法并进行了优化。在ES6中我们可以使用default关键字来指定默认值,并且一个模块只能默认一个导出值:

 1 // ./example.js
 2 // 第一种默认导出语法
 3 export default function(a, b) {
 4     return a + b;
 5 }
 6 // 第二种默认导出语法
 7 function add(a, b) {
 8     return a + b;
 9 }
10 export default add;
11 // 第三种默认导出语法
12 function add(a, b) {
13     return a + b;
14 }
15 export { add as default };

需要注意的是第三种语法,default关键字虽然不能作为元素的名称,但可以作为元素的属性名称,因此可以使用as语法将add函数的属性设置为default。

导入默认值的语法则是这样的:

1 //  第一种语法
2 import add from "./example";
3 //  第二种语法
4 import { default as add } from "./example";

看到这里有些朋友可能会发现,我们的第一种语法中import关键字后面并没有加大括号,认为这是错误的。其实这是导入默认值的独特语法,在这的本地名称add用于表示模块导出的任何默认函数,这种语法是最纯净的,ES6标准创建团队的大佬们也希望这种语法能成为web主流的模块导入形式。

我们前面说的导入匿名函数也同样使用这种语法:

1 //   ./example.js
2 //导出匿名函数
3 export default function(a, b) {
4     return a + b;
5 }
6 // ./say.js
7 import add from "./example";
8 console.log(add(1,1));  // 2

在这里本地名称add就是用于表示上面的匿名函数的。

4.导出已导入的元素

我们同样可以在本模块内导出我们在本模块内导入的元素,有以下几种语法:

 1 //  第一种语法
 2 import { add } from ./example.js;
 3 export { add };
 4
 5 //  第二种语法
 6 export { add } from ./example.js;
 7
 8 //换一个名称导出
 9 export { add as add1 } from ./example.js; //以add这个名称导入,再以add1的名称导出
10
11 // 导出整个模块
12 export *  from ./example.js;

// 最后预祝自己8月份面试成功offer满天飞-_-||

原文地址:https://www.cnblogs.com/mingdao/p/11290240.html

时间: 2024-11-09 06:04:51

简述JavaScript模块化编程(二)的相关文章

Javascript模块化编程(二)AMD规范(规范使用模块)

这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块,先想一想,为什么模块很重要?接下来为您详细介绍,感兴趣的朋友可以了解下啊.今天介绍如何规范地使用模块. 七.模块的规范 先想一想,为什么模块很重要? 因为有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块. 但是,这样做有一个前提,那就是大家必须以同样的方式编写模块,否则你有你的写法,我有我的写法,岂不是乱了套!考虑到Javascript模块现在还没有官方规范,这一点就更重要了. 目前,

JavaScript 模块化编程(笔记)

第一章 JavaScript模块化编程 (一):模块的写法 一 原始写法// 模块就是实现特定功能的一组方法;只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块;    function m1(){        // ...    }    function m2(){        // ...    }// 上面的函数m1()和m2(),组成一个模块;使用时直接调用就行;// 缺点:"污染"了全局变量; 无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出

JavaScript模块化编程(笔记)

一直对JS都是一知半解,最近遇到这方面问题,所以在网上学习了一下,现在还没有完全明白,先贴出笔记; 1 第一章 JavaScript模块化编程(一):模块的写法 2 3 一 原始写法 4 // 模块就是实现特定功能的一组方法;只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块; 5 function m1(){ 6 // ... 7 } 8 function m2(){ 9 // ... 10 } 11 // 上面的函数m1()和m2(),组成一个模块;使用时直接调用就行; 1

Javascript模块化编程(二):AMD规范

作者: 阮一峰 日期: 2012年10月30日 这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块. (接上文) 七.模块的规范 先想一想,为什么模块很重要? 因为有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块. 但是,这样做有一个前提,那就是大家必须以同样的方式编写模块,否则你有你的写法,我有我的写法,岂不是乱了套!考虑到Javascript模块现在还没有官方规范,这一点就更重要了. 目前,通行的Javascript模块规范共有两种

Javascript模块化编程(二):AMD规范(转)

这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块. (接上文) 七.模块的规范 先想一想,为什么模块很重要? 因为有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块. 但是,这样做有一个前提,那就是大家必须以同样的方式编写模块,否则你有你的写法,我有我的写法,岂不是乱了套!考虑到Javascript模块现在还没有官方规范,这一点就更重要了. 目前,通行的Javascript模块规范共有两种:CommonJS和AMD.我主要介绍AMD,但

Javascript模块化编程(二):AMD规范 作者: 阮一峰

声明:转载自阮一峰的网络日志 这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块. (接上文) 七.模块的规范 先想一想,为什么模块很重要? 因为有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块. 但是,这样做有一个前提,那就是大家必须以同样的方式编写模块,否则你有你的写法,我有我的写法,岂不是乱了套!考虑到Javascript模块现在还没有官方规范,这一点就更重要了. 目前,通行的Javascript模块规范共有两种:CommonJS

Javascript模块化编程(二):AMD规范(转)

这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块. (接上文) 七.模块的规范 先想一想,为什么模块很重要? 因为有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块. 但是,这样做有一个前提,那就是大家必须以同样的方式编写模块,否则你有你的写法,我有我的写法,岂不是乱了套!考虑到Javascript模块现在还没有官方规范,这一点就更重要了. 目前,通行的Javascript模块规范共有两种:CommonJS和AMD.我主要介绍AMD,但

Javascript模块化编程(一):模块的写法

作者: 阮一峰 日期: 2012年10月26日 随着网站逐渐变成"互联网应用程序",嵌入网页的Javascript代码越来越庞大,越来越复杂. 网页越来越像桌面程序,需要一个团队分工协作.进度管理.单元测试等等......开发者不得不使用软件工程的方法,管理网页的业务逻辑. Javascript模块化编程,已经成为一个迫切的需求.理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块. 但是,Javascript不是一种模块化编程语言,它不支持"类&qu

Javascript模块化编程

(一):模块的写法 网页越来越像桌面程序,需要一个团队分工协作.进度管理.单元测试等等......开发者不得不使用软件工程的方法,管理网页的业务逻辑. Javascript模块化编程,已经成为一个迫切的需求.理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块. 但是,Javascript不是一种模块化编程语言,它不支持"类"(class),更遑论"模块"(module)了.(正在制定中的ECMAScript标准第六版,将正式支持"