草庐IT

再苦再累也必须要弄懂的:ES6的ES Module

upward_tomato 2023-05-11 原文

再苦再累也必须要弄懂的:ES6的ES Module

Introduciton

  • 今天就来讲一讲,ES6 的模块化规范 ES Module。

  • 什么是模块化?

    百度百科解释道:模块化是指解决一个复杂问题时自顶向下逐层把系统划分成若干模块的过程,有多种属性,分别反映其内部特性。

    我的理解:将代码按照 功能,作用,类别等,划分成一个个独立的文件,每个文件可以看做一个模块。

  • ES6 提供的模块化方案叫做 ES Module,简称 esm

  • 早期的 Javascript 是没有模块化的概念,如果想利用 Javascript 构建一个大型项目,就会有很多问题。例如 1.命名冲突;2.变量私有;2.依赖关系的管理等问题。

  • 在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

运行ES Module规范的 js 文件

在讲 ES Module 具体的知识点之前,必须要先讲,如何执行我们的 ES Module 的代码,方便后续我们调试。

运行代码主要两个环境:浏览器和 NodeJs,我在这里分别做一下说明。

浏览器环境

前置逻辑

正常的情况,我们可以在 html 页面中,直接使用 script 标签引入我们的 js 文件,如下述案例所示。

<script src="./main.js"></script>
<!-- <script type="application/javascript" src="./main.js"></script> -->

script 标签有一个 type 属性,默认情况为:“application/javascript”。所以大多数情况都简写了。但是在 ES Module 中,为了告诉浏览器我们是用的 ES Module,需要修改 type属性为 “module”。

如下所示:

<script src="./main.js" type="module"></script>

演示具体代码:

错误的情况:

// main.js
/* 使用了 ES6 的命令 export */
export var a = 1
<body>
  <script src="./main.js"></script>
  /* 不写type属性的情况,会直接报错 */
  // Uncaught SyntaxError: Unexpected token 'export' (at main.js:1:1)
</body>

正确的情况:

<body>
  <script src="./main.js" type="module"></script>
</body><body>
  <script type="module">
    import { a } from './main.js'
    console.log(a) // 1
  </script>
</body>

本地直接打开 带有 <script type="module"> 的 html 页面,会有跨域问题。

  • 解决方式:可以使用 (VSCode的插件:liveServer) 创建本地服务,打开此 html 页面即可。

  • 跨域的原因:我猜测可能是因为 type=“module” 是异步加载所导致的。

NodeJs环境

NodeJS 本身的模块化标准是 CommonJs。如何让 NodeJS 支持运行 ES Module?

前提:NodeJs 版本要求

NodeJs的8.9之后的版本,就开始支持 ES6了 ,但是在 13.2 版本之后才开启 默认支持运行 ES Module。所以使用版本在 8.9~13.2 之间的 NodeJs ,执行 ES Module 的 js文件。需要添加配置项 --experimental-modules,开启 默认关闭的 ES Module 支持。

方式一: .mjs

修改 js 文件后缀 由 .js.mjs

演示:

// main.mjs
/* 后缀名设置为 mjs */
export var a = 1

// CMD 中的输入命令
node ./main.mjs

// 8.9~13.2 之间版本的 NodeJs
node --experimental-modules ./main.mjs

方式二:"type": "module"

配置项目的 package.json 中的 type 属性为 module。

演示:

// package.json
/* type 设置为 module */
"type": "module"


// CMD 中的输入命令
node ./main.js

// 8.9~13.2 之间版本的 NodeJs
node --experimental-modules ./main.js
  • 对标 ES Module,有时候遇到 .cjs 结尾的文件,其实也是js文件,不过它是 CommonJS 规范。

  • package.json 中的 type 设置为 commonjs,可以表示使用 CommonJS 规范。

第一条:严格模式

Es Module 第一个特点:默认开启了严格模式。即便你没有在文件的开头添加 “use strict”;

既然开启了严格模式,严格模式限制有哪些?

