Node初级与进阶开发类内容 (原文)
(本文章主要内容包括 Node初级的使用 Node高级部分 如有Bug或者写错字体的话请指出来 谢谢 未经过本人允许禁止转载! 禁止转载! 禁止转载!)
Node.js 概述
Node.js简介
Node.js官网对Node.js的定义为——Node.js是一个基于Google Chrome浏览器V8 JavaScript引擎的JavaScript运行环境(正常运行所必需的环境【条件】),主要应用于创建web服务器,但其实际应用不仅限于此。(Node.js is built on top of the Google Chrome V8 JavaScript engine, and it's mainly used to create web servers - but it's not limited to just that.)
官网地址 :nodejs.org/zh-cn
Node.js的基础使用
Node.js 初体验:
在集成终端中输入node JavaScript文件名,即使用node对JavaScript文件中的代码使用其内置的V8引擎进行解析并执行
Node.js的全局对象:
在 Node.js 环境中是没有 window 的,所以 window 对象自然是未定义的。 在 Node.js 环境中全局对象为 global,在 global 对象中会存在一些和 window 对象中名字相同且作用相同的方法,如:
在 Node.js 环境中声明的变量不会被添加到全局对象中,变量声明后只能在当前文件中使用
浏览器与Node.js都可以运行javascript的原因
因为浏览器(如Chrome)与Node.js中都内置了V8 JavaScript解析引擎,它可以将 JavaScript 代码编译为计算机能够识别的机器码
浏览器与Node.js中运行与javascript的区别
浏览器是 JavaScript 的前端运行环境,且Chrome浏览器在内置了 V8引擎以后实际上只能执行 ECMAScript,就是语言中的语法部分。为了能够让 JavaScript 操作浏览器窗口以及 HTML 文档,所以在 V8引擎中添加了控制DOM、BOM及Ajax等api. 所以 JavaScript 在浏览器中运行时是可以完成对应的界面交互行为
和浏览器不同,在 Node.js 中是没有 DOM 和 BOM 的,所以在 Node.js 中不能执行和它们相关的代码,比如 winow.alert() 或者 document.getElementById() . DOM 和 BOM 等是浏览器环境中特有的。在 Node.js 中,作者向其中添加了很多系统级别的 API,比如对操作系统中的文件和文件夹进行操作。获取操作系统信息,比如系统内存总量是多少,系统临时目录在哪,对系统的进程进行操作等等
Node内置模块介绍:
fs文件系统模块介绍:
fs (File system)模块是 Node.js 官方提供的、用来操作文件的模块。fs提供了一系列的方法和属性,用来满足用户对文件的操作需求
fs.readFile()方法 用于读取指定文件的内容
fs.writeFile()方法 用于向指定文件写入内容
Path路径模块
path.join()方法
用来进行多个路径片段拼接成一个完整字符串
path.exname()方法
用来将文件扩展名从路径字符串中解析并提取出来
__dirname变量
用来获取当前文件所在目录的绝对路径
__filename变量
ULR模块
URL模块介绍
URL(Uniform Resource Location统一资源定位符)是用来描述信息资源的字符串,是对网站资源的一种简洁表达形式,也称为网址。url模块是 Node.js 官方提供的、用来处理和解析网络资源地址的模块。它提供了一系列的方法和属性,用来满足用户对资源地址的处理需求
URL字符串与url对象
URL 字符串是包含多个有意义组件的结构化字符串。 解析时,将返回一个 URL 对象,其中包含多个属性
url.parse( )方法
用来解析url地址的方法(旧版方案,已被弃用,但仍能使用),会将url字符串解析为url对象
URL对象
// url.format() 旧版的url模板用来拼接url地址的方法
const urlStr = url.format({
protocol: source.protocol,
hostname: source.hostname,
pathname: source.pathname,
query: {
name: "url"
}
})
更多url模块的相关内容在后续的使用中再进行介绍
http模块
http 模块介绍
http 模块是 Node.js 官方提供的、用来创建 web 服务器的模块。我们通过使用http.creatServer( )方法将一台普通的计算机转变为一台提供网络资源服务的web服务器
http相关内容简介(了解)
客户端:在网络中给负责使用网络节点等资源服务的计算机 服务端:指的就是提供网络资源服务的服务器
IP地址
IP地址简单的点来说就是每一台计算机在网络中的“身份证”,id是具有唯一性的,以便于在服务器提供网络资源服务的时候被快速查找。我们只有在到对方IP地址的情况下,才能与其建立网络通信关系,然后才能进行信息传递(一系列的资源共享等)
域名与域名服务器(IP)的联系
因为IP不便于我们记忆,后来人们发明了另外的用来表示IP地址的字符串表示方案,这个对应的字符串就是我们的域名(域名地址)
IP地址和域名是一一对应的关系,这种对应关系被存放在DNS(域名服务器DomainNameSystem)中,在我们使用域名访问一台服务器的时候,其实首先访问到的是DNS,DNS根据用户输入的域名,将其自动解析为IP地址并跳转到对应的服务器
本地服务器:localhost 其实就是127.0.0.1
端口号
在每台计算机中可能同时需要访问多个web服务器,我们其实也不知道我们到底使用个是哪一台服务器,这是就需要有一个标识帮助我们对齐进行区分,端口号其实就是用于区分不同的web服务的,我们在计算机中使用的每一个web服务器都要有唯一的端口号。用户通过客户端发送过来的请求,通过不同的端口号找到不同的web服务器,可以准确地让对应的服务器进行对应的请求处理
注意:
l 我们在同时使用多台web服务器时,很容易出现端口号被占用的情况,这时需要我们进行手动调配
l 在实际应用中,特别是网络地址(url)中端口号一般会被省略掉
服务器与普通计算机的区别
服务器其实就是一台安装了web服务软件的计算机,相比普通计算机来说性能更强劲。通过web服务软件,普通的计算机就能为其他计算机提供web网络资源服务
在我们node.js中,不需要安装例如TomCat、IIS等第三方web服务器,因为node.js自身就可以提供web服务,只需要使用http模块,通过相关的代码创建服务,就可以将一台普通的安装了node,js运行环境的计算机转变为一台服务器
http.createServer( )方法
用于创建一个web服务器
// 使用require()方法 导入http模块
const http = require("http");
// 使用http.createServer()方法 创建一个服务器
const server = http.createServer(
// 为服务器添加请求或者响应事件
{
"request": (request, response) => {
console.log("已经成功创建了一个服务器")
}
}
)
服务器实例.listen( )方法
用于开启创建好的web服务器
// 开启服务器,使用server.listen()方法来开启的服务器并进行监听
// 参数1 端口号 参数2 ip地址 参数3 事件处理函数
server.listen(80, "127.0.0.1", () => {
console.log("服务器开启成功");
})
服务器实例.on( )方法
用于监听开启的web服务器的请求或响应事件
// server.on()方法用来对以开启的服务器进行请求或响应请求监听
server.on("request", (request, response) => {
/*
request对象,表示http请求对象,其中包含了客户端发送请求所携带的所有信息
response对象,表示http响应对象,其中包含了服务器响应后反馈给客户端的所有信息
*/
const str = `hello ${request.url},this is ${request.method}`
console.log(str);
})
关于客户端请求(request)的常用属性和方法
² method属性 获取请求方式(get、post、delete等)
² url属性 获取请求的路径
² headers属性 获取请求头相关配置信息
关于服务器响应(response)的常用属性和方法
² writeHead( )方法 设置响应时的状态码与数据类型
² statusCode属性 设置返回的http状态码
² setHeader( )方法 设置响应的数据类型
² end( )方法 设置响应结束反馈响应信息
² setTime( )方法 设置响应超时时间,超过设定时间会自动关闭响应
处理响应结果乱码问题
如果不对响应信息进行类型设置的话 默认使用的"text/plain",此时如果响应的信息中存在中文会出现乱码,处理乱码的方式非常简单,只需设置响应类型的字符集编码为utf8或是utf-8即可
response.setHeader("Content-Type", "text/html; charset=utf-8")
练习
1. 访问本地服务器,不同的url地址返回不同的页面内容
// 根据不同的url地址返回不同的内容
server.on("request", (req, res) => {
// 动态获取url地址
const url = req.url
// 定义一个变量,来显示返回的内容
let text = ""
// 根据访问的不同地址返回不同的信息
if (url === "/" || url === "/index.html") {
text = "<h1>欢迎进入我们的首页</h1>"
} else if (url === "/login.html") {
text = "<h1>欢迎进入登录页面!</h1>"
} else {
text = "<h1>404</h1>"
}
//处理反馈的响应类型 防止乱码
res.setHeader("Content-Type", "text/html; charset=utf-8")
// 接触响应请求处理 返回给客户端信息
res.end(text)
})
2. 通过本地服务器打开本地时钟案例的html页面,且能够切换不同的资源,内容均能正常显示(不乱码)
// 导入要使用的模块 fs、path、http
const fs = require("fs")
const path = require("path")
const http = require("http")
// 定义ip
const host = "127.0.0.1"
// 定义端口
const port = "3000"
// 创建web服务器
const server = http.createServer((req, res) => {
console.log("服务器创建成功");
})
// 开启服务器并进行监听
server.listen(port, host, () => {
console.log(`服务器开启成功 http://${host}:${port}`);
})
// 读取文件信息
fs.readFile(clientPath, "utf8", (err, data) => {
// 判断是否读取成功
if (err) {
// 如果读取失败,直接将错误信息显示返回给客户端
res.end(`<h1>${err.message}</h1>`)
}
// 如果读取成功,将读取到的信息返回给客户端
res.end(data)
})
})
模块与模块化
模块的概念
模块(module)就是具有独立功能的、能够分解、组合、维护、更换的基本结构,它们共同构造成为一个整体,例如计算机由主板、CPU、显卡、声卡、内存、硬盘等模块组成,而在我们的node.js环境中默认支持模块系统,每个JavaScript文件就是项目中的一个又一个的模块
模块化的概念
模块化指的就是将庞大繁杂的系统,逐级细分为一个又一个可独立存在的模块的过程,例如我们可以将一个商城划分前端模块与后端模块,而它们又能被划分为登录模块、注册模块、支付模块、详情模块、个人购物车模块等等,而这每一个小模块又能继续细分……也就是说模块化会将问题逐渐缩小化进行处理,模块之间既独立存在但又相互依赖
模块化与模块化开发的概念
事实上模块化开发最终的目的是将程序划分成一个个小的模块。在这个结构中编写属于自己的逻辑代码,有自己的作用域,定义变量名词时不会影响到其他的模块。我们可以将自己希望暴露(共享)的变量、函数、对象等导出在这个模块中复用,也可以通过某种方式,导入其他模块中的变量、函数、对象等进行使用,按照这种模块划分开发程序的过程,称之为模块化开发的过程
模块化的优点
l 更加有利于项目的升级与维护
l 实现了代码的高效复用
l 实现了代码按需加载功能
JavaScript的缺陷与模块化的发展
无论JavaScript有多么优秀、发展的有多么迅速、发展的多好,都不能掩盖它存在的大量缺陷,如:
n 使用var声明变量时存在的作用域问题
n 在JavaScript的面向对象中并不能像常规面向对象语言那样使用class(类)
n 没有模块化问题等等
对于早期的JavaScript来说,确实存在大量问题,没有模块化也确定给开发者带来很多困扰。因为当时的代码量很少,JavaScript的作者仅仅是把JavaScript作为一种实现简单的表单验证或动画的脚本语言来编写的。但是随着前端开发的崛起与JavaScript的飞速发展,代码变得越来越复杂,如:
n ajax的出现意味着前后端开发分离,后端返回数据后,我们需要通过JavaScript进行前端页面的渲染
n SPA(单页面应用程序)出现以后前端页面变得更加复杂,意味着包括前端路由、状态管理等等一系列复杂的需求也需要通过JavaScript来实现
n Node.js的诞生,意味着JavaScript可以编写复杂的后端程序,此时更加彰显出没有模块化的致命硬伤
所以,在JavaScript中制定出模块化已经是大势所趋,但JavaScript直到ES2015(ES6)才推出了自己的模块化方案。在此之前,为了使JavaScript支持模块化,出现了很多不同的模块化规范,例如:CommonJS、AMD、CMD等等
没有模块化带来的问题
早期没有模块化带来了许许多多的问题,例如:代码复用性较低、代码升级维护起来比较麻烦、命名冲突问题等等,虽然我们可以使用比如立即执行函数(IIFE)来处理命名冲突的问题,但是又会带来新的问题,如:
必须记得每一个模块中返回对象的命名,才能在其他模块使用过程中正确的使用
代码写起来混乱不堪,每个文件中的代码都需要包裹在一个匿名函数中来编写
在没有合适的规范情况下,每个人、每个公司都可能会任意命名、甚至出现模块名称相同的情况
所以,虽然实现了模块化,但是我们的实现过于简单且没有规范。所以,我们需要制定一定的规范来约束每个人都按照这个规范去编写模块化的代码,这个规范中应该包括核心功能:模块本身可以导出暴露的属性,模块又可以导入自己需要的属性,JavaScript社区为了解决上面的问题,涌现出一系列好用的规范,例如:CommonJS、AMD、CMD等等
模块化规范
模块化规范就是开发人员共同遵守的,使用何种方式将代码划分成模块、使用何种方式将不同的模块进行组合的约定的规则,以便于各模块间相互调用,而且还降低开发中的沟通成本。
CommonJS规范
Node.js中默认支持模块系统,这个模块系统遵循CommonJS规范,它规定:
在每个模块中,module变量表示当前模块
Module变量表示的是一个对象,使用module对象的exports属性可以暴露(共享)模块的数据
使用require( )方法可以用于加载使用module.exports属性暴露(共享)的模块的数据
CommonJS规范与Node.js的关系
CommonJS是一个规范,最初提出来是在浏览器以外的地方使用,并且当时被命名为ServerJS,后来为了体现它的广泛性,修改为CommonJS,平时我们也会简称为CJS
Node.JS是CommonJS在服务器端一个具有代表性的实现
Browserify(浏览器的前端打包工具)是CommonJS在浏览器中的一种实现
Webpack(模块打包工具)具备对CommonJS的支持和转换
所以Node.JS中对CommonJS进行了支持和实现,让我们在开发node的过程中可以方便的进行模块化开发,如:
在Node中每一个js文件都是一个单独的模块
这个模块中包括CommonJS规范的核心变量:exports、module.exports、require
我们可以使用这些变量来方便的进行模块化开发
Node中对模块化核心的导入、导出功能进行了实现:
exports和module.exports可以负责对模块中的内容进行导出
require函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容
例如在模块a.js中的变量和方法如果想要在模块b.js中进行使用,必须在a.js中导出(暴露或共享)这个模块的内容,然后在b.js中进行导入,才能进行使用
exports导出
exports是一个对象,我们可以在这个对象中添加很多属性,添加的属性会被导出
// 使用exports对象共享模块中的数据
exports.x = x
exports.y = y
exports.getNum = getNum
在模块demo7_1.js中进行导入
// require方法 导入共享的模块
const x = require("./demo7.js")
// 输出共享的数据
console.log(x);
此时就意味着使用demo7_1.js中的变量x来接收在模块demo7.js中共享的exports对象,也就是require方法通过各种方式调取exports对象,最终在demo7.js中找到并且将它赋给了变量x
module.exports导出
尽管我们可以使用exports对象来导出内容,但在实际开发中常用的是module.exports进行导出的,module是node中的一个对象,其中保存了与当前模块相关的内容
module.exports = {
x,y,z
}
module.expoorts与exports的关系
因为原来CommonJS中是没有module.exports的概念的,但是为了实现模块的导出,Node中使用的是Module的类,每一个模块都是Module的一个实例,也就是module,所以在Node中真正用于导出的其实根本不是exports而是module.exports,因为module才是导出的真正实现者
因为module对象的exports属性是exports对象的一个引用,也就是说 module.exports 相当于exports相当于demo7_1.js中的变量x,所以使用exports也可以进行导出
require导入
在其他文件中通过 require 方法引入模块(内置模块、用户自定义模块、第三方库模块等),require 方法的返回值就是对应模块的 module.exports 对象。在导入模块时,模块文件后缀 .js 可以省略,文件路径不可省略。这种方法属于同步导入模块,模块导入后可以立即使用被加载模块中的内容
// require方法 导入共享的模块
const x = require("./demo7")
// 输出共享的数据
console.log(x);
require方法的查找规则
x是一个Node核心内置模块,比如path、http模块,会直接返回核心模块,并且停止查找
X是以 ./ 或 ../ 或 /(根目录)开头的模块
第一步:将X当做一个文件在对应的目录下查找;
如果有后缀名,按照后缀名的格式查找对应的文件
如果没有后缀名,会按照规定的顺序查找
<1> 直接查找文件X
ü <2> 查找X.js文件
ü <3> 查找X.json文件
ü <4> 查找X.node文件
Ø 第二步:没有找到对应的文件,将X作为一个目录
² 查找目录下面的index文件
ü <1> 查找X/index.js文件
ü <2> 查找X/index.json文件
ü <3> 查找X/index.node文件
Ø 仍然没有找到的话,会报错:not found
3. 直接是一个X(没有路径),并且X也不是一个核心模块
如在/Users/hero/Documents/Node/demo7_1.js中使用require(“demo7”)
会按照以下路径查找
"/Users/hero/Documents/Node"
"/Users/hero/Node"
"/Users/Node"
"/Node"
如果通过上面的路径仍然找不到要导入的模块,将报错:not found
模块加载过程
n 模块在被第一次引入时,模块中的js代码会被运行一次
n 模块被多次引入时,会缓存,最终只加载(运行)一次
因为每个模块对象module都有一个loaded属性,属性值为布尔类型,false表示未加载,true表示已加载
n 当存在循环导入时,会按照深度优先查找(按深度进行搜索)的顺序进行加载
这个其实是一种数据结构,叫做图结构,图结构在遍历的过程中,有深度优先搜索(DFS, depth first search)和广度优先搜索(BFS, breadth first search),node中采用的是深度优先算法,所以加载顺序为main -> aaa -> ccc -> ddd -> eee ->bbb
CommonJS规范的缺点
n CommonJS在加载模块是同步的
同步,意味着只有等对应的模块加载完毕,当前模块中的内容才能被运行,在服务器不会有什么问题,因为服务器加载的js文件都是本地文件,加载速度非常快,但应用于浏览器就会出现问题,因为浏览器加载js文件需要先从服务器将文件下载下来,之后再加载运行,所以采用同步策略就意味着后续的js代码都无法正常运行,即使是一些简单的DOM操作,所以在浏览器中,通常不使用CommonJS规范。但在webpack中使用CommonJS是另外一回事,因为它会将代码转成浏览器可以直接执行的代码
早期为了可以在浏览器中使用模块化,通常会采用AMD规范或CMD规范,目前一方面现代的浏览器已经支持ES Modules,另一方面借助于webpack等工具可以实现对CommonJS或者ES Module代码的转换,所以AMD规范和CMD规范已经很少被使用了
AMD规范
AMD(Asynchronous Module Definition,异步模块定义)主要是应用于浏览器的一种模块化规范,采用异步的方式对模块进行加载。其实AMD规范比CommonJS规范更早出现,但CommonJS目前依然在被使用,AMD使用的较少
规范只是定义代码的应该如何去编写,只有有了具体的实现才能被应用,实现AMD比较常用的库是require.js和curl.js
require.js的使用
require.js的下载地址:github.com/requirejs/r…,找到其中的require.js文件进行下载
在HTML页面的引入require.js并加载定义好的入口文件
CMD规范
CMD(Common Module Definition,通用模块定义)规范也是应用于浏览器的一种模块化规范,采用的也是异步加载模块,但是它将CommonJS的优点吸收了过来,目前使用较少实现CMD比较常用的库是sea.js
sea,js的使用
sea.js的下载地址:github.com/seajs/seajs…文件进下载
在HTML页面中引入sea.js文件并使用主入口文件,seajs是用来指定主入口文件的
模块分类
Node.js 中根据模块来源的不同,将模块分为了 3 大类,分别是
n 内置模块
Node.js中本来就内置好的模块,例如 fs、path、url、http 等,在使用时不需加任何路径
n 自定义模块
用户创建的每个 .js 文件,都是自定义模块
n 第三方模块
由第三方开发出来的模块,既非官方提供也非不是我们自己创建的,使用前需要先下载
模块作用域
与函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域。在模块被执行之前,模块中的代码会被包裹在模块包装函数当中,这样每个模块中的代码就都拥有了自己的作用域,所以在模块外部就不能访问模块内部的成员了。
// a.js
const x = 123 // b.js
console.log(x); // 报错
模块作用域的优点
形成了作用域隔离,可以防止全局变量污染
因为Node默认就有模块系统,而这个模块系统又遵循CommonJS规范,所以每个.js中都默认有一个module对象,这个对象中面存储了和当前模块有关的信息,module 和 require 等实际上模块内部成员而非全局对象 global 下面的属性,所以自然就产生了作用域隔离
ES Module
早期JavaScript 一直没有模块(module)体系,无法将一个大的项目拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至就连 CSS 都有@import,但是 JavaScript在这方面没有任何支持,所以对于大型且复杂的项目开发来说就是一个巨大整障碍。所以才会产生我们前面学习的社区规范:CommonJS、AMD、CMD等。前者用于服务器端,后者用于客户端(浏览器)
所以ECMA之后的更新中推出了模块化系统。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系以及输入和输出的变量。CommonJS 和 AMD、CMD规范,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象的属性
// CommonJS模块
let { stat, exists, readfile } = require('fs');
let fs = require('fs');
let stat = fs.stat;
let exists = fs.exists;
let readfile = fs.readfile;// 等同于
上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个fs对象,然后再从fs对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”
ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入
// ES6 module
import { stat, exists, readFile } from 'fs';
上面代码的实质是从fs模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。
由于 ES6 模块是编译时加载的,所以使得静态分析成为了可能
基本语法
严格模式(了解)
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict"
严格模式主要有以下限制:
变量必须声明后再使用
函数的参数不能有同名属性,否则报错
不能使用with语句
不能对只读属性赋值,否则报错
不能使用前缀 0表示八进制数,否则报错
不能删除不可删除的属性,否则报错
不能删除变量delete prop,会报错,只能删除属性delete global[prop]
eval不会在它的外层作用域引入变量
eval和arguments不能被重新赋值
arguments不会自动反映函数参数的变化
不能使用arguments.callee
不能使用arguments.caller
禁止this指向全局对象
不能使用fn.caller和fn.arguments获取函数调用的堆栈
增加了保留字(比如protected【受保护的】、static【静态的】和interface【接口】等)
export关键字
模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,将一个模块中的变量、函数、类等导出,import命令用于输入其他模块提供的功能
方式1:直接在语句声明前添加export
// 导出变量
export const num = 123;
// 导出函数
export function getNum(num) {
console.log(num);
}
// 导出类
export class Dog{
color = "yellow"
call() {
console.log("汪汪汪\~");
}
}
方式2:将要被导出内容的标识符写在脚本末尾expoer后的{ }中
在export关键字后使用一对大括号指定所要输出的一组变量({}表示的不是对象),这与前一种写法是等价的,但因为它写在脚本尾部可以一眼看清楚输出了哪些内容,所以更加推荐
const num = 123;
function getNum(num) {
console.log(num);
}
class Dog {
color = "yellow"
call() {
console.log("汪汪汪\~");
}
}
// 导出变量、函数、类
export { num, getNum, Dog }
导出时起别名
导出时可以给被导出内容的标识符通过as关键字起一个别名
const num = 123;
function getNum(num) { …… }
class Dog { …… }
// 导出变量、函数、类
export {
num as testNum,
getNum as testFun,
Dog as testClass
}
注意事项
² export导出的内容,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值
² 这与 CommonJS 规范完全不同,CommonJS 模块输出的是值的缓存,不存在动态更新
import关键字
使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块
方式1:import “模块”
直接使用import导入暴露数据的整个模块,也就是会导入它的暴露的所有内容
import "./profile.js";
方式2:import{ 标识符列表 } from “模块”
可以将暴露数据的模块中的数据,通过写在import后的{ }中进行调用。注意这里的{ }表示的也不是一个对象,里面只是存放导入的标识符列表内容,大括号中的标识符必须与被导入模块导出内容的标识符相同
// main.js
import { firstName, lastName, year } from './profile.js';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
方式3:使用一个*表示导入模块中的所有内容
除了指定加载某个输出值,还可以使用一个星号(*)指定一个对象来进行整体加载,所有输出值都将加载在这个对象上面
// circle.js
function area(radius) {
return Math.PI \* radius \* radius;
}
function circumference(radius) {
return 2 \* Math.PI \* radius;
}
export { area, circumference }
导出计算圆的面积与周长的方法
// main.js
import \* as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
计算圆的面积与周长的方法会被整体导入到*指定的circle对象中
需要注意的是模块整体加载到的那个对象(circle),应该是可以静态分析的,所以不允许运行时改变
导入时起别名
导入时可以给被导入内容的标识符通过as关键字起一个别名
import { lastName as surname } from './profile.js';
注意事项
Import中输入的内容都是只读的,因为本质上来说它只是引用的数据。所以,不允许在加载模块的脚本里面改写导入的内容
import { lastName } from './profile.js';
lastName = "xiaohei"; //Syntax Error : "lastname" is read-only;
但如果lastname是一个对象,是允许被改写的
import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js后缀可以省略(在浏览器端使用的话不可省略)
如果只是模块名,不带有路径,则必须有配置文件来告诉 JavaScript 引擎模块的位置
Import语句在被解析时会提升到整个模块的头部,首先执行
foo();
import { foo } from 'my_module'; // 不会报错
import是静态执行的,所以不能使用表达式等这些只有在运行时才能得到结果的语法结构
如果重复使用import导入同一个模块,则这个模块只会被导入一次,因为import语句是 Singleton(单例) 模式,在首次导入时会被放在缓存中,再次被调用时其实调用的是缓存中的模块
import在静态解析阶段执行,所以它是一个模块之中最早执行的
export default
前面我们用到的导入、导出功能都是必须使用具体标识符的,但用户在使用程序时肯定希望快速上手,所以未必会查看相关的说明,所以就需要用到export default命令,为模块指定默认导出,默认导出时不需要指定名字
// export-default.js
export default function () {
console.log('foo');
}
// 或者写为
function foo() {
console.log('foo');
}
export default foo;
foo函数的函数名foo,在模块外部无效,因为都被指定为默认导出,所以加载时会被视为匿名函数加载
/ import-default.js
import customName from './export-default';
customName(); // 'foo'
在导入时不需要知道原模块导出的接口名,不需要使用 { },且可以自定义标识符,以便于和现有的CommonJS等规范相互操作
注意事项
² 一个模块只能有一个默认导出,因此export default命令只能使用一次
² 因为import只可能对应唯一的export default,所以import后面不用加大括号
² 其实export default就是将被暴露内容的标识符起了个叫default的别名,而系统允许你在导入时将其改为任意的标识符
// modules.js
export {add as default};
// 等同于
export default add;
// app.js
import { default as foo } from 'modules';
// 等同于
import foo from 'modules';
export与import的结合使用
如果在一个模块之中,先导入后导出同一个模块,import语句可以与export语句写在一起,这个用法是在ES2020中更新的
export { foo, bar } from 'my\_module';
// 可以简单理解为
import { foo, bar } from 'my\_module';
export { foo, bar };
也可以使用整体导出的写法
export { foo, bar } from 'my\_module';
// 写为整体输出的形式
export \* from 'my\_module';
默认接口的写法
export { default } from 'foo';
具名接口改为默认接口的写法
export { es6 as default } from './someModule';
// 等同于
import { es6 } from './someModule';
export default es6;
默认接口也可以改为具名接口
export { default as es6 } from './someModule';
优点
在开发和封装一个功能库时,通常是需要将暴露的所有接口放到一个文件中的,这样不仅方便阅读,也方便制定统一的接口规范
好啦 本期就更新到这里 下期我们更新模板之间的继承类
转载自:https://juejin.cn/post/7225925643883774008