【node学习】node.js

一、node与浏览器的区别
1、生态系统不同
没有document、window以及其他对象(比如cookies)
2、运行环境
在node中,可以选择运行环境,不需要像浏览器端那样去做适配。
3、模块系统不同
node中使用的是CommonJS,浏览器端使用的是ES的模块系统。
简单来讲就是node,使用require()而不是import

二、V8引擎
1、关于js引擎
V8提供了执行 JavaScript 的运行时环境。 DOM 和其他 Web 平台 API 则由浏览器提供。
JavaScript 引擎独立于托管它的浏览器。
V8也是node的引擎。chromium是chrome的js引擎,spiderMonkey是Firefox的引擎,等等…
V8 还为桌面应用程序(通过 Electron 等项目)提供支持。Electron 可以让你使用纯 JavaScript 调用丰富的原生 APIs 来创造桌面应用。
2、编译
JavaScript 是由 V8 在其内部编译的,使用了即时(JIT)编译以加快执行速度。

三、运行node脚本与退出
node 文件名.js 运行node脚本
node的主程序是app.js
process不需要引入
退出node process.exit(1)
正常终止 process.kill(process.pid, ‘SIGTERM’) terminate?
立即终止process.kill(process.pid, 'SIGKILL) kill?

四、读取环境变量
Node.js 的 process 核心模块提供了 env 属性,该属性承载了在启动进程时设置的所有环境变量。
process.env.NODE_ENV // “development”
node自动将环境变量设置成development,在脚本运行前可以将其设置成production,表明是生产环境

五、使用node.js REPL
1、REPL
REPL 也被称为运行评估打印循环,是一种编程语言环境(主要是控制台窗口),它使用单个表达式作为用户输入,并在执行后将结果返回到控制台。
就相当于单步执行的环境,跟浏览器的console几乎一样。
在这里插入图片描述
第一个值 测试 是告诉控制台要打印的输出,然后得到 undefined,它是运行 console.log() 的返回值。

2、tab自动补全
想查看Number类的成员,输入Number.,再按tab
在这里插入图片描述

如果是查看全局成员,输入global.,再按tab
在这里插入图片描述
3、点命令
REPL 有一些特殊的命令,所有这些命令都以点号 . 开头。它们是:
.help: 显示点命令的帮助。
.editor: 启用编辑器模式,可以轻松地编写多行 JavaScript 代码。当处于此模式时,按下 ctrl-D 可以运行编写的代码。
.break: 当输入多行的表达式时,输入 .break 命令可以中止进一步的输入。相当于按下 ctrl-C。
.clear: 将 REPL 上下文重置为空对象,并清除当前正在输入的任何多行的表达式。
.load: 加载 JavaScript 文件(相对于当前工作目录)。
.save: 将在 REPL 会话中输入的所有内容保存到文件(需指定文件名)。
.exit: 退出 REPL(相当于按下两次 ctrl-C)。

六、从命令行接收参数

七、输出到命令行
原来js也有格式化输出…
console.log(‘我的%s已经%d岁’, ‘猫’, 2)//我的猫已经2岁了
%s 会格式化变量为字符串
%d 会格式化变量为数字
%i 会格式化变量为其整数部分
%o 会格式化变量为对象

console.clear()清空控制台
console.count()
在这里插入图片描述
console.trace()打印堆栈信息

相当于一个定时器
console.time()
console.timeEnd()

console.log()打印到了标准输出流(stdout)
console.error()会将信息报到错误日志中

甚至可以安装一个chalk库来改变标准输出流的颜色

八、从命令行中接受输入

九、使用exports公开功能
在这里插入图片描述
在这里插入图片描述
module.exports 和 export 之间有什么区别?
前者公开了它指向的对象。 后者公开了它指向的对象的属性。

引用模块
在这里插入图片描述
十、npm简介
如果具有package.json,可以运行npm.install 在node_modules文件夹中安装项目所需的所有依赖
npm install < package-name>安装特定的包,通常会添加下面的标志
–save 安装并添加条目到 package.json 文件的 dependencies。
–save-dev 安装并添加条目到 package.json 文件的 devDependencies。
区别主要是,devDependencies 通常是开发的工具(例如测试的库),而 dependencies 则是与生产环境中的应用程序相关。
npm update检查所有软件包是否满足版本限制的更新版本
npm update < package-name>

npm run会执行 package.json文件中scripts对象中的脚本(主要是简便命令的长度)

十一、npm将软件包安装到哪里
默认情况下,npm install就会将软件包安装到node_modules中
如果使用 -g标志则执行的是全局安装。
在这里插入图片描述
npm root -g会告知全局环境在计算机上的位置
注意:如果使用nvm安装node,则位置不确定

十二、如何使用npm或者执行npm安装的软件包
如果只是在代码中使用,require一下就好
如果是可执行文件,输入其路径即可
./node_modules/.bin/cowsay
注意: .bin 文件夹,其中包含指向 cowsay 二进制文件的符号链接
当然也可以使用新的指令npx,比如npx cowsay,npx会自动找到程序包的位置

十三、package.json指南
简介
package.json 文件是项目的清单。它是用于工具的配置中心,也是 npm 和 yarn 存储所有已安装软件包的名称和版本的地方。
对于应用程序,package.json 文件中的内容没有固定的要求。 唯一的要求是必须遵守 JSON 格式,否则,尝试以编程的方式访问其属性的程序则无法读取它。
version 表明了当前的版本。
name 设置了应用程序/软件包的名称。
description 是应用程序/软件包的简短描述。
main 设置了应用程序的入口点。
private 如果设置为 true,则可以防止应用程序/软件包被意外地发布到 npm。
scripts 定义了一组可以运行的 node 脚本。
dependencies 设置了作为依赖安装的 npm 软件包的列表。
devDependencies 设置了作为开发依赖安装的 npm 软件包的列表。
engines 设置了此软件包/应用程序在哪个版本的 Node.js 上运行。
browserslist 用于告知要支持哪些浏览器(及其版本)

属性
1、name:软件包的名称
名称必须少于 214 个字符,且不能包含空格,只能包含小写字母、连字符(-)或下划线(_)。
这是因为当软件包在 npm 上发布时,它会基于此属性获得自己的 URL。
如果在 GitHub 上公开地发布此软件包,则 GitHub 仓库的名称是作为此属性的不错选择。
2、author:软件包作者名称(可以嵌套)
3、contributors:软件包除了作者外的贡献者,是一个数组
4、bugs:链接到软件包的问题跟踪器,最常用的是github的issues页面
5、homepage:软件包的主页
6、version:软件包当前版本
此属性遵循版本的语义版本控制记法,这意味着版本始终以 3 个数字表示:x.x.x。
第一个数字是主版本号,第二个数字是次版本号,第三个数字是补丁版本号。
仅修复缺陷的版本是补丁版本,引入向后兼容的更改的版本是次版本,具有重大更改的是主版本。
7、license:软件包的许可证
8、keywords:包含和软件包功能相关的关键字数组
9、description:对软件包的简短描述
10、repository:软件包仓库的位置
11、main:设置软件包的入口
当在应用程序中导入此软件包时,应用程序会在该位置搜索模块的导出。
比如 “src/main.js”
12、private:可以设置成true,防止软件包被意外发到npm上
13、定义一组可以运行的node脚本
14、dependencies:作为依赖安装的npm、yarn软件包的列表
15、devDependencies:作为开发依赖安装的npm和yarn软件包的列表。
16、engines:使用的node.js的版本,也包含npm、yarn等版本
17、browserslist:告知支持哪些浏览器(及其版本),Babel、Autoprefixer 会用到这个

十四、package-lock.json
在版本5以后,npm引入了package-lock,json文件
该文件旨在跟踪被安装的每个软件包的确切版本,以便产品可以以相同的方式被 100% 复制(即使软件包的维护者更新了软件包)。
semver 表示法:
如果写入的是 〜0.13.0,则只更新补丁版本:即 0.13.1 可以,但 0.14.0 不可以。
如果写入的是 ^0.13.0,则要更新补丁版本和次版本:即 0.13.1、0.14.0、依此类推。
如果写入的是 0.13.0,则始终使用确切的版本。
无需将 node_modules 文件夹(该文件夹通常很大)提交到 Git,当尝试使用 npm install 命令在另一台机器上复制项目时,如果指定了 〜 语法并且软件包发布了补丁版本,则该软件包会被安装。 ^ 和次版本也一样。

原始的项目(git clone)和新初始化(npm install)的项目实际上是不同的。 即使补丁版本或次版本不应该引入重大的更改,但还是可能引入缺陷。
package-lock.json 会固化当前安装的每个软件包的版本,当运行 npm install时,npm 会使用这些确切的版本。
package-lock.json 文件需要被提交到 Git 仓库,以便被其他人获取(如果项目是公开的或有合作者,或者将 Git 作为部署源)。
当运行 npm update 时,package-lock.json 文件中的依赖的版本会被更新。

其实package-lock就是锁定安装时的包版本号,需要上传到git上,以保证其他人在install时候,大家的依赖版本相同。
官方文档:这个package-lock.json 是在 npm install时候生成一份文件,用以记录当前状态下实际安装的各个npm package的具体来源和版本号.

每一次install,就更新一次lock?
package-lock.json很大是因为每个软件包也有自己的依赖,都要统一起来版本。
参考文章

十五、查看npm安装的软件包
npm list查看当前目录软件包及其依赖
npm list -g查看全居软件包及其依赖
npm list --depth=0仅查看顶层软件包
npm list < package-name>查看软件包及其依赖
npm view [package_name] version查看软件包的版本

十六、安装旧版本npm软件包
npm install < package>@< version> 安装< version>版本的< package>
npm view < package> versions 查看可以安装的版本

十七、将所有node依赖包安装到最新版本
npm update
npm outdated 查看软件包的新版本
在这里插入图片描述
若要将所有软件包更新到新的主版本,则全局地安装 npm-check-updates 软件包:
npm install -g npm-check-updates
然后运行:
ncu -u
这会升级 package.json 文件的 dependencies 和 devDependencies 中的所有版本,以便 npm 可以安装新的主版本。
现在可以运行更新了:
npm update

十八、npm版本
^: 只会执行不更改最左边非零数字的更新。 如果写入的是 ^0.13.0,则当运行 npm update 时,可以更新到 0.13.1、0.13.2 等,但不能更新到 0.14.0 或更高版本。 如果写入的是 ^1.13.0,则当运行 npm update 时,可以更新到 1.13.1、1.14.0 等,但不能更新到 2.0.0 或更高版本。
~: 如果写入的是 〜0.13.0,则当运行 npm update 时,会更新到补丁版本:即 0.13.1 可以,但 0.14.0 不可以。
aa >: 接受高于指定版本的任何版本。
aa >=: 接受等于或高于指定版本的任何版本。
< =: 接受等于或低于指定版本的任何版本。
< : 接受低于指定版本的任何版本。
=: 接受确切的版本。
-: 接受一定范围的版本。例如:2.1.0 - 2.6.2。
||: 组合集合。例如 < 2.1 || > 2.6。
可以合并其中的一些符号,例如 1.0.0 || >=1.1.0 <1.2.0,即使用 1.0.0 或从 1.1.0 开始但低于 1.2.0 的版本。
还有其他的规则:
无符号: 仅接受指定的特定版本(例如 1.2.1)。
latest: 使用可用的最新版本。

十九、卸载npm包
npm uninstall < package-name>
如果使用 -S 或 --save 标志,则此操作还会移除 package.json 文件中的引用。
如果程序包是开发依赖项(列出在 package.json 文件的 devDependencies 中),则必须使用 -D 或 --save-dev 标志从文件中移除:
npm uninstall -S < package-name>
npm uninstall -D < package-name>
如果该软件包是全局安装的,则需要添加 -g 或 --global 标志:
npm uninstall -g < package-name>
可以在系统上的任何位置运行此命令,因为当前所在的文件夹无关紧要。

二十、Node事件循环
这里值得注意的是,node中的事件循环和浏览器的事件循环不同:
可以参考这篇文章
1、阻塞
JavaScript 中几乎所有的 I/O 基元都是非阻塞的。 网络请求、文件系统操作等。 被阻塞是个异常,这就是 JavaScript 如此之多基于回调(最近越来越多基于 promise 和 async/await)的原因。
2、调用堆栈
调用堆栈是一个 LIFO 队列(后进先出)。
事件循环不断地检查调用堆栈,以查看是否需要运行任何函数。
当执行时,它会将找到的所有函数调用添加到调用堆栈中,并按顺序执行每个函数。
3、消息队列
当调用 setTimeout() 时,浏览器或 Node.js 会启动定时器。 当定时器到期时(在此示例中会立即到期,因为将超时值设为 0),则回调函数会被放入“消息队列”中。
在消息队列中,用户触发的事件(如单击或键盘事件、或获取响应)也会在此排队,然后代码才有机会对其作出反应。 类似 onLoad 这样的 DOM 事件也如此。
事件循环会赋予调用堆栈优先级,它首先处理在调用堆栈中找到的所有东西,一旦其中没有任何东西,便开始处理消息队列中的东西
我们不必等待诸如 setTimeout、fetch、或其他的函数来完成它们自身的工作,因为它们是由浏览器提供的,并且位于它们自身的线程中。 例如,如果将 setTimeout 的超时设置为 2 秒,但不必等待 2 秒,等待发生在其他地方。
4、es6作业队列
可以参考这篇文章

二十一、process.nextTick()
每当事件循环进行一次完整的行程时,我们都将其称为一个tick。
当将一个函数传给 process.nextTick() 时,则指示引擎在当前操作结束(在下一个事件循环滴答开始之前)时调用此函数:

process.nextTick(() => {
  //做些事情
})

事件循环正在忙于处理当前的函数代码。
当该操作结束时,JS 引擎会运行在该操作期间传给 nextTick 调用的所有函数。
这是可以告诉 JS 引擎异步地(在当前函数之后)处理函数的方式,但是尽快执行而不是将其排入队列。
调用 setTimeout(() => {}, 0) 会在下一个滴答结束时执行该函数,比使用 nextTick()(其会优先执行该调用并在下一个滴答开始之前执行该函数)晚得多。
当要确保在下一个事件循环迭代中代码已被执行,则使用 nextTick()。

二十二、setImmediate()
当要异步地(但要尽可能快)执行某些代码时,其中一个选择是使用 Node.js 提供的 setImmediate() 函数:

setImmediate(() => {
  //运行一些东西
})

作为 setImmediate() 参数传入的任何函数都是在事件循环的下一个迭代中执行的回调。
setImmediate() 与 setTimeout(() => {}, 0)(传入 0 毫秒的超时)、process.nextTick() 有何不同?
传给 process.nextTick() 的函数会在事件循环的当前迭代中(当前操作结束之后)被执行。 这意味着它会始终在 setTimeout 和 setImmediate 之前执行。
延迟 0 毫秒的 setTimeout() 回调与 setImmediate() 近乎等价。

二十三、定时器
注意setTimeout可以传入多个参数,并且这些参数会保存具体的值,而非引用,因此不会发生在延迟后实参发生变化的问题。

const myFunction = (firstParam, secondParam) => {
  // 做些事情
}
// 2 秒之后运行
setTimeout(myFunction, 2000, firstParam, secondParam)

同时调用setTimeout后,setTimeout的返回值是这个定时器的id,可以利用这个以及clearTimeout来做一些节流防抖。
1、零延迟:
如果将超时延迟指定为 0,则回调函数会被尽快执行(但是是在当前函数执行之后):
通过在调度程序中排队函数,可以避免在执行繁重的任务时阻塞 CPU,并在执行繁重的计算时执行其他函数。
2、setInterval()
setInterval 每 n 毫秒启动一个函数,而无需考虑函数何时完成执行。
如果需要在每个函数间暂停一段时间,建议使用递归setTimeout

二十四、异步编程
1、回调
你不知道用户何时单击按钮。 因此,为点击事件定义了一个事件处理程序。 该事件处理程序会接受一个函数,该函数会在该事件被触发时被调用,这就是所谓的回调。
回调是一个简单的函数,会作为值被传给另一个函数,并且仅在事件发生时才被执行。 之所以这样做,是因为 JavaScript 具有顶级的函数,这些函数可以被分配给变量并传给其他函数(称为高阶函数)。
通常会将所有的客户端代码封装在 window 对象的 load 事件监听器中,其仅在页面准备就绪时才会运行回调函数:

window.addEventListener('load', () => {
  //window 已被加载。
  //做需要做的。
})

回调的错误处理:
一种非常常见的策略是使用 Node.js 所采用的方式:任何回调函数中的第一个参数为错误对象(即错误优先的回调)。
如果没有错误,则该对象为 null。 如果有错误,则它会包含对该错误的描述以及其他信息。
回调的问题:回调地狱(callback hell),解决方案es6 promise、es7 async-await

二十五、Promise
1、链式使用promise

const status = response => {
  if (response.status >= 200 && response.status < 300) {
    return Promise.resolve(response)
  }
  return Promise.reject(new Error(response.statusText))
}

const json = response => response.json()

fetch('/todos.json')
  .then(status)    // 注意,`status` 函数实际上在这里被调用,并且同样返回 promise,
  .then(json)      // 这里唯一的区别是的 `json` 函数会返回解决时传入 `data` 的 promise,
  .then(data => {  // 这是 `data` 会在此处作为匿名函数的第一个参数的原因。
    console.log('请求成功获得 JSON 响应', data)
  })
  .catch(error => {
    console.log('请求失败', error)
  })

2、错误处理
当 promise 链中的任何内容失败并引发错误或拒绝 promise 时,则控制权会转到链中最近的 catch() 语句。
3、级联错误
如果在 catch() 内部引发错误,则可以附加第二个 catch()来处理,依此类推。
4、Promise.all()
如果需要同步不同的 promise,则 Promise.all() 可以帮助定义 promise 列表,并在所有 promise 都被解决后执行一些操作。

const f1 = fetch('/something.json')
const f2 = fetch('/something2.json')

Promise.all([f1, f2])
  .then(res => {
    console.log('结果的数组', res)
  })
  .catch(err => {
    console.error(err)
  })

es6解构语法

Promise.all([f1, f2]).then(([res1, res2]) => {
  console.log('结果', res1, res2)
})

5、Promise.race()

const first = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, '第一个')
})
const second = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, '第二个')
})

