ES6之路之模块详解

简介: 一个模块只不过是一个写在文件中的 JavaScript 代码块。模块中的函数或变量不可用,除非模块文件导出它们。简单地说,这些模块可以帮助你在你的模块中编写代码,并且只公开应该被你的代码的其他部分访问的代码部分。

简介

何为模块

一个模块只不过是一个写在文件中的 JavaScript 代码块。

模块中的函数或变量不可用,除非模块文件导出它们。

简单地说,这些模块可以帮助你在你的模块中编写代码,并且只公开应该被你的代码的其他部分访问的代码部分。

为什么要使用模块

  1. 增加可维护性:由于每个模块都是独立的,每个人写的代码是不会相互影响的,在维护代码的时候很好排查是哪个模块出错。
  2. 可复用性:在日常的开发中,特别是大点的项目,代码的可复用性就更重要了,也许你会用复制粘贴的形式,但是直接一个 import 命令就可以搞定,岂不快哉。
  3. 避免命名污染:在 javascript 脚本中,所有的 js 文件的顶级作用域创建的变量,会被添加到共享的全局作用域,这就会导致不同的人开发的代码可能会有相同的变量名,导致变量名污染。

如何使用

导出模块

导出模块所用的命令是 export。

前面也提到一个模块就是一个 javascript 文件,在这个模块中定义的变量,外部是无法获取到的,只有通过 export 导出的变量其他模块才可以用

最简单的导出方式就是在声明的变量、函数、类前面加一个 export

// export1.js 

// 导出变量
export let name = '桃翁';

// 导出函数
export function print() {
    console.log("欢迎关注公众号:前端桃园");
}

// 导出类
export class Person {
    constructor(name) {
        this.name = name;
    }
}
// 私有函数
function privateFunction () {
    console.log('我是私有函数,外部访问不了我');
}
注意
1. 被导出的函数或者类,都必须要有名称,意思就是说不能用这种方式导出匿名函数或者匿名类。
2. privateFunction 函数,没有加 export 命令,被当做这个模块的私有变量,其他模块是访问不到的。

除了上面那种导出方式,还有另外一种

// export2.js

// 导出变量
let name = '桃翁';

// 导出函数
function print() {
    return '欢迎关注公众号:前端桃园';
}

// 导出类
class Person {
    constructor(name) {
        this.name = name;
    }
}

// 私有函数
function privateFunction () {
    return '我是私有函数,外部访问不了我';
}

export { name, print, Person }

上面这种写法导入一组变量,与 export1.js 是等价的。

导入模块

导入的模块可以理解为是生产者(或者服务的提供者),而使用导入的模块的模块就是消费者。

导入模块的命令是 import, import 的基本形式如下:

import { var1, var2 } from './example.js'

import 语句包含两部分:一是导入需要的标识符,二是模块的来源。

注意:浏览器中模块来源要以「/」或者 「./」 或者 「../」开头 或者 url 形式,不然会报错。

例如我们导入 export1.js 模块,可以这么导入

// import1.js
import { name, print, Person } from './export1.js';

console.log(name); // 桃翁

console.log(print()); // 欢迎关注公众号:前端桃园

// 报错, 不能定义相同名字变量
let name = 2333; 

// 报错,不能重新赋值
name = "小猪";

可以看到导入绑定(这里不理解绑定,文章后面会解释)时,形式类似于对象解构,但实际上并无关联。

当导入绑定的时候,绑定类似于使用了 const 定义,意味着不能定义相同的变量名,但是没有暂时性死区特性(但是在 深入理解ES6 这本书里面说是有暂时性死区限制,我在 chrome 上测试了的,读者希望也去试下,到底受不受限制)。

let name = 2333;

上面这行代码会报错。

命名空间导入

这种导入方式是把整个生产者模块当做单一对象导入,所有的导出被当做对象的属性。

// import2.js
import * as namespace from './export1.js'

console.log(namespace.name); // 桃翁

console.log(namespace.print()); // 欢迎关注公众号:前端桃园

重命名导入导出

有时候你并不想导出变量的原名称,需要重新命名,这个时候只需要使用 as 关键字来制定新的名字即可。

重命名导出

// export3.js

function print() {
    return '欢迎关注公众号:前端桃园';
}

export { print as advertising }

导重命名入

拿上面导出的举例子

// import3.js
import { advertising as print } from './export3.js'

console.log(typeof advertising); // "undefined"

console.log(print()); // 欢迎关注公众号:前端桃园 

此代码导入 advertising 函数并重命名为了 print ,这意味着此模块中 advertising 标识符不存在了。

default 关键字

default 关键字是用来做默认导入导出的。

默认导出