我第一反应:this 指向不再是指向 window ,而是 undefined

主要有以下限制:

  1. 变量必须声明后再使用
  2. 函数的参数不能有同名属性,否则报错
  3. 不能使用 with 语句
  4. 不能对只读属性赋值,否则报错
  5. 不能使用前缀 0 表示八进制数,否则报错
  6. 不能删除不可删除的属性,否则报错
  7. 不能删除变量 delete prop,会报错,只能删除属性 delete global[prop]
  8. eval 不会在它的外层作用域引入变量
  9. eval 和 arguments 不能被重新赋值
  10. arguments 不会自动反映函数参数的变化
  11. 不能使用 arguments.callee
  12. 不能使用 arguments.caller
  13. 禁止 this 指向全局对象
  14. 不能使用 fn.caller 和 fn.arguments 获取函数调用的堆栈
  15. 增加了保留字(比如 protected、static 和 interface)

第二条:export

模块中的变量是私有的,外部是无法直接访问的,我们可以通过 export 主动向外输出变量,供外部模块使用。

export 英文释义:导出,输出;(我习惯理解为输出的意思)

!!!export这个单词不要混淆,需要和CommonJS 的 exportsmodule.exports作区分

说个我自己用来记忆理解:CommonJS 的输出,默认都是放在一个对象中输出, 对象存储的信息比较多,所以需要加 s 后缀。

export 的使用案例

1. 普通输出

export var tomato = 'sweet'

export var say = function () {
  console.log('说话')
}

2. 批量输出

var a = 1
var b = 2

export { a, b }
// 这种方式更好,可以很容易知道输出了哪些变量。

3.输出别名

var n = 1
export { n as m }
// 实际上输出的名称为 m。但是取值是取 n 的值。

export 的注意事项

1.错误的导出

// 错误的输出
export 1

// 错误的输出
var num = 1
export num

上面的示例会报错, why ?
正确的导出,实质上是,在接口名与模块内部变量之间,建立了一一对应的关系。
我自己的理解:使用 export 命令输出,必须建立 输出的变量名 和 内部的变量之间的关系,才可以输出

2. export import不允许在块级作用域中使用

if (true) {
  // 报错
  export var a = 1
}

ES6 的 export import 不允许在块级作用域中使用。

第三条:import

说完export,说说 import;既然模块的变量可以导出,那么我们如何引入这些变量?这就需要命令: import

import 英文释义:进口,进口商品;输入,引进 (我习惯理解为输入的意思)

前置条件

为了方便演示 import ,加上我本地使用 node 调试代码比较方便。我先定义一个导出的文件 a.mjs

后续没有明确说明的。默认都是导入下方的 a.mjs ,并且采取 node ./main.mjs 的方式运行示例代码。

export var tomato = 'sweet'

export var say = function () {
  console.log('说话')
}

var a = 1
var b = 2

export { a, b }

import 的使用案例

1.直接引入 a.mjs 中的变量a

import { a } from './a.mjs'
console.log(a) // 1

2.引入 a.mjs 中的变量 b 但是取别名为 newB

import { b as newB } from './a.mjs'
console.log(newB) // 2
// console.log(b) // 直接报错: b is not defined

3.引入 a.mjs 所有的输出到 obj 中

import * as obj from './a.mjs'
obj.say() // 说话
console.log(a,b) // 1 2

4.执行块a.mjs 但不引入变量

import './a.mjs'

import 的注意事项

1. import 的变量是只读的

import { a } from './a.mjs'
console.log(a) // 1

a = 2 // TypeError Assignment to constant variable.

2. import 的变量实际上是对原本模块中变量的引用

// a.mjs
export var num = 1
export function add() {
  num++
}

// main.mjs
import * as obj from './a.mjs'
console.log(obj.num)
// 1
obj.add()

console.log(obj.num)
// 2

/* 这里可以发现,import引入的变量其实是对原本模块变量的一个链接引用,当原模块变量值改变的时候,我们引入的变量的值也会跟着改变 */