Promise.race([first, second]).then(result => {
  console.log(result) // 第二个
})

6、常见错误
Uncaught TypeError: undefined is not a promise
如果在控制台中收到 Uncaught TypeError: undefined is not a promise 错误,则请确保使用 new Promise() 而不是 Promise()。
UnhandledPromiseRejectionWarning
这意味着调用的 promise 被拒绝,但是没有用于处理错误的 catch。 在 then 之后添加 catch 则可以正确地处理。

二十六、Async和Await
在任何函数之前加上 async 关键字意味着该函数会返回 promise。
Promise写法(可读性稍差)

const getFirstUserData = () => {
  return fetch('/users.json') // 获取用户列表
    .then(response => response.json()) // 解析 JSON
    .then(users => users[0]) // 选择第一个用户
    .then(user => fetch(`/users/${user.name}`)) // 获取用户数据
    .then(userResponse => userResponse.json()) // 解析 JSON
}

getFirstUserData()

async/await写法

const getFirstUserData = async () => {
  const response = await fetch('/users.json') // 获取用户列表
  const users = await response.json() // 解析 JSON
  const user = users[0] // 选择第一个用户
  const userResponse = await fetch(`/users/${user.name}`) // 获取用户数据
  const userData = await userResponse.json() // 解析 JSON
  return userData
}

