前言
兄弟兄弟,想要独自开发做全栈吗,全栈啊!!!
如果是,那么学习nodejs ,就该是你进击全栈的必经之路!
什么是node
简介
Node.js 是一个 javascript运行环境,它让 javascript 可以开发后端程序,实现几乎其他后端语言实现的所有功能这意味着我们只需要掌握 javascript 一门语言就能够进行全栈开发!
Node.js 基于V8引擎,而V8其实是Google发布的开源JavaScript引擎,本身是用于Chrome浏览器的js解释部分,但是总有大佬不按套路
出牌,Node之父Ryan Dahl就把这个V8搬到了服务器上,用于做服务器的软件
Node.js 发布于2009年5月,经过十几年的发展,它已经成为前端中顶梁柱的存在,就算你不需要它进行服务端开发,它也存在于你开发过程中的方方面面,如前面提到的 Vue, React ,Webpack
特性
- Node.js 贯穿于客户端 (如 Vue 这类框架的底层依赖) 和服务端 (如后端开发)
- Node.js 的语法完全就是
javascript和ES6的语法 - Node.js 具有超强的高并发能力,能够实现高性能服务器
- 开发周期短、开发成本低、学习成本低
单论后端开发,Node.js 作为 Javascript 的运行环境的性能虽不能与 Java 这类编译语言相比,但它独有的特性完全可以弥补这性能的差距,其至能够进行超越这也就是为什么它能够发展如此之快,能够受到越来越多人青睐的原因!看到这里你可能已经明白,为什么我会说 Node 是前端到全栈的必经之路!
Node环境搭建
官网下载:Node.js官网
进入官方网站,根据自己的需要去下载(建议下载稳定版本)

下载到本地,双击运行,一路 next 即可
在中端中输入如下命令:
node -v
npm -v
出现对应的版本号,即搭建完成,可以畅快体验 node 了

fs 文件操作模块
1. 读取文件
调用 fs.readFile 方法读取文件
- 参数一:读取文件的存放路径
- 参数二:读取文件时候采用的编码,一般默认为UTF-8
- 参数三:回调函数,拿到读取失败和成功的结果error,dataStr
//导入fs模块,来操作文件
const fs = require('fs')
fs.readFile('./test1.js','utf-8',(error,dataStr) => {
console.log(error)
console.log('-------')
console.log(dataStr)
})
在终端进入该目录下,调用node ./test.js命令

2. 写入文件
调用 fs.writeFile 方法读取文件
- 参数一:写入文件的路径
- 参数二:要写入的数据
- 参数三:写入文件时候采用的编码,一般默认为UTF-8
- 参数四:回调函数,拿到写入失败的结果error,写入成功结果为null
fs.writeFile('./test2.js','hello node.js', 'utf-8', (error) => {
if (error) return console.log('文件写入失败:'+error.message)
console.log('文件写入成功')
})
同上,执行 node ./test.js 命令
若写入的目标文件不存在,则会自动创建,但不支持创建目录;若存在则会对目标文件的内容进行覆盖

3. fs模块小练习
考试成绩整理
目标:
整理前:
小红=99 小白=100 小黄=70 小黑=66 小绿=88
整理后:
小红:99
小白:100
小黄:70
小黑:66
小绿:88
业务逻辑代码 |
//导入fs模块
const fs = require('fs')
fs.readFile('./score.txt','utf-8',(error,dataStr) => {
if (error) return console.log('读取成绩文件失败')
console.log('读取文件成功')
//对读取到的数据进行拆分,空格拆分
let scoreList = dataStr.split(' ')
//创建新的数组,用来保存处理后的数据
let scoreListNew = []
scoreList.forEach(item => {
//用 : 替换 =
scoreListNew.push(item.replace('=',':'))
})
//把列表用 换行符(\n) 拆分为一个字符串,写入文件
fs.writeFile('./scoreNew.txt',scoreListNew.join('\n'),'utf-8',(err) => {
if (err) return console.log('写入文件失败')
console.log('写入文件成功')
})
})
运行结果:

零碎芝士
__dirname 当前文件所处的路径
console.log(__dirname)
//C:\...\src\demo2
path 路径模块
path.join()方法,用来将多个路径片段拼接成一个完整的路径字符串path.basename()方法,用来从路径字符串中,将文件名解析出来path.extname()方法,用来获取文件的拓展名
path.join() 方法:
const fs = require('fs')
const path = require('path')
// ../会抵消一层路径
const pathStr = path.join('/a','/b/c','../','./','/d')
console.log(pathStr)// \a\b\d
const pathStr2 = path.join(__dirname, '../demo2/score.txt')
console.log(pathStr2)
//C:\...src\demo2\score.txt
fs.readFile(pathStr2, 'utf-8', (error, dataStr) => {
if (error) return console.log('读取文件失败:' + error.message)
console.log('文件读取成功:' + dataStr)
})
path.basename() 方法:
//将路径的文件名称解析出来
const pathStr3 = path.basename(pathStr2)
console.log(pathStr3)//score.txt
//将路径的文件名称解析出来,写入第二个拓展名参数,会只得到文件名
const pathStr4 = path.basename(pathStr2,'.txt')
console.log(pathStr4)//score
path.extname 方法:
//获取文件的拓展名
const pathStr5 = path.extname(pathStr2)
console.log(pathStr5)//.txt
//获取路径的文件名
const pathStr6 = path.basename(pathStr2,path.extname(pathStr2))
console.log(pathStr6)//score
path模块小练习
目标:将一个html+css+javascript的代码结构,拆分为三个模块,并引入css和javascript
匹配 <style></style> 标签的正则及文件内容读取和处理
const fs = require('fs')
const path = require('path')
//其中 \s表示空白符,\S表示非空白符;*表示匹配任意次
const regStyle = /<style>[\s\S]*<\/style>/
//匹配<script></script>标签的正则
const regScript = /<script>[\s\S]*<\/script>/
fs.readFile(path.join(__dirname, './index.html'), 'utf-8', (err, dataStr) => {
if (err) return console.log('读取数据失败')
console.log(dataStr)
resolveCss(dataStr)
resolveJs(dataStr)
resolveHtml(dataStr)
})
css 样式处理方法
function resolveCss(htmlStr) {
const r1 = regStyle.exec(htmlStr)
const newCss = r1[0].replace('<style>','').replace('</style>','')
//将提取出来的css样式写入到css文件里
fs.writeFile(path.join(__dirname,'./index.css'),newCss,'utf-8',(err) => {
if (err) return console.log('写入数据失败')
console.log('写入数据index.css成功')
})
}
script 处理方法
function resolveJs(htmlStr) {
const r2 = regScript.exec(htmlStr)
const newJs = r2[0].replace('<script>','').replace('</script>','')
//将提取出来的js写入到js文件里
fs.writeFile(path.join(__dirname,'./index.js'),newJs,'utf-8',(err) => {
if (err) return console.log('写入数据失败')
console.log('写入数据index.js成功')
})
}
定义处理 html 结构的方法
function resolveHtml(htmlStr) {
// console.log(regStyle)
// console.log(regScript)
const newHtml = htmlStr.replace(regStyle,'<link rel=stylesheet href="./index.css"/>').replace(regScript,'<script type="text/javascript" src="./index.js"/>')
// console.log(newHtml)
fs.writeFile(path.join(__dirname,'./index.html'),newHtml,'utf-8',(err) => {
if (err) return console.log('写入newHtml数据失败')
console.log('写入newHtml数据成功')
})
}
http 模块
创建web服务实例 http.createServer()
请求路径:res.url
请求方法:res.method
端口号:8090
防止中文显示乱码的问题,需要设置响应头Content-Type值为:text/html;charset=utf-8:
resp.setHeader('Content-Type','text/html;charset=utf-8')
向客户端发送指定内容,并结束这次的请求的处理过程:resp.end()
//导入http模块
const http = require('http')
//创建web服务实例
const server = http.createServer()
//为服务器实例绑定request事件,监听客户端的请求
server.on('request', (res,resp) => {
// console.log('someone visit out web server')
console.log(`请求路径为:${res.url},请求方式为:${res.method}`)
resp.setHeader('Content-Type','text/html;charset=utf-8')
//resp.end()方法,向客户端发送指定内容,并结束这次的请求的处理过程
resp.end(`请求路径为:${res.url},请求方式为:${res.method}`)
})
//启动服务器
server.listen(8090, () => {
console.log('server running at http://127.0.0.1:8090')
})
根据请求的url进行相应的设定响应
const http = require('http')
const server = http.createServer()
server.on('request', (res,resp) => {
const url = res.url
let content = '<h2>404 Not found!</h2>'
if (url == '/' || url == '/index.html') {
content = '<h2>首页</h2>'
} else if (url == '/about.html') {
content = '<h2>关于页面</h2>'
}
resp.setHeader('Content-Type','text/html;charset=utf-8')
resp.end(content)
})
server.listen(8099, () => {
console.log('server running at http://127.0.0.1:8099')
})
http 模块小案例
将上面拆分好的 html、css、JavaScript 模块进行加载响应
请求空地址或index.html页面时,均加载 index.html 页面显示
其他不存在页面响应为: 404 Not found
//导入http模块
const http = require('http')
//导入fs模块
const fs = require('fs')
//导入path模块
const path = require('path')
//创建web服务器
const server = http.createServer()
//监听web服务器的request事件
server.on('request', (req,resp) => {
//预定义空白的文件存放路径
fpath = ''
//获取到用户请求的url地址
const url = req.url
if (url == '/') {
fpath = path.join(__dirname, '/clock/index.html')
} else {
fpath = path.join(__dirname, '/clock'+url)
}
//根据映射过来的文件路径读取文件
fs.readFile(fpath,'utf-8',(err,dataStr) => {
//读取文件失败,进行相应404
if (err) return resp.end('<h1>404 Not fount</h1>')
resp.end(dataStr)
})
})
//启动服务器
server.listen(8099, () => {
console.log('server running at http://127.0.0.1:8099')
})
CommonJS 部分
每个模块内部,module 变量代表当前模块。
module 变量是一个对象,它的exports 属性(即 module.exports )是对外的接口
加载某个模块,其实是加载该模块的module.exports 属性。require() 方法用于加载模块
模块暴露使用
自定义一个模块,并暴露属性和方法
// module.exports对象上挂载 username 属性
module.exports.username = 'ls'
// module.exports对象上挂载 hello 函数
module.exports.hello = function () {
console.log('你好:'+username)
}
定义之后未进行暴露,该属性为模块内部的私有成员
//模块内部的私有成员
let age = 20
module.exports 指向一个全新的对象
module.exports = {
name: 'zs',
age: 20,
hobby: ['吃饭', '睡觉', '喝酒'],
say() {
console.log('hello')
}
}
调用模块并使用模块内的方法、属性
const custom = require('./自定义模块')
console.log(custom)

