Node.js 入门
Node.js 概述
- Node.js是什么
是一个执行js代码的工具,工具是指可以安装在计算机操作系统上的软件 - 为什么浏览器和Node.js 都可以运行js代码
因为浏览器和Node.js都内置了JavaScrip V8 引擎,它可以将js代码编译为计算机能够识别的机器码 - 浏览器中运行的 js 和 Node.js 中运行的 js 有区别吗
浏览器中运行的 js 只能控制浏览器窗口和DOM
Node.js 中运行的 js 只能控制操作系统级别的内容 - Node.js 可以做什么
通常用来构建服务端应用和创建前端工程化工具
js 运行在浏览器中,我们就叫它客户端 js
js 运行在Node.js 中,我们就叫它服务端 js
系统环境变量
系统环境变量是指定义在操作系统上的变量,变量存储了系统运行时所需要的参数
比如在使用 webpack 构建前端应用时就使用到了系统环境变量,因为webpack 需要根据系统环境变量判断当前为开发环境还是生产环境(该变量为NODE_ENV ,值为 production 或者 development,webpack 在运行时通过 process.env.NODE_ENV 获取变量的值,从而知道当前代码的运行环境)
体验Node.js
function sayHello(name) {
console.log("Hello" + " "+ name + "!")
}
sayHello("Node")
zhangmeng@zhangmengdeMacBook-Pro 1-basics % node index.js
Hello Node!
全局对象
- Node.js 环境中全局对象为 global,在global 中存在一些和 window 对象中名字相同且作用相同的方法(global.console.log、global.setTimeout)
模块系统
模块概述
一个js文件就是一个模块,在模块文件中定义的变量和函数默认只能在模块文件中使用,如果需要在其他模块文件中使用,必须显示将其进行导出
模块成员导出
每一个模块文件中都存在一个模块对象,即 module ,该对象保存了当前模块相关信息
在模块对象中有一个属性 exports,该属性存储了一个对象,模块内部需要导出的成员都需要存储在这个对象中
const url = "http://www.example.com"
function log(message) {
console.log(message)
}
console.log(__filename)
console.log(__dirname)
// 方法一、
// exports.endPoint = url
// exports.log = log
// 方法二、
// module.exports = {
// endPoint: url,
// log: log
// }
模块成员导入
在其他文件中通过 require 方法引入模块,require 的参数就是要引入模块的路径,,引入模块的后缀可以省略,require 方法的返回值为 module.exports 对象
const logger = require("./logger")
console.log(logger)
console.log(logger.log)
console.log(logger.endPoint)
zhangmeng@zhangmengdeMacBook-Pro 2-module % node index.js
/Users/zhangmeng/Documents/课件/前端工程化开发/03-01-study-materials/03-01-code/03-01-01-前端工程化概述、Node.js基础/2-module/logger.js
/Users/zhangmeng/Documents/课件/前端工程化开发/03-01-study-materials/03-01-code/03-01-01-前端工程化概述、Node.js基础/2-module
{ endPoint: 'http://www.example.com', log: [Function: log] }
[Function: log]
http://www.example.com
Node.js 内置模块
- path:模块内提供了一些和路径操作相关的方法
- File System:文件操作系统,提供了和操作文件相关的方法
Path 模块
const path = require("path")
console.log(path.parse(__filename))
运行结果:
zhangmeng@zhangmengdeMacBook-Pro 2-module % node module_path.js
{
root: '/',
dir: '/Users/zhangmeng/Documents/课件/前端工程化开发/03-01-study-materials/03-01-code/03-01-01-前端工程化概述、Node.js基础/2-module',
base: 'module_path.js',
ext: '.js',
name: 'module_path'
}
File System 模块
const fs = require("fs")
const result = fs.readdirSync("./")
fs.readdir("./", function (error, files) {
console.log(files)
})
运行结果:
zhangmeng@zhangmengdeMacBook-Pro 2-module % node module_fs.js
[ '.git', 'index.js', 'logger.js', 'module_fs.js', 'module_path.js' ]
NPM
Node.js 软件包
每一个基于 Node.js 开发的应用程序都是Node.js 软件包
什么是 NPM
Node Package Manager,Node.js 环境中的软件包管理器,随 Node.js 一起被安装
可以将 Node 软件包添加到我们的应用程序中,并对其进行管理,比如下载、删除、更新、查看版本等
NPM 和 Node 是两个独立的应用程序,只是被捆绑安装了
package.json
Node.js 规定在每一个软件包中都必须包含一个叫做package.json 的文件
它是应用程序的描述文件,包含应用程序的相关信息,比如:应用名称,应用版本,应用作者
通过 package.json 文件可以方便的管理应用和发布应用
创建 package.json 文件:npm init
快速创建 package.json 文件:npm init --yes
下载 Node.js 软件包
在应用程序的根目录下执行命令:
npm i 包名称
npm install 包名称
软件包下载完成后回发生三件事情:
- 软件包会存储在 node_module 文件夹中,如果应用程序中不存在此文件夹,npm 会自动创建
- 软件包会记录在 package.json 中,包含名字及版本号
- npm 会在应用中创建 package-lock.json 文件,用于记录软件包及软件包依赖包的下载地址及版本
使用Node.js 软件包
在引入第三方软件包时,在require 方法中不需要添加路径信息,只需要传入包名称,require 方法会自动去 node_module 文件夹中查找
const _ = require("lodash")
const array = ["a", "b", "c", "d"]
// chunk 对数组中的元素进行分组
// 参数一表示要进行操作的数组
// 参数二表示每一组中包含的元素个数
console.log(_.chunk(array, 2)) // [ [ 'a', 'b' ], [ 'c', 'd' ] ]
软件包依赖问题
-
我的应用只依赖 mongoose 软件包,但是下载后 node_modules 文件夹下除了 mongoose 还有一些其他的软件包
原因:其他的软件包为 mongoose 它的依赖包 -
为什么 mongoose 的依赖包不放在 mongoose 的文件夹中
原因一:A依赖X,B依赖X,C依赖X,X就会下载3次
原因二:A依赖B,B依赖C,就会存在文件夹嵌套的情况 -
所有的软件包都放在 node_modules 文件夹下不会导致版本冲突
原因:A依赖X的1.0版本,B依赖X的2.0版本,先下载A,X的1.0版本会放置在根目录下 node_modules 文件夹中。再下载B,发现根目录下已经存在X,但是版本不一致,所以X的2.0版本会放置在 B 软件包下的 node_modules 文件夹中 -
node_modules 文件夹中的软件包不需要提交到 git 仓库中,但是其他人拿到应用程序运行不起来
解决方案:可以根据 package.json 中的记录下载对应的软件包。为了版本一致,npm 还会根据 package-lock.json 文件中的记录的地址进行下载
⚠️:将应用程序提交到版本库之前,将 node_modules 文件夹添加到 .gitignore 文件中
git init
git status
echo "node_modules/" > .gitigore
git status
git add .
git commit -m "npm first commit"
异步编程
CPU 与存储器
- CPU
中央处理器,代码被编译为机器码后,就是通过 CPU 执行的 - 存储器
内存:临时存储数据,断电后数据丢失
磁盘:持久存储数据,断电后数据不丢失
I/O 模型
- 同步 I/O 操作
CPU等待 I/O 操作完成获取到操作结果后再去执行其他指令 - 异步 I/O 操作
CPU不等待 I/O 操作完成,CPU 发出 I/O 操作指令后,内存和磁盘开始工作,CPU继续执行其他指令, 当 I/O 操作完成后,会通知 CPU 调用回调函数 - Node.js 采用的就是异步非阻塞 I/O 模型
const fs = require("fs")
fs.readFile("./x.txt", "utf-8", function (err, result) {
console.log(result)
})
console.log("Hello")
执行结果:
zhangmeng@zhangmengdeMacBook-Pro 4-async % node 1-callback.js
Hello
x
const fs = require("fs")
const data = fs.readFileSync("./y.txt", { encoding: "utf-8" })
console.log(data)
执行结果:
zhangmeng@zhangmengdeMacBook-Pro 4-async % node 1-callback.js
y
进程与线程
当我们运行应用程序时,操作系统都会创建该应用程序的实例对象,该实例对象就是应用程序的进程,操作系统会按进程为单位,为应用程序分配资源,比如内存,这样应用程序就可以在操作系统中运行起来
JS 单线程还是多线程
在 Node.js 代码运行环境中,它为js代码运行提供了一个主线程,通常我们说的单线程就是指这个主线程,但是 Node.js 内部依赖了一个叫做 libuv 的 c++ 库,在这个库中它维护了一个线程池,默认情况下,这个线程池中存在4个线程,js中的异步代码就是在这些线程中执行的,所以说js代码的运行不仅仅是一个线程,本质上js还是多线程的
基于回调函数的异步编程
什么是回调函数
回调函数是指将一个函数通过参数的方式传递给另一个函数,参数函数就是回调函数
function A() {
console.log("A is running")
}
function B(callback) {
console.log("B Start")
callback() // A is running
console.log("B End")
}
B(A)
回调函数传递参数
function A(arg) {
console.log("A is running")
console.log(arg)
}
function B(callback) {
console.log("B Start") callback("我是B函数传递给A函数的参数") // A is running console.log("B End")
}
B(A)
回调函数在异步编程中的应用
const fs = require("fs")
fs.readFile("./index.html", "utf-8", function (error, data) { if (error) console.log("发生了错误")
console.log(data)
})
回调地狱
回调函数多层嵌套导致代码难以维护
const fs = require("fs")
fs.readFile("./x.txt", "utf-8", function (error, x) {
fs.readFile("./y.txt", "utf-8", function (error, y) {
fs.readFile("./z.txt", "utf-8", function (error, z) {
console.log(x)
console.log(y)
console.log(z)
}) })
})
基于promise的异步编程
promise 概述
- promise 是 js 中异步编程解决方案,可以解决回调函数地狱问题
- promise 可以理解为一个容器,用于包裹异步api,当容器中的一步 api 执行完成后,promise 允许我们在容器外面获取异步 api 的结果,从而避免回调函数嵌套
- promise 有三个状态,分别是:等待(pending)、成功(fulfilled)、失败(rejected),默认为等待,等待可以变成成功,等待可以变成失败
- 状态一旦更改不可以改变
promise 基础语法
const fs = require("fs")
const promise = new Promise(function (resolve, reject) {
fs.readFile("./x.txt", "utf-8", function (error, result) {
if (error) {
// pending -> rejected
reject(error)
} else {
// pending -> fulfilled
resolve(result)
}
})
})
promise
.then(function (result) {
console.log(result)
})
.catch(function (error) {
console.log(error)
})
promise 链式调用
const fs = require("fs")
function readFile(path) {
return new Promise(function (resovle, reject) {
fs.readFile(path, "utf-8", function (error, result) {
if (error) {
reject(error)
} else {
resovle(result)
}
})
})
}
readFile("./x.txt")
.then(function (x) {
console.log("x")
return readFile("./y.txt")
})
.then(function (y) {
console.log("y")
return readFile("./z.txt")
})
.then(function (z) {
console.log("z")
})
.catch(function (error) {
console.log(error)
})
.finally(function () {
console.log("finally")
})
运行结果:
zhangmeng@zhangmengdeMacBook-Pro 4-async % node 3-then.js
xx
yy
zz
finally
promise.all 并发异步操作
function readFile(path) {
return new Promise(function (resovle, reject) {
fs.readFile(path, "utf-8", function (error, result) {
if (error) {
reject(error)
} else {
resovle(result)
}
})
})
}
Promise.all([
readFile("./x.txt"),
readFile("./y.txt"),
readFile("./z.txt")
]).then(function (result) {
console.log(result)
})
运行结果:
zhangmeng@zhangmengdeMacBook-Pro 4-async % node 4-Promise.all.js
[ 'xx', 'yy', 'zz' ]
基于异步函数的异步编程
promise 虽然解决了回调地狱问题,但是代码仍然不简洁,可以使用异步函数简化代码
- 异步函数概述
const fs = require("fs")
function readFile(path) {
return new Promise(function (resolve, reject) {
fs.readFile(path, "utf-8", function (error, data) {
if (error) return reject(error)
resolve(data)
}) })
}
async function getFileContent() {
let x = await readFile("./x.txt")
let y = await readFile("./y.txt")
let z = await readFile("./z.txt")
return [x, y, z]
}
getFileContent().then(console.log)
- util.promisify
const fs = require("fs")
const util = require("util")
// 将基于回调函数的异步 API 转换为返回 Promise 的API
const readFile = util.promisify(fs.readFile)
async function getFileContent() {
let x = await readFile("./x.txt", "utf-8")
let y = await readFile("./y.txt", "utf-8")
let z = await readFile("./z.txt", "utf-8")
return [x, y, z]
}
getFileContent().then(console.log)