getFirstUserData()

除了可读性之外,async/await更好调试,因为调试 promise 很难,
因为调试器不会跳过异步的代码。
Async/await 使这非常容易,因为对于编译器而言,它就像同步代码一样。

二十七、node事件触发器

const EventEmitter = require('events')
const eventEmitter = new EventEmitter()
eventEmitter.on('start', () => {
  console.log('开始')
})
eventEmitter.emit('start')

eventEmitter.on('start', number => {
  console.log(`开始 ${number}`)
})
eventEmitter.emit('start', 23)

eventEmitter.on('start', (start, end) => {
  console.log(`${start}${end}`)
})
eventEmitter.emit('start', 1, 100)

EventEmitter 对象还公开了其他几个与事件进行交互的方法,例如:
once(): 添加单次监听器。
removeListener() / off(): 从事件中移除事件监听器。
removeAllListeners(): 移除事件的所有监听器。

二十八、搭建http服务器

const http = require('http')

const port = 3000

const server = http.createServer((req, res) => {
  res.statusCode = 200
  res.setHeader('Content-Type', 'text/plain')
  res.end('你好世界\n')
})

server.listen(port, () => {
  console.log(`服务器运行在 http://${hostname}:${port}/`)
})

二十九、使用 Node.js 发送 HTTP 请求(实际上使用axios更好)
GET