3. 和 export 同理, import 语句不允许放在块级作用域中使用,会直接报错;

4. 由于ES Module是静态编译,所以 import会被提升到最顶部执行 ;

// a.mjs
console.log('a.mjs开始执行啦')
export var num = 1


// main.mjs
console.log('main.js开始执行了')

import * as obj from './a.mjs'
console.log(obj.num)

console.log('main.js结束执行了')

// a.mjs开始执行啦
// main.js开始执行了
// 1
// main.js结束执行了

/* 可以看到,优先执行了import * as obj from './a.mjs', 随即执行了 a.mjs*/

5. import 的执行逻辑 优先深度遍历,先子后父;

// a.mjs
console.log('a开始执行啦')
import { say } from './b.mjs'
import { edit } from './c.mjs'
export var a = 1
console.log('a结束了')

// b.mjs
console.log('b开始执行啦')
export function say() {
  console.log('开始说话')
}
import { a } from './a.mjs'
console.log(a)
console.log('b结束了')

// c.mjs
console.log('c开始执行啦')
export function edit() {
  console.log('开始编辑')
}
console.log('c结束了')

// b开始执行啦
// undefined
// b结束了
// c开始执行啦
// c结束了
// a开始执行啦
// a结束了

6. 多次重复执行同一句 import 语句,只会执行一次;

// a.mjs
console.log('执行文件a')
export var tomato = 'sweet'

// main.mjs
import './a.mjs'
import './a.mjs'
// 执行文件a

/* 不管我引入了多少次 a.mjs ,只会打印一次 执行文件a */

第四条:默认导出 和 默认引入

上面的实例,通过 import 和 export 就可以顺利的 输出和输入变量。

但是还有一个问题,对于使用者来说,拿到一个模块,有些时候我们并不知道,这个模块输出了什么。此时 ES6 提供了默认的导出和默认的引入来解决这种困扰。

默认导出和默认输出 的使用案例

// a.mjs
export default 1

// main.mjs
import a from './a.mjs'
console.log(a)
// 1

默认导出和默认输出 的注意事项

1. export default 和 export的区别

/* 默认导出和默认输出 */
// a.mjs
export default 1

// main.mjs
import a from './a.mjs'
console.log(a)
// 1


/* 普通的写法 */
// a.mjs
export var a = 1

// main.mjs
import { a } from './a.mjs'
console.log(a)
// 1

为什么 export default 不使用 var 声明一个变量导出呢?

原因是 export default 可以看做就是输出一个叫做 default 的变量或方法,然后系统允许你为它取任意名字。

下面看示例:2. export default 的本质

2. export default 的本质

/* export default */
// a.mjs
export default 1

// main.mjs
import a from './a.mjs'
console.log(a)
// 1

/* 模拟 default*/
// a.mjs
var a = 1
export {a as default}


import a  from './a.mjs'
console.log(a)
// 1

可以看到上述代码 ,使用 export {a as default} ,效果是和 export default 是相同的。

3. 又想引入部分变量,又想引入默认的导出?

// a.mjs
export default function () {
  console.log('你好呀')
}
export var a = 1
export var b = 2


// main.mjs
import xx, { a, b } from './a.mjs'
xx() // 你好呀
console.log(a, b) // 1 2

/* 需要注意:这里的xx可以任意替换 */ 

第五条:import()

由于 ES Module 是静态编译,提前异步加载,所以不能在块级作用域中使用 import export;

但是有这么几个问题:

  • 一方面 CommonJS 的 require 是运行时加载(ES Module 若要支持 CommonJS,这个运行时就需要支持)。

  • 一方面,有时候确实希望根据代码逻辑,去引入文件。

ES2022 引入了 import(),可以叫做 动态 import;

import() 的使用

// a.mjs
export default function () {
  console.log('你好呀')
}

export var a = 1