exports 和 module.exports 的使用误区
本质上,module.exports 和 exports 指向的是同一块内存地址
module.exports 指向一个全新的对象
exports.username = 'zs'
module.exports = {
gender: '男',
age: 20
}
//暴露出去的为:{gender: '男', age: 20}
exports 指向一个全新的对象
module.exports.username = 'zs'
exports = {
gender: '男',
age: 20
}
//暴露出去的为:{username: 'zs'}
共同为指向的内存地址添加属性
exports.username = 'zs'
module.exports.gender = '女'
//暴露出去的为:{username: 'zs', gender: '女'}
为 module.exports 添加一块新的对象
exports = {
username: 'zs',
gender: '男'
}
module.exports = exports
module.exports.age = 19
//暴露出去的为:{username: 'zs', gender: '男', age: 19}
一句解决:使用require 模块时,得到的永远是 module.exports 指向的对象
注意:为了防止混乱,建议不要在同一个模块中同时使用 exports 和 module.exports
npm 与包
从哪里下载包
国外有一家IT 公司,叫做 npm ,Inc 这家公司旗下有一个非常著名的网站: npm官网

它是全球最大的包共享平台,你可以从这个网站上搜索到任何你需要的包,只要你有足够的耐心!到目前为止,全球约1100多万的开发人员,通过这个包共享平台,开发并共享了超过 120 多万个包 供我们使用 npm , Inc. 公司 提供了一个服务器 ,来对外共享所有的包,我们可以从这个服务器上下载自己所需要的包
注意:
从 https://www.npmjs.com/ 网站上搜索自己所需要的包
从https://registry.npmjs.org/ 服务器上下载自己需要的包
如何下载包
npm , Inc.公司 提供了一个包管理工具,我们可以使用这个包管理工具,从 服务器 把需要的包下载到本地使用。
这个包管理工具的名字叫做 Node Package Manager (简称 npm 包管理工具),这个包管理工具随着 Nodejs 的安装包一起被安装到了用户的电脑上
npm 安装包命令(以 moment 包为例)
npm i moment
安装指定版本的包
npm i moment@2.24.0 (包名后跟@符+版本号,若该包已存在会进行覆盖)
包的语义化版本规范:
包的版本号是以“点分十进制”形式进行定义的,总共有三位数字,例如 2.24.0
其中每一位数字所代表的的含义如下
- 第1位数字:
大版本 - 第2位数字:
功能版本 - 第3位数字:
Bug修复版本
版本号提升的规则: 只要前面的版本号增长了,则后面的版本号归零
使用 moment 包
可以在 npm官网 找到 moment 包