// defaultExport.js

// 第一种默认导出方式
export default function print() {
    return '欢迎关注公众号:前端桃园';
}

// 第二种默认导出方式
function print() {
    return '欢迎关注公众号:前端桃园';
}

export default print;

// 第三种默认导出方式
function print() {
    return '欢迎关注公众号:前端桃园';
}

export { print as default }

default 这个关键字在 JS 中具有特殊含义,既可以作为同命名导出,又标明了模块需要使用默认值。

注意: 一个模块中只能有一个默认导出。

默认导入

默认导入和一般的导入不同之处就是不需要写大括号了,看起来更简洁。

把上面 defaultExport.js 模块导出的作为例子

import print from './defaultExport.js'

console.log(print()); // 欢迎关注公众号:前端桃园 

那如果既有默认的又有非默认的怎么导入呢?看例子就明白了

// defaultImport1.js

let name = '桃翁';

function print() {
    return '欢迎关注公众号:前端桃园';
}

export { name, print as default }
// defaultImport2.js

import print, { name } from './defaultImport1.js'

console.log(print()); // 欢迎关注公众号:前端桃园

console.log(name); // 桃翁

混合导入需要把默认导入的名称放在最前面,然后用逗号和后面非默认导出的分割开。

思考了很久是否应该加上进阶内容,本来是想写入门级系列的,但是想了想,还是都写进来吧,入门的看入门前面基础,深入理解的看进阶。

进阶

进阶部分主要介绍 模块的几个特性

  • 静态执行
  • 动态关联
  • 模块不会重复执行

静态执行

所谓静态执行其实就是在编译阶段就需要确定模块的依赖关系,那么就会出现 import 命令会优先于模块其他内容的执行,会提前到编译阶段执行。

// static1.js
console.log('佩奇');

import { nouse } from './static2.js'

// static2.js
export function nouse() {
    return '我是不需要的';
}

console.log('小猪');

可以看到最后输出的应该是「小猪」先输出,而「佩奇」后输出,可以得出虽然 static2.js 在后面引入,但是会被提升到模块的最前面先执行。

这也是我前面所说的不受暂时性死区原因之一,在这里可以写一个例子试试:

// static3.js
console.log(nouse());

import { nouse } from './static2.js'

// 结果:
// 小猪
// 我是不需要的

经检验确实是可以在 import 之前使用导入的绑定。

静态执行还会导致一个问题,那就是不能动态导入模块。

// 报错
if (flag) {
    import { nouse } from './static3.js'
}

// 报错
import { 'no' + 'use' } from './static3.js'

因为 import 是静态执行的,所以在静态(词法)分析阶段,是没法得到表达式或者变量的值的。

但是为了解决这个问题,因为了 import() 这个函数,这个算扩展内容吧,写太多了我怕没人看完了,后面会有扩展阅读链接。

动态关联

所谓的动态关联,其实就是一种绑定关系, 这是 ES6 非常重要的特性,一定仔细阅读。

在 ES6 的模块中,输出的不是对象的拷贝,不管是引用类型还是基本类型, 都是动态关联模块中的值,。

// dynamic1.js
export let name = '桃翁';

export function setName(newName) {
    name = newName;
}

// dynamic2.js
import { name, setName } from './dynamic1.js'

console.log(name); // 桃翁

setName('不要脸');

console.log(name); // 不要脸

奇迹般的发现在 dynamic2.js 模块中可以修改 dynamic1.js 模块里面的值, 并且反应到 name 绑定上(这个是重点,这个反应到了消费者模块), 所以我们把导入的变量叫做绑定。

在生产者模块导出的变量与消费者模块导入的变量会有一个绑定关系,无论前者或者后者发生改变,都会互相影响。

注意区分在一个文件或模块中基本类型的赋值,两者是互不影响的。

模块不会重复执行

这个特性比较好理解,就是如果从一个生产者模块中分别导入绑定,而不是一次性导入,生产者模块不会执行多次。

// noRepeat1.js
export let name = '桃翁';

export let age = '22';

console.log('我正在执行。。。');

// noRepeat2.js
import { name } from './noRepeat1.js';
import { age } from './noRepeat1.js';

console.log(name);
console.log(age);

// 结果
// 我正在执行。。。
// 桃翁
// 22

虽然导入了两次,但是 noRepeat1.js 只有执行一次。若同一个应用(注意是同一个应用不是模块)中导入同一个模块,则那些模块都会使用一个模块实例,意思就是说是一个单例。

后记

码字不易,写技术文章是真的累,作者花的时间至少是读者读的时间的十倍。在此想到阮老师写了那么多文章,不知道是花了多少时间,竟然还有人这么恨他,攻击他的网站。

