Node基础

Node基础

一、初始Node.js

1. 什么是Node.js

Node.js is a JavaScript runtime built on Chrome’s V8 JavaScript engine

Node.js是一个基于Chrome V8 引擎的 JavaScript运行环境

Node.js的官网地址:https://nodejs.org/zh-cn/

2. Node.js中的JavaScript运行环境

在这里插入图片描述

注意:

  1. 浏览器是JavaScript的前端运行环境
  2. Node.js是JavaScript的后端运行环境
  3. Node.js中无法调用DOM和DOM等浏览器内置API

3. Node.js可以做什么

Node.js作为一个JavaScript的运行环境,仅仅提供了基础的功能和API。然而,基于Node.js提供的这些基础功能,很多强大的工具和框架如雨后春笋,层出不穷,所以学会了Node.js,可以让前端程序员胜任更多的工作和岗位:

  1. 基于Express框架(http://www.expressjs.com.cn/),可以快速构建Web应用
  2. 基于Electron框架(https://electronjs.org/),可以构建跨平台的桌面应用
  3. 基于restify框架(http://restify.com/),可以快速构建API接口项目
  4. 读写和操作数据库、创建实用的命令行工具辅助前端开发.etc

4. Node.js怎么学

浏览器中的JavaScript学习路劲:

JavaScript基础语法 + 浏览器内置API(DOM + BOM)+第三方库(jQuery、art-template等)

Node.js的学习路劲:

JavaScript基础语法 + Node.js内置API模块(fs、path、http等)+ 第三方API(express、mysql等)

5. Node.js环境的安装

如果希望通过Node.js来运行JavaScript代码,则必须在计算机上安装Node.js环境才行。

安装包可以从Node.js的官网首页直接下载,进入到Node.js的官网首页(下载 | Node.js 中文网 (nodejs.cn)),点击绿色的按钮,下载所需要的版本后,双击直接安装即可

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

二、fs 文件系统模块

1. 什么是fs文件系统模块

fs模块是Node.js官方提供的、用来操作文件的模块。他提供了一系列的方法和属性,用来满足用户对文件的操作需求

例如:

  • fs.readFile()方法,用来读取指定文件中的内容
  • fs.writeFile()方法,用来向指定文件中写入内容

如果要在JavaScript代码中,使用fs模块用操作文件,则需要使用如下的方式到导入它:

const fs = require('fs')

2. 读取指定文件中的内容

2.1 fs.readFile()的语法格式

使用fs.readFile()方法,可以读取指定文件中的内容,语法格式如下:

fs.readFile(path[,options],callback)

参数解读:

  • 参数1:必选参数,字符串,表示文件的路径。
  • 参数2:可选参数,表示以什么编码格式来读取文件。
  • 参数3:必选参数,文件读取完成后,通过回调函数拿到读取的结果。
2.2 fs.readFile()的示例代码

以utf8的编码格式,读取指定文件的内容,并打印err和dataStr的值:

// 1.导入 fs模块,来操作文件
const fs = require('fs')

// 2. 调用fs.readFile()方法读取文件
// 参数1:读取文件的存放路径
// 参数2: 读取文件时候采用的编码格式,一般默认指定utf8
// 参数3:回调函数,拿到读取失败和成功的结果 err dataStr

// 2.1 打印失败的结果
// 如果读取成功,则err的值为null
// 如果读取失败,则err的值我错误对象,dataStr的值为 undefined
fs.readFile('./files/1.txt','utf8',(err,dataStr) => {
    console.log(err);
    console.log('---------');
    console.log(dataStr);
})
2.3 判断文件是否读取成功

可以判断err对象是否为null,从而知晓文件读取的结果:

const fs = require('fs')

fs.readFile('./files/1.txt','utf8',(err,dataStr) => {
    // 判断文件是否读取成功
    if(err) {
        return console.log('文件读取失败!' + err.message);
    }
    console.log('文件读取成功!' + dataStr);
})

3. 向指定的文件中写入内容

3.1 fs.writeFile()语法格式

使用fs.writeFile()方法,可以向指定的文件中写入内容,语法格式如下:

fs.writeFile(file,data[,options],callback)

参数解读:

  • 参数1:必选参数,需要指定一个文件路径的字符串,表示文件的存放路径
  • 参数2:必选参数,表示要写入的内容
  • 参数3:可选参数,表示以什么格式写入文件内容,默认值是utf8
  • 参数4:必选参数,文件写入完成后的回调函数
3.2 fs.writeFile()的示例代码
// 1.导入fs文件系统模块
const fs = require('fs')

// 2.调用 fs.writeFile()方法,写入文件的内容
// 参数1:表示文件的存放路径
// 参数2:表示哟啊写入的内容
// 参数3:回调函数,拿到读取失败和成功的结果

let str = '你好,Node.js'

fs.writeFile('./files/2.txt',str, err => {
    // 2.1 如果文件写入成功,则err的值等于null
    // 2.2 如果文件写入失败, 则err的值等于一个 错误对象
    console.log(err);
})
3.3 判断文件是否写入成功
// 1.导入fs文件系统模块
const fs = require('fs')

// 2.调用 fs.writeFile()方法,写入文件的内容
// 参数1:表示文件的存放路径
// 参数2:表示哟啊写入的内容
// 参数3:回调函数,拿到读取失败和成功的结果

let str = '你好,Node.js'

fs.writeFile('./files/2.txt',str, err => {
    // 2.1 如果文件写入成功,则err的值等于null
    // 2.2 如果文件写入失败, 则err的值等于一个 错误对象
    if(err) {
        return console.log('文件写入失败!' + err.message);
    }
    console.log('文件写入成功!');
})

4. 练习- 考试成绩整理

在这里插入图片描述

在这里插入图片描述

// 1. 导入fs文件系统模块
const fs = require('fs')

// 2. 调用 fs.readFile()读取文件内容
fs.readFile('./files/成绩.txt','utf8',(err,dataStr) => {
    // 3. 判断文件是否读取成功
    if(err) {
        return console.log('文件读取失败!' + err.message)
    }
    // 4.1 先把成绩的数据,按照空格进行分割
    const arrOld = dataStr.split(' ')
    // 4.2 循环分割后的数组,对每一项数据,机芯字符串的替换操作
    const arrNew = []
    arrOld.forEach(item => {
        arrNew.push(item.replace('=',': '))
    })
    // 4.3 把新数组中农的每一项,进行合并,得到一个新的字符串
    const newStr = arrNew.join('\r\n')

    // 5. 调用 fs.writeFile()方法,把处理完毕的成绩,写入新文件中
    fs.writeFile('./files/成绩-ok.txt',newStr,err => {
        if(err) {
            return console.log('写入文件失败!' + err.message)
        }
        console.log('写入文件成功!');
    })
})

5. fs模块 - 路径动态拼接的问题

在使用fs模块操作文件时,如果提供的操作路径是以./../开头的相对路径时,很容易出现路径动态拼接错误的问题

原因:代码在运行的时候,会以执行node命令时所处的目录,动态拼接出被操作文件的完整路径

解决方案:在使用fs模块模块操作文件时,直接提供完整的路径,不要提供./或…/开头的相对路径,从而防止路径动态拼接的问题

// __dirname 表示当前文件所处的目录

// 1. 导入fs文件系统模块
const fs = require('fs')

fs.readFile(__dirname + '/files/1.txt','utf8', (err,dataStr) =>{
    if(err) {
        return console.log('读取文件失败!' + err.message)
    }
    console.log('读取文件成功!' + dataStr);
})

三、path路径模块

1. 什么是path路径模块

path模块是Node.js官方提供的、用来处理路径的模块。他提供了一些列的方法和属性,用来满足用户对路径的处理需求。

例如:

  • path.join()方法,用来将多个路径片段拼接成一个完整的路径字符串

  • path.basename()方法,用来从路径字符串中,将文件名解析出来

如果要在JavaScript代码中,使用path模块来处理路径,则需要使用如下的方式先导入它:

const path = require('path')

2. 路径拼接

2.1 path.join()的语法格式

使用path.join()方法,可以把多个路径片段拼接为完整的路径字符串,语法格式如下:

path.join([...paths])

参数解读:

  • …paths路径片段的序列
  • 返回值:
2.2 path.join()的代码示例

使用path.join()方法,可以把多个路径片段拼接为完整的路径字符串:

const path = require('path')
const fs = require('fs')

// 注意: ../ 会抵消前面的路径
 const pathStr = path.join('/a','/b/c','../../','./d','e')
 console.log(pathStr); // \a\d\e

fs.readFile(path.join(__dirname,'./files/成绩-ok.txt'),'utf8',(err,dataStr) => {
    if(err) {
        return console.log(err.message);
    }
    console.log(dataStr);
})

注意:今后凡是涉及到路径拼接的操作,都要使用path.join()方法进行处理。不要直接使用 + 进行字符串的拼接

3. 获取路径中的文件名

3.1 path.basename()的语法格式

使用path.basename()方法,可以获取路径中的最后一部分,经常通过这个方法获取路径中的文件名,语法格式如下:

path.basename(path[,ext])

参数解读:

  • path必选参数,表示一个路径的字符串
  • ext可选参数,表示文件扩展名
  • 返回:表示路径中的最后一部分
3.2 path.basename()的代码示例

使用path.basename()方法,可以从一个文件路径中,获取到文件的名称部分:

const path = require('path')

// 定义文件存放路径
const fpath = './files/1.txt'

const fullName = path.basename(fpath)
console.log(fullName);

const nameWithoutExt = path.basename(fpath,'.txt')
console.log(nameWithoutExt);

4. pathextname()的语法格式

4…1

使用path.extname()方法,可以获取路径中的扩展名部分,语法格式如下:

path.extname(path)

参数解读:

  • path必选参数,表示一个路径的字符串
  • 返回:返回得到的扩展名字符串
4.2 path.extname()的代码示例

使用path.extname()方法,可以获取路径中的扩展名部分:

const path = require('path')

// 这里是文件的存放路径
const fpath = './path.basename()使用.js'

const fext = path.extname(fpath)
console.log(fext);

5.综合案例

// 1.1 导入 fs 模块
const fs = require('fs')
// 1.2 导入 path 模块
const path = require('path')

// 1.3 定义正则表达式,分别匹配<style></style> 和<script></script>
const regStyle = /<style>[\s\S]*<\/style>/
const regScript = /<script>[\s\S]*<\/script>/

// 2.1 调用 fs.readFile() 方法读取文件
fs.readFile(path.join(__dirname,'./files/index.html'),'utf8',(err,dataStr) => {
    // 2.2 读取 HTML 文件失败
    if(err) {
        return console.log('读取HTML文件失败!' + err.message)
    }
    // 2.3 读取文件成功后,调用对应的三个方法,分别拆解除css,js,读取HTML文件
    resolveCss(dataStr)
    resolveScript(dataStr)
    resolveHTML(dataStr)
})

// 3.1 定义处理css样式的方法
function resolveCss(htmlStr) {
    // 3.2 使用正则提取需要的内容
    const r1 = regStyle.exec(htmlStr)
    // 3.3 将提取出来的样式字符串,进行字符串的replace替换操作
    const newCss = r1[0].replace('<style>','').replace('</style>','')
    // 3.4 调用 fs.writeFile()方法,将提取的方式,写入到clock目录中idnex.css的文件里面
    fs.writeFile(path.join(__dirname,'./clock/index.css'),newCss, err => {
        if(err) {
            return console.log('写入CSS样式失败!' + err.message)
        }
        console.log('写入样式成功');
    })
}

// 4.1 定义处理js脚本的方法
function resolveScript (htmlStr) {
    // 4.2 使用正则提取需要的内容
    const r2 = regScript.exec(htmlStr)
    // 4.3 将提取出来的脚本字符串,进行字符串的replace替换操作
    const newScript = r2[0].replace('<script>','').replace('</script>','')
    // 4.4 调用 fs.writeFile()方法,将提取的脚本,写入到clock目录中index.js的文件里面
    fs.writeFile(path.join(__dirname,'./clock/index.js'),newScript, err => {
        if(err) {
            return console.log('写入JS脚本失败!' + err.message)
        }
        console.log('写入JS脚本成功!');
    })
}

// 5.1 定义处理HTML 结构的方法
function resolveHTML(htmlStr) {
    // 5.2 将字符串调用replace方法,把内嵌的style和script标签,替换为外联的link和script标签
    const newHTML = htmlStr.replace(regStyle,'<link rel = "stylesheet" href = "./index.css"/>')
    .replace(regScript,'<script src="./index.js"></script>')
    // 5.3 写入 index.html这个文件
    fs.writeFile(path.join(__dirname,'./clock/index.html'),newHTML,err => {
        if(err) {
            return console.log('写入HTML文件失败!' + err.message)
        }
        console.log('写入HTML文件成功!');
    })
}
  • 案例的两个注意点
  1. fs.writeFile()方法只能用来创建文件,不能用来创建路径
  2. 重复调用fs.writeFile()写入同一个文件,新写入的内容会覆盖之前的旧内容

四、http模块

1. 什么是http模块

回顾:什么是客户端、什么是服务器

在网络节点中,负责消费资源的电脑,叫做客户端;负责对外提供网络资源的电脑,叫做服务器。

http模块是Node.js官方提供的、用来创建web服务器的模块。通过http模块提供的http.createServer()方法,就能方便的把一个普通的电脑,变成一台Web服务器,从而对外提供Web资源服务。

如果要希望使用http模块创建Web服务器,则需要先导入他:

const http = require('http')

2. http模块的作用

服务器和普通电脑的区别在于,服务器上安装了web服务器软件,例如:IIS、Apache等。通过安装这些服务器软件,就能把一台普通的电脑变成一台web服务器

在Node.js中,我们不需要使用IIS、Apache等这些第三方web服务器软件。因为弥望可以基于Node.js提供的http模块,通过几行简单的代码,就能轻松的手写一个服务器软件,从而对外提供web服务

3. 服务器相关概念

3.1 IP地址

IP地址就是互联网上每台计算机的唯一地址,因此IP地址具有唯一性,如果把"个人电脑"比作"一个电话",那么"IP地址"就相当于"电话号码",只有在知道对方IP地址的前提下,才能与对应的电脑之间进行数据通信。

IP地址的格式:通常用"点分十进制"表示成(a.b.c.d)的形式,其中,a,b,c,d都是0~255之间的十进制整数。如果:用点分十进制表示的IP地址(192.168.1.1)

注意:

  1. 互联网中每台Web服务器,都有之间的IP地址,例如:大家可以在Windows的终端运行ping www.baidu.com命令,即可查看百度服务器的IP地址
  2. 在开发期间,自己的电脑即是一台服务器,也是一个客户端,为了方便测试,可以在自己的浏览器中输入127.0.0.1这个IP地址,就鞥把自己的电脑当做一台服务器进行访问了
3.2 域名域名服务器

尽管IP地址能够唯一地标记网络上的计算机,但IP地址是一长串数字,不直观,而且不便于记忆,于是人们又发明了另一套字符型地址方案,即所谓的域名(Domain Name)地址

IP地址域名是一一对应的关系,这份对应关系存放在一种叫做域名服务器(DNS,Domain name server)的电脑中。使用者只需通过好记的域名访问对应的服务器即可,对应的转换工作由域名服务器实现。因此,域名服务器就是提供IP地址和域名之间的转换服务器的服务器

注意:

  1. 单纯使用IP地址,互联网中的电脑也能够正常工作。但是有了域名的加持,能让互联网的世界变得更加方便。
  2. 在开发测试期间,127.0.0.1对应的域名是localhost它们都代表我们之间的这台电脑,在使用效果上没有任何区别
3.3 端口号

计算机中的端口号,就好像是现实生活中的门牌号用于。通过门牌号,外卖小哥可以在整栋大楼从多的房间中,准确把外卖送到你的手中。

同样的道理,在一台电脑中,可以运行成百上千个web服务。每个web服务器都对应一个唯一的端口号。客户端发送过来的网络请求,通过端口号,可以被准确地交给对应的web服务进行处理。

在这里插入图片描述

注意:

  1. 每个端口号不能同时被多个web服务占用
  2. 在实际应用中,URL中的80端口可以被省略

4. 创建最基本的web服务器

4.1 创建web服务器的基本步骤
  1. 导入http模块
  2. 创建web服务器实例
  3. 为服务器实例绑定request事件,监听客户端的请求
  4. 启动服务器
(1)导入http模块

如果希望在自己的电脑上创建一个web服务器,从而对外提供web服务,则需要导入http模块:

const http = require('http')
(2)创建web服务器实例

调用`http.createServer()方法,即可快速创建一个web服务器实例:

const server = http.createServer()
(3)为服务器实例绑定request事件

为服务器实例绑定request事件,即可监听客户端发送过来的网络请求:

// 使用服务器实例的 .on()方法,为服务器绑定一个request事件
server.on('request',(req,res) => {
    // 只要有客户端来请求我们自己的服务器,就会触发request事件,从而调用这个事件处理函数
    console.log('Someone visit our web server .')
})
(4)启动服务器

调用服务器实例的.listen()方法,即可启动当前的web服务器实例:

// 调用server.listen(端口号,回调)方法,即可启动web服务器
server.listen(80,() => {
	console.log('http server running at http://127.0.0.1')
})
// 1. 导入http模块
const http = require('http')
// 2. 创建web服务器实例
const server = http.createServer()
// 3. 为服务器实例绑定 request事件,监听客户端的请求
server.on('request',function(req,res) {
    console.log('Someone visit our web server.');
})
// 4. 启动服务器
server.listen(80,function() {
    console.log('server running at http://127.0.0.1');
})
(5)req请求对象

只要服务器接收到了客户端的请求,就会调用通过server.on()为服务器绑定的request时间处理函数

如果想在事件处理函数中,访问与客户端相关的数据或属性,可以使用如下方式:

const http = require('http')

const server = http.createServer()

server.on('request', req => {
    // req.url 是客户端请求的url地址
    const url = req.url
    // req.method 是客户端请求的method类型
    const method = req.method
    const str = `Your request url is ${url},and request method is ${method}`
    console.log(str);
})

server.listen(80,function() {
    console.log('server running at http://127.0.0.1');
})
(6)res响应对象

在服务器的request事件处理函数中,如果想访问与服务器相关的数据或属性,可以使用如下方式:

const http = require('http')

const server = http.createServer()

server.on('request', (req,res) => {
    // req.url 是客户端请求的url地址
    const url = req.url
    // req.method 是客户端请求的method类型
    const method = req.method
    const str = `Your request url is ${url},and request method is ${method}`
    console.log(str);
    // 调用 res.end()方法,向客户端响应一些内容
    res.end(str)
})

server.listen(80,function() {
    console.log('server running at http://127.0.0.1');
})
4.2 解决中文乱码问题

当调用res.end()方法,向客户端发送中文内容的时候,会出现乱码问题,此时,需要手动设置内容的编码格式

const http = require('http')

const server = http.createServer()

server.on('request',(req,res) => {
    // 定义一个字符串,包含中文的内容
    const str = `您请求的URL地址是${req.url},您请求的method类型为${req.method}`
    // 调用 res.setHeader()方法,设置 Content-Type 响应头,解决中文乱码的问题
    res.setHeader('Content-Type','text/html; charset=utf-8')
    // res.end() 将内容响应给客户端
    res.end(str)
})
server.listen(80,() => {
    console.log('server running at http://127.0.0.1');
})

5. 根据不同的url响应不同的html内容

5.1 核心实现步骤
  1. 获取请求的url地址
  2. 设置默认的响应内容为404 Not found
  3. 判断用户请求的是否为// index.html首页
  4. 判断用户请求的是否为/ about.html关于页面
  5. 设置Content-Type响应头,防止中文乱码
  6. 使用res.end()把内容响应给客户端
5.2 动态响应内容
const http = require('http')
const server = http.createServer()

server.on('request',(req,res) => {
    // 1. 获取请求的url地址
    const url = req.url
    // 2. 设置默认的响应内容为 404 Not found
    let content = '<h1>404 Not found</h1>'
    // 3. 判断用户请求的是否为 / 或 /index.html首页
    // 4. 判断用户请求的是否为 /about.html 关于页面
    if(url === '/' || url === '/index.html') {
        content = '<h1>首页</h1>'
    } else if(url === '/about.html') {
        content = '<h1>关于页面</h1>'
    }
    // 5. 设置 Content-Type 响应头,解决中文乱码的问题
    res.setHeader('Content-Type','text/html; charset=utf-8')
    // 6. 使用 res.end()把内容响应给客户端
    res.end(content)
})

server.listen(80,() => {
    console.log('server running at http://127.0.0.1');
})

6. 案例 - 实现clock时钟的web服务器

6.1 核心思路

把文件的实际存放路径作为每个资源的请求url地址

在这里插入图片描述

6.2 实现步骤
  1. 导入需要的模块
  2. 创建基本的web服务器
  3. 将资源的请求url地址隐射为文件的存放路径
  4. 读取文件内容并响应给客户端
  5. 优化资源的请求路径
(1)导入需要的模块
// 1.1 导入http模块
const http = require('http')
// 1.2 导入fs文件系统模块
const fs = require('fs')
// 1.3 导入path路径处理模块
const path = require('path')
(2)创建基本的web服务器
// 2.1 创建web服务器实例
const server = http.createServer()
// 2.2 监听web服务器 request事件,监听客户端的请求
server.on('request',(req,res) => {

})
// 2.3 启动web服务器
server.listen(80,() => {
    console.log('server running at http://127.0.0.1');
})
(3)将资源的请求url地址映射为文件的存放路径
// 3.1 获取到客户端请求的url地址
// /clock/index.html
// /clock/index.css
// /clock/index.js
const url = req.url
// 3.2 把调用的URL地址映射为具体文件的存放路径
const fpath = path.join(__dirname,url)
(4)读取文件的内容并响应给客户端
// 4.1 根据 "隐射"过来的文件路径读取文件
fs.readFile(fpath,'utf8',(err,dataStr) => {
    // 4.2 读取文件失败后,向客户端应固定的 "错误消息"
    if(err) {
        return console.log('404 Not fount.');
    }
    // 4.3 读取文件成功后,将 "读取成功的内容"响应给客户端
    res.end(dataStr)
})
(5)优化资源的请求路径
// 5.1 预定义一个空白的文件存放路径
  let fpath = ''
  if (url === '/') {
    fpath = path.join(__dirname, './clock/index.html')
  } else {
    //     /index.html
    //     /index.css
    //     /index.js
    fpath = path.join(__dirname, '/clock', url)
  }
(6)实现代码
// 1.1 导入 http 模块
const http = require('http')
// 1.2 导入 fs 模块
const fs = require('fs')
// 1.3 导入 path 模块
const path = require('path')

// 2.1 创建 web 服务器
const server = http.createServer()
// 2.2 监听 web 服务器的 request 事件
server.on('request', (req, res) => {
  // 3.1 获取到客户端请求的 URL 地址
  //     /clock/index.html
  //     /clock/index.css
  //     /clock/index.js
  const url = req.url
  // 3.2 把请求的 URL 地址映射为具体文件的存放路径
  // const fpath = path.join(__dirname, url)
  // 5.1 预定义一个空白的文件存放路径
  let fpath = ''
  if (url === '/') {
    fpath = path.join(__dirname, './clock/index.html')
  } else {
    //     /index.html
    //     /index.css
    //     /index.js
    fpath = path.join(__dirname, '/clock', url)
  }

  // 4.1 根据“映射”过来的文件路径读取文件的内容
  fs.readFile(fpath, 'utf8', (err, dataStr) => {
    // 4.2 读取失败,向客户端响应固定的“错误消息”
    if (err) return res.end('404 Not found.')
    // 4.3 读取成功,将读取成功的内容,响应给客户端
    res.end(dataStr)
  })
})
// 2.3 启动服务器
server.listen(80, () => {
  console.log('server running at http://127.0.0.1')
})

五、模块化

1. 模块化的基本概念

1.1 什么是模块化
(1)现实中的模块化

在这里插入图片描述

(2)编程领域中的模块化

编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相赖多个小模块

把代码进行模块化拆分的好处:

  1. 提高了代码的复用性
  2. 提高了代码的可维护性
  3. 可以实现按需加载
1.2 模块化规范

模块化规范就是对代码进行模块化的拆分与组合时,需要遵守的那些规则。

例如:

  • 使用说明样的语法格式来引用模块
  • 在模块中使用什么样的语法格式向外暴露成员

模块化规范的好处:大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的互相调用

2. Node.js中的模块化

2.1 Node.js中模块的分类

Node.js中根据模块来源的不同,将模块分成了3个大类,分别是:

  • 内置模块(内置模块是由Node.js官方提供的,例如 fs、path、http等)
  • 自定义模块(用户创建的每个.js文件,都是自定义模块)
  • 第三方模块由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载
2.2 加载模块

使用强大的require()方法,可以加载需要的内置模块,用户自定义模块、第三方模块进行使用。例如:

// 1. 加载内置的 fs模块
const fs = require('fs')

// 2. 加载用户的自定义模块
const custom = require('./custom.js')

// 3. 加载第三方模块(关于第三方模块的下载和使用)
const moment = require('moment')

注意:使用require()方法加载其他模块时,会执行被加载模块中的代码

2.3 Node.js中的模块作用域
(1)什么是模块作用域

函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访,这种模块级别的访问限制,叫做模块作用域

(2)模块作用域的好处

防止了全局变量污染的问题

在这里插入图片描述

2.4 向外共享模块作用域中的成员
(1)module对象

在每个.js自定义模块中都有一个module对象,他里面存储了和当前模块有关的信息,打印如下:

在这里插入图片描述

(2)module.exports对象

在自定义模块中,可以使用module.exports对象,将模块内的成员共享出去,供外界使用。

外界用require()方法导入自义定模块时,得到的就是module.exports所指的对象

在这里插入图片描述

(3)共享成员时的注意点

使用 require()方法导入模块时,导入的结果,永远以module.exports指向的对象为准

在这里插入图片描述

(4)exports对象

由于module.exports单词写起来比较复杂,为了简化向外共享成员的代码,Node提供了exports对象。默认情况下,exports和module.exports指向同一个对象.最终共享的结果,还是以module.exports指向的对象为准

exports = {
    username: '小米',
    sayHi() {
       console.log('Hi!')
    }
}
  • exports和 module.exports的使用误区

时刻谨记,require()模块时,得到永远是module.exports指向的对象:

2.5 Node.js中的模块化规范

Node.js遵循了CommonJS模块化规范,CommonJS规定了模块的特性各模块之间如何互相依赖

CommonJS规定:

  1. 每个模块内部,module变量代表当前模块
  2. module变量是一个对象,它的exports属性(即module.exports是对外的接口
  3. 加载某个模块,其实是加载该模块的module.exports属性。require()方法用于加载模块

3. npm与包

3.1 包
(1)什么是包

Node.js中的第三方模块又叫做

就像电脑计算机指的是相同的东西,第三方模块指的是同一个概念,只不过叫法不同

(2)包的来源

不同于Node.js中的内置模块与自定义模块,包是由第三方个人或团队开发出来的,免费供所有人使用。

注意:Node.js中的包都是免费且开源的,不需要付费即可免费下载使用

(3)为什么需要包

由于Node.js的内置模块仅提供了一些底层的API,导致在基于内置模块进行项目开发的时,效率很低。

包是基于内置模块封装出来的,提供了更高级、更方便的API,极大的提高了开发效率

内置模块之间的关系,类似于jQuery浏览器内置API之间的关系

(4)从哪里下载包

国外有一家IT公司,叫做npm,lnc.这家公司旗下有一个非常著名的网站:https://www.npmjs.com/,它是全球最大的包共享平台,你可以从这个网站上搜索到任何你需要的包,只要你有足够的耐心!

到目前为止,全球约1100多万的开发人员,通过这个包共享平台,开发并共享了超过120多万个包供我们使用

npm,lnc.公司提供了一个地址为https://registry.npmjs.org/的服务器,来对外共享所有的包,我们可以从这个服务器上下载自己所需要的包

3.2 npm初体验
(1)格式化时间的传统做法
  1. 创建格式化时间的自定义模块
  2. 定义格式化时间的做法
  3. 创建补零函数
  4. 从自定义模块中导出格式化时间的函数
  5. 导入格式化时间的自定义模块
  6. 调用格式化时间的函数
function dataFormat(dtStr) {
    const dt = new Date(dtStr)

    let y = padZero(dt.getFullYear())
    let m = padZero(dt.getMonth() + 1)
    let d = padZero(dt.getDate())

    let hh = padZero(dt.getHours())
    let mm = padZero(dt.getMinutes())
    let ss = padZero(dt.getSeconds())

    return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}

// 定义补零函数
function padZero(n) {
    return n > 9 ? n : '0' + n
}

module.exports = {
    dataFormat
}
(2)格式化时间的高级用法
  1. 使用npm包管理工具,项目中安装格式化时间的包moment
  2. 使用require()导入格式化时间的包
  3. 参考moment的官方API文档对时间进行格式化
(3)在项目中安装包的命令

如果想在项目中安装指定名称的包,需要运行如下的命令:

npm install 包的完整名称

上述的装包命令,可以简写成如下格式:

npm i 完整的包名称
(4)初次装包后多了那些文件

初次装包完成后,在项目文件夹下多一个叫做node_modules的文件夹和package-lock.json的配置文件

其中:

node_modules文件夹用来存放所有已安装到项目中的包。require()导入第三方包时,就是从这个目录中查找并加载包。

package-lock.json配置文件用来记录node_modules目录下的每一个包的下载信息,列如包的名字、版本号、下载地址等

注意:

程序员不要手动修改node_modules或package-lock.json文件中的任何代码,npm包管理工具会自动维护它们

(5)安装指定版本的包

默认情况下,使用npm install 命令安装包的时候,会自动安装最新版本的包,可以在包名之后,通过@符号指定具体的版本,例如:

npm i moment@2.22.2
(6) 包的语义化版本规范

包的版本号是以"点分十进制"形式进行定义的,总共有三位数字,例如2.24.0

其中每一位数字所代表的含义如下:

第1位数字:大版本

第2为数字:功能版本

第3为数字:Bug修复版本

版本号提升的规则:只要前面的版本号增长了,则后面的版本号归零

3.3 包管理配置文件

npm规定,在项目根目录中,必须提供一个叫做package.json的包管理配置文件。用来记录与项目有关的一些配置信息。例如:

  • 项目的名称、版本号、描述等
  • 项目中都用到了那些包
  • 那些包只在开发期间会用到
  • 那些包在开发部署时都需要用到
(1)多人协作的问题

在这里插入图片描述

(2)如何记录项目中安装了那些包

项目根目录中,创建一个叫做package.json的配置文件,即可用来记录项目中安装了那些包。从而方便剔除node_modules目录之后,在团队成员之间共享项目的源代码

注意:今后在项目开发中,一定要把node_modules文件夹,添加到.gitignore忽略文件或在那个

(3)快速创建package.json

npm包管理工具提供了一个快捷命令,可以在执行命令时所处的目录中,快速创建package.json这个包管理配置文件:

// 作用:在执行命令所处的目录中,快速新建package.json文件
npm init -y

注意:

  1. 上述命令只能在英文的目录下成功运行!所以,项目文件夹的名称一定要使用英文命名,不要使用中文,不能出现空格
  2. 运行 npm install命令安装包的时候,npm包管理工具会自动把包的名称版本号,记录岛package.json中
(4) dependencies节点

在这里插入图片描述

package.json 文件中,有一个dependencies节点,专门用来记录您使用npm install命令安装了那些包

(5)一次性安装所有的包

当我们拿到一个剔除了 node_modules的项目之后,需要先把所有的包下载到项目中,才能将项目运行起来。

否者会报类似于下面的错误:

// 由于项目运行依赖于 moment这个包,如果没有提前安装好这个包,就会报如下的错误:
Error: Cannot find module 'moment'

可以运行 npm install命令 (或 npm i)一次性安装所有的依赖包:

// 运行 npm install 命令时,npm包管理工具会先读取 package.json中的 dependencies节点
// 读取到记录的所有依赖包名称和版本号之后, npm 包管理工具会把这些包一次性下载到项目目录中
npm install 
(6)卸载包

可以运行 npm uninstall命令,来卸载指定的包:

// 使用 npm uninstall 具体的包名,来卸载包
npm uninstall moment

注意:npm uninstall 命令执行成功后,会把卸载的包,自动从package.json的dependencies中移除掉

(7)devDependencies节点

如果某些包只在项目开发阶段会用到,在项目上线之后不会用到,则建议把这些包记录到devDependencies节点中。

与之对应的,如果某些包在开发项目上线之后都需要用到,则建议把这些包记录岛dependencies节点中。

您可以使用如下的命令,将包记录到devDependencies节点中:

// 安装指定的包,并记录到devDependencies节点中
npm i 报名 -D
// 注意:上述命令是简写形式,等价与下面完整的写法:
npm install 包名 --save-dev
3.4 解决下包速度慢的问题
(1)为什么下包速度慢

在使用npm下包的时候,默认从国外的https://registry.npmjs.org/服务器进行下载,此时,网络数据的传输需要经过漫长的海底光缆,因此下包速度会很慢

(2)淘宝 NPM镜像服务器

在这里插入图片描述

(3)切换 npm的下包镜像源

下包的镜像源,指的就是下包的服务器地址

# 查看当前的下包镜像源
npm config get registry
# 将下包的镜像源切换为淘宝镜像源
npm config set registry=https://registry.npm.taobdo.org/
# 检查镜像源是否下载成功
npm config get registry
(4)nrm

为了更方便的切换下包的镜像源,我们可以装nrm这个小工具,里面nrm提供的终端命令,可以快速查看和切换下包的镜像源.

# 通过 npm 包管理器,将nrm安装为全局可用的工具
npm i nrm -g
# 查看所有可用的镜像源
npm ls
# 将下包的镜像源切换为 taobao镜像
nrm use taobao
3.5 包的分类

使用npm 包管理工具下载的包,共分为两大类,分别是:

  • 项目包
  • 全局包
(1)项目包

那些被安装到项目node_modules目录中的包,都是项目包

项目包又分为两类,分别是:

  • 开发依赖包(被记录到devDependencies节点中的包,只在开发期间会用到)
  • 核心依赖包(被记录到dependencies节点中的包,在开发期间和项目上线之后都会用到)
npm i 包名 -D #`开发依赖包`(被记录到`devDependencies`节点中的包,只在开发期间会用到)
npm i 包名 # `核心依赖包`(被记录到`dependencies`节点中的包,在开发期间和项目上线之后都会用到)
(2)全局包

在执行 npm install 命令时,如果提供了 -g参数,则会把包安装为全局包

全局包会被安装到C:\User\用户目录\AppData\Roaming\npm\node_modules目录下:

npm i 包名-g # 全局安装指定的包
npm uninstall 包名 -g # 卸载全局安装的包

注意:

  1. 只有工具性质的包,才有全局安装的必要性,因为他们提供了好的终端命令
  2. 判断某个包是否需要全局安装后能使用,可以参考官方提供的使用说明即可
(3)i5ting_toc

i5ting_toc是一个开源把md文档转为html页面的小工具,使用步骤如下:

# 将i5ting_toc 安装诶全局包
npm install -g i5ting_toc
# 调用 i5ting_toc,轻松实现md转html的功能
i5ting_toc -f 要转换的md文件路径 -o
3.6 规范的包结构

在清除了包的概念、以及如何下载和使用包之后,接下来,我们深入了解一下包的内部结构

一个规范的包,他的组成结构,必须符合一下3点要求:

  1. 包必须以单独的目录而存在
  2. 包的顶级目录下要必须包含package.json这个包管理配置文件
  3. package.json 中必须包含 name,version,main这三个属性,分别代表包的名字、版本号、包的入口

注意:以上3点要求是一个规范的包结构必须遵守的格式,关于更多的约束,可以参考如下网址:

https://yarnpkg.com/zh-Hans/docs/package-json

3.7 开发属于自己的包
(1)需要实现的功能
  1. 格式化日期
  2. 转义HTML中的特殊字符
  3. 还原HTML中的特殊字符
// 1. 导入直接的包
const itheima = require('itheima-utils')

// ----功能3:还原HTML中的特殊字符----
const rawHTML = itheima.htmlUnEscape(str)

console.log(rawHTML)
(2)初始化包的基本结构
  1. 新增itheima-tools文件夹,作为包的根目录
  2. 在itheima-tools文件夹中,新建如下三个文件:
  • package.json(包管理配置文件)
  • index.js(包的入口文件)
  • README.md(包的说明文档)
(3)初始化 package.json
{
    "name": "itheima-tools",
    "version": "1.0.0",
    "main": "index.js",
    "description": "提供了格式化时间、HTMLEscape相关的功能",
    "keywords": [
        "itheima",
        "dateFormat",
        "escape"
    ],
    "license": "ISC"
}

关于更多license许可协议相关的内容,可参考https://www.jianshu.com/p/86254523e898

(4)在index.js中定义格式化时间的方法
function dataFormat(dtStr) {
    const dt = new Date(dtStr)

    let y = padZero(dt.getFullYear())
    let m = padZero(dt.getMonth() + 1)
    let d = padZero(dt.getDate())

    let hh = padZero(dt.getHours())
    let mm = padZero(dt.getMinutes())
    let ss = padZero(dt.getSeconds())

    return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}

// 定义补零函数
function padZero(n) {
    return n > 9 ? n : '0' + n
}

module.exports = {
    dataFormat
}
(5)在index.js中定义转义HTML的方法
// 定义转义 HTML字符函数
function htmlEscape(htmlstr) {
    return htmlstr.replace(/<|>|"|&/g, match => {
        switch(match) {
            case '<':
                return "&lt;"
            case '>':
                return "&gt;"
            case '"':
                return "&quot;"
            case '&':
                return "&amp;"
        }
    })
}
(6)在index.js中定义还原HTML的方法
// 定义还原 HTML 字符函数
function htmlUnescape(str) {
    return str.replace(/&lt;|&gt;|&quot;|&amp;/g, match => {
        switch(match) {
            case '&lt;':
                return "<"
            case '&gt;':
                return ">"
            case '&quot;':
                return '"'
            case '&amp;':
                return "&"
        }
    })
}
(7)不同的功能进行模块化拆分
  1. 将格式化时间的功能,拆分到 src > dateFormat.js中
  2. 将处理HTML字符串的功能,拆分到 src > htmlEscape.js中
  3. 在index.js中,导入两个模块,得到需要向外共享的方法
  4. 在index.js中,使用module.exprts把对应的方法共享出去
(8)编写包的说明文档

包根目录中的README.md文件,是包的使用说明文档。通过他,我们可以事先把包的使用说明,以markdown的格式写出来,方便用户参考

README文件中具体写说明内容,没有强制的要求;只要能够清晰地把包的作用、用法、注意事项等描述清除即可。

我们所创建的这个包的README.md文档中,会包含以下6项内容:

安装方式、导入方式、格式化时间、转义HTML中的特殊字符、还原HTML中的特殊字符、开源协议

## 安装
```
npm install itheima-tools
```

## 导入
```js
const itheima = require('itheima-tools')
```

## 格式化时间
```js
// 调用 dateFormat 对时间进行格式化
const dtStr = itheima.dateFormat(new Date())
// 结果  2022-08-04 17:20:58
console.log(dtStr)
```

## 转义 HTML 中的特殊字符
```js
// 带转换的 HTML 字符串
const htmlStr = '<h1 title="abc">这是h1标签<span>123&nbsp;</span></h1>'
// 调用 htmlEscape 方法进行转换
const str = itheima.htmlEscape(htmlStr)
// 转换的结果 &lt;h1 title=&quot;abc&quot;&gt;这是h1标签&lt;span&gt;123&amp;nbsp;&lt;/span&gt;&lt;/h1&gt;
console.log(str)
```

## 还原 HTML 中的特殊字符
```js
// 待还原的 HTML 字符串
const str2 = itheima.htmlUnEscape(str)
// 输出的结果 <h1 title="abc">这是h1标签<span>123&nbsp;</span></h1>
console.log(str2)
```

## 开源协议
ISC
3.8 发布包
(1)注册npm账号
  1. 访问https://www.npmjs.com/网站,点击sign up按钮,进入注册用户界面
  2. 填写账号相关的信息:Full Name、Public Email、Username、Password
  3. 点击Create an Acount按钮,注册账号
  4. 登陆邮箱,点击验证链接,进行账号的验证
(2)登陆npm账号

npm账号注册完成后,可以在终端中执行npm login命令,依次输入用户名、密码、邮箱后,即可登陆成功

(3)把包发布到npm上

将终端切换到包的根目录之后,运行npm publish命令,即可将包发布到npm上 (注意:包名不同雷同)

(4)删除已发布的包

运行npm unpublish 包名 --force命令,即可从npm删除已发布的包.

注意:

  1. npm unpublish命令只能删除72小时以内发布的包
  2. npm unpublish删除的包,在24小时内不允许重复发布
  3. 发布包的时候要慎重,尽量不要往npm上发布没有意义的包!

4. 模块的加载机制

4.1 优先从缓存中加载

模块在第一次加载后悔被缓存。这也意味着多次调用require()不会导致模块的代码被执行多次。

注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率

4.2 内置模块的加载机制

内置模块是由Node.js官方提供的模块,内置模块的加载优先级最高

例如,require(‘fs’)始终返回内置的fs模块,即使在node_modules目录下有名字相同的包也叫做fs。

4.3 自定义模块的加载机制

使用 require() 加载自定义模块时,必须指定以./../开头的路径标识符。在加载自定义模块时,如果没有指定./或…/这样的路径标识符,则node会把他当做内置模块或第三方模块进行加载

同时,在使用require()导入自定义模块时,如果省略了文件的扩展名,则Node.js会按顺序分别尝试加载以下的文件:

  1. 按照确切的文件名进行加载
  2. 补全.js扩展名进行加载
  3. 补全.json扩展名进行加载
  4. 补全.node扩展名进行加载
  5. 加载失败,终端报错
4.4 第三方模块的加载机制

如果传递给require()的模块标识符不是一个内置模块,也没有以’./‘或’…/'开头,则Node。js会从当前模块的父目录开始,尝试从/node_modules文件夹中加载第三方模块

如果没有找到对应的第三方模块,则移动到再上一层父目录或在那个,进行加载,直到文件系统的根目录。

例如,假设在’C:\Users\itheima\project\foo.js’文件里调用了require('tools'),则Node.js会按一下顺序查找:

  1. C:\Users\itheima\projet\node_modules\tools
  2. C:\Users\ithiema\node_modules\tools
  3. C:\Users\node_moduels\tools
  4. C:\node_modules\tools
4.5 目录作为模块

当把目录作为模块标识符,传递给require()进行加载的时候,有三种加载方式:

  1. 在被加载的目录下查找一个叫做package.json的文件,并寻找main属性,作为requier()加载的入口
  2. 如果目录里没有package.json文件,或者main入口不存在或无法解析,则Node.js将会试图加载目录下的index.js文件
  3. 如果以上两步都失败了,则Node.js会在终端打印错误消息,报告模块的缺失: Error: Cannot find module ‘xxx’
    同雷同`)
(4)删除已发布的包

运行npm unpublish 包名 --force命令,即可从npm删除已发布的包.

注意:

  1. npm unpublish命令只能删除72小时以内发布的包
  2. npm unpublish删除的包,在24小时内不允许重复发布
  3. 发布包的时候要慎重,尽量不要往npm上发布没有意义的包!

4. 模块的加载机制

4.1 优先从缓存中加载

模块在第一次加载后悔被缓存。这也意味着多次调用require()不会导致模块的代码被执行多次。

注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率

4.2 内置模块的加载机制

内置模块是由Node.js官方提供的模块,内置模块的加载优先级最高

例如,require(‘fs’)始终返回内置的fs模块,即使在node_modules目录下有名字相同的包也叫做fs。

4.3 自定义模块的加载机制

使用 require() 加载自定义模块时,必须指定以./../开头的路径标识符。在加载自定义模块时,如果没有指定./或…/这样的路径标识符,则node会把他当做内置模块或第三方模块进行加载

同时,在使用require()导入自定义模块时,如果省略了文件的扩展名,则Node.js会按顺序分别尝试加载以下的文件:

  1. 按照确切的文件名进行加载
  2. 补全.js扩展名进行加载
  3. 补全.json扩展名进行加载
  4. 补全.node扩展名进行加载
  5. 加载失败,终端报错
4.4 第三方模块的加载机制

如果传递给require()的模块标识符不是一个内置模块,也没有以’./‘或’…/'开头,则Node。js会从当前模块的父目录开始,尝试从/node_modules文件夹中加载第三方模块

如果没有找到对应的第三方模块,则移动到再上一层父目录或在那个,进行加载,直到文件系统的根目录。

例如,假设在’C:\Users\itheima\project\foo.js’文件里调用了require('tools'),则Node.js会按一下顺序查找:

  1. C:\Users\itheima\projet\node_modules\tools
  2. C:\Users\ithiema\node_modules\tools
  3. C:\Users\node_moduels\tools
  4. C:\node_modules\tools
4.5 目录作为模块

当把目录作为模块标识符,传递给require()进行加载的时候,有三种加载方式:

  1. 在被加载的目录下查找一个叫做package.json的文件,并寻找main属性,作为requier()加载的入口
  2. 如果目录里没有package.json文件,或者main入口不存在或无法解析,则Node.js将会试图加载目录下的index.js文件
  3. 如果以上两步都失败了,则Node.js会在终端打印错误消息,报告模块的缺失: Error: Cannot find module ‘xxx’
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值