进入Moment.js Documentation 学习使用

在本地安装moment包

自定义时间格式化模块和已经写好的moment模块
自定义时间格式化模块
//定义格式化时间的方法
function dateFormat(dtStr) {
const dt = new Date(dtStr)
const year = dt.getFullYear()
const month = padZero(dt.getMonth() + 1)
const date = padZero(dt.getDate())
const hh = padZero(dt.getHours())
const mm = padZero(dt.getMinutes())
const ss = padZero(dt.getSeconds())
return `${year}-${month}-${date} ${hh}:${mm}:${ss}`
}
//定义补零的函数
function padZero(n) {
return n > 9 ? n : '0' + n
}
//暴露出去
module.exports = {
dateFormat
}
调用自己的自定义时间格式化模块
const TIME = require('./dateFormat')
// console.log(TIME)
// console.log(new Date())
console.log(TIME.dateFormat(new Date()))
输出结果:

根据moment官方使用文档,简单用代码获取一个的当前时间
//导入moment
const moment = require('moment')
//调用format方法
const dt = moment().format('YYYY-MM-DD HH:mm:ss')
console.log(dt)
运行结果

自定义的时间模块代码量大,开发效率低;使用第三方成型的模块开发,书写少量代码,开发效率更高
初次安装后多了哪些文件
初次装包完成后,在项目文件夹下多一个叫做 node_modules 的文件夹和 pakage-lock.json 的配置文件
其中:
node_modules 文件夹用来存放所有已安装到项目中的包。require() 导入第三方包时,就是从这个目录中查找并加载包package-lock.json 配置文件用来记录 node_modules 目录下的每一个包的下载信息,例如包的名字、版本号、下载地址等信息
我们要知道一件事情
包的数据内容较大,不利于我们的网络传输,所以一般在传输项目文件时,都会把node_modules 文件剔除再进行网络传输,这就需要我们有一个文件来记录我们项目中都使用过哪些包,这样,别人在接收后才可以下载相应的包,来保证项目的正常运行,这就用到一个文件package.json ,里面的dependencies节点记录了使用的相关的包和版本号
{
"name": "nodejs",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"moment": "^2.24.0"
}
}
快速新建 package.json 文件
npm init -y
注意:
- 上述命令只能在 英文的目录 下成功运行!
- 项目文件夹的名称一定要使用 英文命名,不要使用中文,不能出现空格
- 运行
npm install命令安装包的时候, npm包管理工具会自动把包的名称和版本号,记录到package.json中
安装多个包
在npm i 后包之间用空格隔开
npm i jquery art-template
一次性安装项目所需的所有依赖包
当我们拿到一个剔除了node_modules 的项目之后,
想要一次性安装package_json中dependencies所有依赖包
可以执行命令:
npm install
或(npm i)
卸载包
npm uninstall 包名
devDependencies 节点
若某些包在开发阶段会用到,项目上线后不会再用,则建议把这些包记录到 devDependencies 节点中
对于项目开发阶段和上线阶段都会用到的则记录到 dependencies 节点中
可以使用命令如下,将包记录到devDependencies 节点中
安装指定的包并记录到devDependencies 节点中:
npm i 包名 -D
上述命令为简写形式,等价于下面的完整写法:
npm install 包名 --save-dev
解决下载包速度慢问题
1.为什么下包速度慢
在使用 npm 下包的时候,默认从国外的 https://registry.npmjs.org/ 服务器进行下载
此时,网络数据的传输需要经过漫长的 海底光缆 ,因此下包速度会很慢
淘宝NPM镜像服务器
淘宝在国内搭建了一个服务器,专门把国外官方服务器上的包同步到国内的服务器,
然后在国内提供下包的服务从而极大地提高了下包的速度
扩展:
镜像(Mirroring)是一种文件存储形式,一个磁盘上的数据在另一个磁盘上存在一个完全相同的副本即为镜像
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lZdBS9TD-1670723245848)(img_2.png)]](/https://i-blog.csdnimg.cn/blog_migrate/92cf82e897964d3ada9d0c42ef888560.png)
切换 npm 的下包镜像源
下包的镜像源,指的就是下包的服务器地址
# 查看当前的下包镜像源
npm config get registry
# 将下包的镜像源切换为淘宝镜像
npm config set registry https://registry.npm.taobao.org/
# 检查镜像资源是否切换成功
npm config get registry
nrm工具
为了更方便的切换下包的镜像源,我们可以安装 nrm 这个小工具
利用 rm 提供的终端命令,可以快速查看和切换下包的镜像源
# 通过 npm 包管理器,将 nrm 安装为全局可用的工具
npm i nrm -g
#查看所有可用的镜像源
nrm ls
# 将下包的镜像源切换为 taobao 镜像
nrm use taobao
包的分类
项目包
那些被安装到项目的 node_modules 目录中的包,都是项目包
项目包又分为两类,分别是:
开发依赖包(被记录到 devDependencies 节点中的包,只在开发期间会用到)
核心依赖包(被记录到 dependencies 节点中的包,在开发期间和项目上线之后都会用到)
npm i 包名 -D # 开发依赖包 (会被记录到 devDependencies 节点下)
npm i 包名 # 核心依赖包 (会被记录到 dependencies 节点下)
全局包
在执行 npm install 命令时,如果提供了-g 参数,则会把包安装为全局包
全局包会被安装到 C:\Users\用户目录\AppData\Roaming\npm\node_modules 目录下.
npm i package_name -g # 全局安装指定的包
npm uninstall package_name -g # 卸载指定的安装全局的包
注意:
只有工具性质的包,才有全局安装的必要性。因为它们提供了好用的终端命令;
判断某个包是否需要全局安装后才能使用,可以参考官方提供的使用说明即可
i5ting_toc
i5ting_toc 是一个可以把md 文档转为html页面的小工具,使用步骤如下:
# 将 i5ting_toc 安装为全局包
npm i i5ting_toc -g
# 调用 i5ting_toc ,实现 md 转 html 功能
i5ting -f filePath -o
开发属于自己的包
新建一个包目录(pac_tolls),包含以下三个文件:
- index.js
- package.json
- README.md
初始化package.json
{
"name": "pac_tools",
"version": "1.0.0",
"main": "index.js",
"description": "提供了格式化时间, html Escape的功能",
"keywords": ["hello", "dateFormat", "htmlEscape", "htmlUnEscape"],
"license": "ISC"
}
将不同的功能进行模块化拆分
- 将格式化时间的功能,拆分到
src -> dateFormat.js中; - 将处理
HTML字符串的功能,拆分到src -> htmlEscape.js中; - 在
index.js中,导入两个模块,得到需要向外共享的方法在index.js中; - 使用
module.exports把对应的方法共享出去
src/dateFormat.js
//格式化时间的方法
function dateFormat(dataStr) {
const dt = new Date(dataStr)
const year = dt.getFullYear()
const month = padZero(dt.getMonth() + 1)
const date = padZero(dt.getDate())
const hh = padZero(dt.getHours())
const mm = padZero(dt.getMinutes())
const ss = padZero(dt.getSeconds())
return `${year}-${month}-${date} ${hh}:${mm}:${ss}`
}
//补零的方法
function padZero(n) {
return n > 9 ? n : '0' + n
}
module.exports = {
dateFormat
}
src/htmlEscape.js
//转换html的方法
function htmlEscape(htmlStr) {
return htmlStr.replace(/<|>|"|&/g, (match) => {
switch (match) {
case '<':
return '<'
case '>':
return '>'
case '"':
return '"'
case '&':
return '&'
}
})
}
//还原html的方法
function htmlUnEscape(str) {
return str.replace(/<|>|"|&/g, (match) => {
switch (match) {
case '<':
return '<'
case '>':
return '>'
case '"':
return '"'
case '&':
return '&'
}
})
}
module.exports = {
htmlEscape,
htmlUnEscape
}
index.js
/*包的入口文件*/
const date = require('./src/dateFormat')
const escape = require('./src/htmlEscape')
//hello world
function hello() {
console.log('hello world')
}
//对外暴露
module.exports = {
hello,
/*dateFormat: date.dateFormat,
htmlEscape: escape.htmlEscape,
htmlUnEscape: escape.htmlUnEscape*/
...date,
...escape
}
编写包的说明文档
包根目录中的 README.md 文件,是包的使用说明文档;
通过它,我们可以事先把包的使用说明,以 markdown 的格式写出来,方便用户参考
README 文件中具体写什么内容,没有强制性的要求,只要能够清晰地把包的作用、用法、注意事项等描述清楚即可
我们所创建的这个包的 README.md 文档中,会包含以下6项内容:
- 安装方式、
- 导入方式、
- 格式化时间、
- 转义HTML中的特殊字符、
- 还原HTML中的特殊字符、
- 开源协议
## 安装
npm install pac_tools
## 导入
const pac_tools = require('pac_tools')
## 格式化时间
//调用dateFormat 对时间进行格式化
const dateStr = pac_tools.dateFormat(new Date())
console.log(dateStr)
//结果:2022-12-05 12:05:48
## 转义 html 中的特殊字符
//定义待转换的html字符串
const htmlStr = '<h1>hello<span>jsl</span></h1>'
//调用htmlEscape转换方法
const str = pac_tools.htmlEscape(htmlStr)
console.log(str)
// 结果: <h1>hello<span>jsl</span></h1>
## 还原 html 中的特殊字符
//定义待转换的html字符串
const htmlStr = '<h1>hello<span>jsl</span></h1>'
//调用htmlEscape转换方法
const str = pac_tools.htmlEscape(htmlStr)
//调用htmlUnEscape还原方法
console.log(pac_tools.htmlUnEscape(str))
// 结果: <h1>hello<span>jsl</span></h1>
## 开源协议
ISC
发布包
- npm官网注册账号
- 登录npm账号
npm 账号注册完成后,可以在终端中执行 npm login 命令;
依次输入用户名、密码(盲打)、邮箱后,邮箱会收到一个验证码,输入校验码,即可登录成功
注意:
在运行npm login 命令之前,必须先把下包的服务器地址切换为 npm 的官方服务器。否则会导致发布包失败!
//查看切换服务器
npm config get registry
//切换服务器为npm服务器
nrm use npm
登录成功