我在文章中给我公众号打了很多广告,在此抱个歉,刚运营的公众号,需要拉点粉丝,不喜欢的注重内容就好。

拓展

原生ECMAScript模块: 动态 import()

目录
相关文章
|
2月前
|
机器学习/深度学习 自然语言处理 监控
13_命名实体识别:精准提取文本中的关键信息
在当今信息爆炸的时代,人们每天需要处理海量文本数据。如何从这些非结构化文本中高效地提取关键信息,成为了自然语言处理(NLP)领域的核心挑战之一。命名实体识别(Named Entity Recognition,简称NER)技术正是解决这一问题的关键技术,它能够自动识别并分类文本中的人名、地名、组织机构名、时间、日期、金额等具有特定含义的实体。
|
5月前
|
供应链 数据可视化 开发者
供应链可视化工具:穿透全球贸易的迷雾
企业面临三重供应链挑战:多级库存失控、物流黑箱延误、风险传导滞后,导致巨额损失。破局需构建三维透视引擎——库存神经图谱、物流穿透雷达、风险预警熔断器。结合板栗看板、FourKites、Resilinc、Elementum等工具,打造高可视、强响应、韧性强的数字供应链体系,迎接2028年可视化竞争时代。
供应链可视化工具:穿透全球贸易的迷雾
|
11月前
|
容灾 安全 关系型数据库
数据传输服务DTS:敏捷弹性构建企业数据容灾和集成
数据传输服务DTS提供全球覆盖、企业级跨境数据传输和智能化服务,助力企业敏捷构建数据容灾与集成。DTS支持35种数据源,实现全球化数据托管与安全传输,帮助企业快速出海并高效运营。瑶池数据库的全球容灾、多活及集成方案,结合DTS的Serverless和Insight功能,大幅提升数据传输效率与智能管理水平。特邀客户稿定分享了使用DTS加速全球业务布局的成功经验,展示DTS在数据分发、容灾多活等方面的优势。
357 0
|
运维 监控 数据挖掘
交换机镜像之MAC镜像,有哪些分类?
【10月更文挑战第2天】
241 1
交换机镜像之MAC镜像,有哪些分类?
|
JavaScript 前端开发 API
详解队列在前端的应用,深剖JS中的事件循环Eventloop,再了解微任务和宏任务
该文章详细讲解了队列数据结构在前端开发中的应用,并深入探讨了JavaScript的事件循环机制,区分了宏任务和微任务的执行顺序及其对前端性能的影响。
|
8月前
|
前端开发 JavaScript API
2.7K star!这个汉字工具库让中文处理变得超简单,开发者必备!
是一个功能全面的汉字工具库,提供拼音转换、笔画动画、偏旁查询、成语接龙、语音合成等20+种实用功能。支持Web/Node.js/小程序多端运行,仅需简单API调用即可实现复杂中文处理,是教育类应用、输入法开发、游戏设计的瑞士军刀
289 11
|
11月前
|
运维 Linux 数据安全/隐私保护
统信-龙蜥技术认证培训专场
在2024龙蜥大会中,本次分享的主题是龙蜥技术认证培训的相关内容。 1.课前准备 2.课程介绍 3.服务器操作系统 4.蜥基础课程讲解 5.现场考试
332 14
|
存储 关系型数据库 MySQL
为什么MySQL索引结构是B+tree ?
在MySQL中,为了提高检索效率和稳定性,采用了B+树作为索引的数据结构。相比二叉树或B树,B+树的非叶子节点仅存储key和指针,使得每页能容纳更多key,树的层级更浅,检索更快;所有数据集中在叶子节点,形成双向链表,利于区间查询。以16KB页为例,三层B+树可容纳约2190万条数据。
372 1
|
前端开发 JavaScript
Bootstrap5 消息弹窗(Toasts)1
Bootstrap5 弹窗(Toasts) 是一种轻量级通知组件,适用于页面角落或底部显示临时信息。使用 .toast 类创建,包含 .toast-header 和 .toast-body 分别定义标题和内容。默认关闭状态,通过 .show 显示,利用 data-bs-dismiss="toast" 关闭。支持通过 JavaScript 初始化和控制显示。
|
开发者
鸿蒙next版开发:ArkTS组件通用属性(菜单控制)
在HarmonyOS 5.0中,ArkTS引入了灵活的菜单控制属性,支持通过长按、点击或鼠标右键触发弹出式菜单,增强用户交互体验。本文详细介绍了bindMenu和bindContextMenu方法,以及MenuItem的配置属性,并提供了示例代码,帮助开发者更好地理解和使用这些功能。
750 1

热门文章

最新文章