// main.js
console.log('main.js开始执行了')
setTimeout(() => {
  import('./a.mjs')
    .then((res) => {
      console.dir(res)
    })
    .catch((err) => {
      console.log(err)
    })
}, 200)

// main.js开始执行了
// [Module: null prototype]   { a: 1, default: [Function: default] }

import() 的注意事项

1. import() 函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。

2. import() 函数返回值是一个 Promise 对象;

3. import() 适用场景

  • 条件加载

    if (xxx) {
     import('./xxx')
    }
    
  • 路由懒加载

    const UserDetails = () => import('./views/UserDetails.vue')
    
  • 按需加载

    xxx.onClick = function () {
      import('./xxx')
    }
    

<script> 加载文件的顺序

前面有说到 <script> 标签,这个章节说一下 <script> 加载文件的顺序 。

1. 默认情况

默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到<script>标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。

2. 异步加载

<script src="xxx.js" defer></script>
<script src="xxx.js" async></script>

添加属性,defer 或者 async,都会开启异步加载

deferasync的区别是:

  • defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;
  • async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。

一句话,defer是“渲染完再执行”,async是“下载完就执行”。

另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。

3. <script type="module" src="./foo.js"></script> 加载文件的逻辑

有上述的解释可以得到如下结论:

<script type="module" src="./foo.js"></script>
<!-- 等同于 -->
<script type="module" src="./foo.js" defer></script>

所以 <script type="module" src="./foo.js"></script> 可以理解为,异步加载 ./foo.js 文件,等页面渲染完毕之后再开始执行我们的脚本。

当然也可以给<script>标签加上 async 标志,使它一旦下载完,就中断渲染,执行这个脚本。

<!-- 例如: -->
<script type="module" src="./foo.js" async></script>

总结

总结一下学到的知识。

  1. ES Module 是 ES6提供的模块化标准;

  2. ES Module 基础知识主要包含这几点:

    • 默认开启严格模式;
    • import
    • export
    • default export

参考文章

end

  • 本文主要记录了我学习到的 ES Module,若有错误,欢迎指出不胜感激。
  • 本文还缺少了 ES Module 和我们常用的 CommonJS 的差异比较。我打算学习完 CommonJS 之后,再对比一下两者的差异。
  • 加油!!!