发布
进入要发布的包的根目录,执行命令:
npm publish
发布完成:

删除发布的包
运行:
npm unpublish 包名 --force
命令,即可从 npm 删除已发布的包
煮雨
npm unpublish命令只能删除 72 小时以内发布的包npm unpublish删除的包,在24小时内不许重复发布- 发布包的时候要慎重,尽量不要往
npm上发布没有意义的包!
第三方模块的加载机制
如果传递给 require() 的模块标识符不是一个内置模块,也没有以 ./ 或 ../ 开头,则 Node.js 会从当前模块的父目录开始,尝试从 /node_modules 文件夹中加载第三方模块。
如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录.例如,假设在 C:Users\itheima\project foojs' 文件里调用了 require('tools'),则 Node,js 会按以下顺序查找:
C:\Users\itheima\project\node_modules\toolsC:\Users\itheima\node_modules\toolsC:\Users\node_modules\toolsC: node_modules\tools
当把目录作为模块标识符
当把目录作为模块标识符,传递给 require() 进行加载的时候,有三种加载方式:
- 在被加载的目录下查找一个叫做
package.json的文件,并寻找main属性,作为require()加载的入口 - 如果目录里没有
package.json文件,或者main入口不存在或无法解析,则Nodejs将会试图加载目录下的index.js文件 - 如果以上两步都失败了,则
Node.js会在终端印错误消息,报告模块的缺失:Error: Cannot find module xx

2092

被折叠的 条评论
为什么被折叠?