const https = require('https')
const options = {
  hostname: 'nodejs.cn',
  port: 443,
  path: '/todos',
  method: 'GET'
}

const req = https.request(options, res => {
  console.log(`状态码: ${res.statusCode}`)

  res.on('data', d => {
    process.stdout.write(d)
  })
})

req.on('error', error => {
  console.error(error)
})

req.end()

POST

const https = require('https')

const data = JSON.stringify({
  todo: '做点事情'
})

const options = {
  hostname: 'nodejs.cn',
  port: 443,
  path: '/todos',
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Content-Length': data.length
  }
}

const req = https.request(options, res => {
  console.log(`状态码: ${res.statusCode}`)

  res.on('data', d => {
    process.stdout.write(d)
  })
})

req.on('error', error => {
  console.error(error)
})

req.write(data)
req.end()

三十、使用 Node.js 获取 HTTP 请求的正文数据
如果使用的是 Express,则非常简单:使用 body-parser Node.js 模块。

const express = require('express')
const app = express()
app.use(
  express.urlencoded({
    extended: true
  })
)
app.use(express.json())
app.post('/todos', (req, res) => {
  console.log(req.body.todo)
})

原生node(还要监听事件…)

const server = http.createServer((req, res) => {
  let data = '';
  req.on('data', chunk => {
    data += chunk;
  })
  req.on('end', () => {
    JSON.parse(data).todo // '做点事情'
  })
})