有关再苦再累也必须要弄懂的:ES6的ES Module的更多相关文章

  1. 使用canal同步MySQL数据到ES - 2

    文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co

  2. ES基础入门 - 2

    ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear

  3. 关于ES集群信息的一些查看 - 2

    文章目录查看ES信息查看节点信息查看分片信息实际场景下ES分片及副本数量应该怎么分关于ES的灵活使用查看ES信息查看版本kibana:GET/查看节点信息GET/_cat/nodes?v解释:ip:集群中节点的ip地址;heap.percent:堆内存的占用百分比;ram.percent:总内存的占用百分比,其实这个不是很准确,因为buff/cache和available也被当作使用内存;cpu:cpu占用百分比;load_1m:1分钟内cpu负载;load_5m:5分钟内cpu负载;load_15m:15分钟内cpu负载;node.role:上图的dilmrt代表全部权限master:*代表

  4. linux查看es节点使用情况,elasticsearch(es) 如何查看当前集群中哪个节点是主节点(master) - 2

    elasticsearch查看当前集群中的master节点是哪个需要使用_cat监控命令,具体如下。查看方法es主节点确定命令,以kibana上查看示例如下:GET_cat/nodesv返回结果示例如下:ipheap.percentram.percentcpuload_1mload_5mload_15mnode.rolemastername172.16.16.188529952.591.701.45mdi-elastic3172.16.16.187329950.990.991.19mdi-elastic2172.16.16.231699940.871.001.03mdi-elastic4172

  5. 五-1、elasticsearch集群搭建(ES集群搭建) - 2

    目录一、下载Elasticsearch1.选择你要下载的Elasticsearch版本二、采用通用搭建集群的方法三、配置三台es1.上传压缩包到任意一台虚拟机中2.解压并修改配置文件(配置单台es)3.配置三台es集群4.设置后台启动和开机自启(可选)一、下载Elasticsearch1.选择你要下载的Elasticsearch版本es下载地址这里我下载的是二、采用通用搭建集群的方法集群搭建方法三、配置三台es1.上传压缩包到任意一台虚拟机中上传方式有两种第一种:使用xftp上传直接拖动过去就可以了。第二种:使用lrzsz先安装yum-yinstalllrzsz切换到要上传的位置cd/opt/

  6. windows安装es、kibana教程 - 2

    目录前言第一个部分:安装ES的包1.安装成功的截图2.下载es的安装包3.检查本地的jdk的安装是否存在问题4.修改config文件夹下面的配置第二部分:windows安装Kibana可视化工具1.下载安装包2.安装过程中遇到的问题3.安装6.0.0的版本是可以的4.安装后的效果第三部分:安装Elasticsearch-Head进行搜索本地es环境内的所有数据1.下载git项目文件:GitHub-mobz/elasticsearch-head:Awebfrontendforanelasticsearchcluster2.关于kibana不能监控es环境内数据的问题3.重启es的bat文件,使用

  7. 优化大数据量查询方案——SpringBoot(Cloud)整合ES - 2

    一、Elasticsearch简介实际业务场景中,多端的查询功能都有很大的优化空间。常见的处理方式有:建索引、建物化视图简化查询逻辑、DB层之上建立缓存、分页…然而随着业务数据量的不断增多,总有那么一张表或一个业务,是无法通过常规的处理方式来缩短查询时间的。在查询功能优化上,作为开发人员应该站在公司的角度,本着优化客户体验的目的去寻找解决方案。本人有幸做过Tomcat整合solr,今天一起研究一下当前比较火热的Elasticsearch搜索引擎。Elasticsearch是一个非常强大的搜索引擎。它目前被广泛地使用于各个IT公司。Elasticsearch是由Elastic公司创建。它的代码位

  8. ES条件查询 - 2

    matchAll分页查询@TestpublicvoidtestMatchAll()throwsIOException{//创建查询请求对象SearchRequestsearchRequest=newSearchRequest("goods");//构建查询条件(分页,查询所有)SearchSourceBuildersearchSourceBuilder=newSearchSourceBuilder();searchSourceBuilder.query(QueryBuilders.matchAllQuery());//searchSourceBuilder.from(0);searchSour

  9. es-3-创建索引&新建mapping - 2

    通过elasticsearch-head新建索引新建完的索引,重点要设置setting和mapping两个参数,后面详细展开介绍。索引命名要求索引命名只能使用小写字母不能包含除-_以外的特殊字符不能用-_开头长度必须小于255B索引别名给一个索引起多个别名给多个索引起一个别名(更有意义,为了不让一个索引的容量过于大,可以每隔一段时间把新增数据新建一个索引,然后命名同一个别名)。_mappingmapping相当于数据库中的schema的定义,作用如下:定义索引中的字段名称定义字段的数据类型,如字符串,数字,布尔…字段、倒排索引的相关配置(分不分词,分词器的选择等)mapping会把JSON文档

  10. 【微服务】ES使用实战·黑马旅游(五) - 2

    🚗Es学习·第五站~🚩Es学习起始站:【微服务】Elasticsearch概述&环境搭建(一)🚩本文已收录至专栏:微服务探索之旅👍希望您能有所收获一.引入综合前几站所学,我们已经对Elasticsearch的使用有了一定的了解,接下来让我们一起通过一个综合实战案例来复习前几站所学内容,体会在实际生产中的作用。我们一起实现如下功能:酒店搜索和分页酒店结果过滤我周边的酒店酒店竞价排名数据聚合筛选选项搜索框自动补全酒店数据的同步二.环境搭建按照第一站的学习部署Elasticsearch并启动运行。按照第二站的学习中的如下步骤,初始化测试项目并在Es导入数据。使用Elasticsearch,肯定离不开

随机推荐