三十一、使用文件描述符
fs.open()
在与位于文件系统中的文件进行交互之前,需要先获取文件的描述符。
文件描述符是使用 fs 模块提供的 open() 方法打开文件后返回的:

const fs = require('fs')

fs.open('/Users/joe/test.txt', 'r', (err, fd) => {
  //fd 是文件描述符。
})

注意,将 r 作为 fs.open() 调用的第二个参数。
该标志意味着打开文件用于读取。
其他常用的标志有:
r+ 打开文件用于读写。
w+ 打开文件用于读写,将流定位到文件的开头。如果文件不存在则创建文件。
a 打开文件用于写入,将流定位到文件的末尾。如果文件不存在则创建文件。
a+ 打开文件用于读写,将流定位到文件的末尾。如果文件不存在则创建文件。
也可以使用 fs.openSync 方法打开文件,该方法会返回文件描述符(而不是在回调中提供):

const fs = require('fs')

try {
  const fd = fs.openSync('/Users/joe/test.txt', 'r')
} catch (err) {
  console.error(err)
}

一旦获得文件描述符,就可以以任何方式执行所有需要它的操作,例如调用 fs.open() 以及许多与文件系统交互的其他操作。

三十二、文件属性
fs.stat()
fs.statSync()
每个文件都带有一组详细信息,可以使用 Node.js 进行检查。
具体地说,使用 fs 模块提供的 stat() 方法。
调用时传入文件的路径,一旦 Node.js 获得文件的详细信息,则会调用传入的回调函数,并带上两个参数:错误消息和文件属性:

const fs = require('fs')
fs.stat('/Users/joe/test.txt', (err, stats) => {
  if (err) {
    console.error(err)
    return
  }
  //可以访问 `stats` 中的文件属性
})

Node.js 也提供了同步的方法,该方法会阻塞线程,直到文件属性准备就绪为止:

const fs = require('fs')
try {
  const stats = fs.statSync('/Users/joe/test.txt')
} catch (err) {
  console.error(err)
}

使用 stats.isFile() 和 stats.isDirectory() 判断文件是否目录或文件。
使用 stats.isSymbolicLink() 判断文件是否符号链接。
使用 stats.size 获取文件的大小(以字节为单位)。

三十三、文件路径
推荐使用以下方式将此模块引入到文件中:

const path = require('path')

给定一个路径,可以使用以下方法从其中提取信息:

dirname: 获取文件的父文件夹。
basename: 获取文件名部分。
extname: 获取文件的扩展名。
可以通过为 basename 指定第二个参数来获取不带扩展名的文件名:

path.basename(notes, path.extname(notes)) //notes

可以使用 path.join() 连接路径的两个或多个片段:

const name = 'joe'
path.join('/', 'users', name, 'notes.txt') //'/users/joe/notes.txt'

可以使用 path.resolve() 获得相对路径的绝对路径计算:

path.resolve('tmp', 'joe.txt') //'/Users/joe/tmp/joe.txt' 如果从主文件夹运行。

如果第一个参数以斜杠开头,则表示它是绝对路径:

path.resolve('/etc', 'joe.txt') //'/etc/joe.txt'

path.normalize() 是另一个有用的函数,当包含诸如 .、… 或双斜杠之类的相对说明符时,其会尝试计算实际的路径:

path.normalize('/users/joe/..//test.txt') //'/users/test.txt'

三十四、读取文件
在 Node.js 中读取文件最简单的方式是使用 fs.readFile() 方法,向其传入文件路径、编码、以及会带上文件数据(以及错误)进行调用的回调函数:

const fs = require('fs')

fs.readFile('/Users/joe/test.txt', 'utf8' , (err, data) => {
  if (err) {
    console.error(err)
    return
  }
  console.log(data)
})

另外,也可以使用同步的版本 fs.readFileSync():

const fs = require('fs')

try {
  const data = fs.readFileSync('/Users/joe/test.txt', 'utf8')
  console.log(data)
} catch (err) {
  console.error(err)
}

fs.readFile() 和 fs.readFileSync() 都会在返回数据之前将文件的全部内容读取到内存中。
这意味着大文件会对内存的消耗和程序执行的速度产生重大的影响。
在这种情况下,更好的选择是使用流来读取文件的内容。

三十五、写入文件
在 Node.js 中写入文件最简单的方式是使用 fs.writeFile() API。
例如:

const fs = require('fs')

const content = '一些内容'

fs.writeFile('/Users/joe/test.txt', content, err => {
  if (err) {
    console.error(err)
    return
  }
  //文件写入成功。
})

另外,也可以使用同步的版本 fs.writeFileSync():

const fs = require('fs')

const content = '一些内容'

try {
  const data = fs.writeFileSync('/Users/joe/test.txt', content)
  //文件写入成功。
} catch (err) {
  console.error(err)
}

默认情况下,此 API 会替换文件的内容(如果文件已经存在)。

可以通过指定标志来修改默认的行为:

fs.writeFile('/Users/joe/test.txt', content, { flag: 'a+' }, err => {})

可能会使用的标志有:
r+ 打开文件用于读写。
w+ 打开文件用于读写,将流定位到文件的开头。如果文件不存在则创建文件。
a 打开文件用于写入,将流定位到文件的末尾。如果文件不存在则创建文件。
a+ 打开文件用于读写,将流定位到文件的末尾。如果文件不存在则创建文件。
将内容追加到文件末尾的便捷方法是 fs.appendFile()(及其对应的 fs.appendFileSync()):

const content = '一些内容'

fs.appendFile('file.log', content, err => {
  if (err) {
    console.error(err)
    return
  }
  //完成!
})

三十六、使用文件夾
1、使用 fs.access() 检查文件夹是否存在以及 Node.js 是否具有访问权限。
2、使用 fs.mkdir() 或 fs.mkdirSync() 可以创建新的文件夹。
3、使用 fs.readdir() 或 fs.readdirSync() 可以读取目录的内容。
这段代码会读取文件夹的内容(全部的文件和子文件夹),并返回它们的相对路径:

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

const folderPath = '/Users/joe'

fs.readdirSync(folderPath)

可以获取完整的路径:

fs.readdirSync(folderPath).map(fileName => {
  return path.join(folderPath, fileName)
})

也可以过滤结果以仅返回文件(排除文件夹):

const isFile = fileName => {
  return fs.lstatSync(fileName).isFile()
}

fs.readdirSync(folderPath).map(fileName => {
  return path.join(folderPath, fileName)
})
.filter(isFile)

4、使用 fs.rename() 或 fs.renameSync() 可以重命名文件夹。 第一个参数是当前的路径,第二个参数是新的路径:

const fs = require('fs')

fs.rename('/Users/joe', '/Users/roger', err => {
  if (err) {
    console.error(err)
    return
  }
  //完成
})

fs.renameSync() 是同步的版本:

const fs = require('fs')

try {
  fs.renameSync('/Users/joe', '/Users/roger')
} catch (err) {
  console.error(err)
}

5、使用 fs.rmdir() 或 fs.rmdirSync() 可以删除文件夹。

删除包含内容的文件夹可能会更复杂。

在这种情况下,最好安装 fs-extra 模块,该模块非常受欢迎且维护良好。 它是 fs 模块的直接替代品,在其之上提供了更多的功能。

在此示例中,需要的是 remove() 方法。

使用以下命令安装:

npm install fs-extra

并像这样使用它:

const fs = require('fs-extra')

const folder = '/Users/joe'

fs.remove(folder, err => {
  console.error(err)
})

也可以与 promise 一起使用:

fs.remove(folder)
  .then(() => {
    //完成
  })
  .catch(err => {
    console.error(err)
  })

或使用 async/await:

async function removeFolder(folder) {
  try {
    await fs.remove(folder)
    //完成
  } catch (err) {
    console.error(err)
  }
}

const folder = '/Users/joe'
removeFolder(folder)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值