Skip to main content

node.js 入门

关于 node 入门的一篇课件

前言

Node.js 是什么?

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,就是你的 JavaScript 代码可以在 Node.js 里面跑。

为什么需要 Node.js?

  • 性能好、部署容易,能够轻松处理高并发问题。

  • 它让我们可以用 js 写后端程序,顶层路由设计等。

  • Node.js 是前端工程化的重要支柱之一。(目前前端开发环境离不开 nodejs,是我们使用很多工具和提高开发效率的基础)

Node.js 特性

  1. 单线程 非阻塞 I/O 事件驱动

    java/php/.net 服务器语言中,为每个客户端创建一个新的线程,要让 Web 应用程序支持更多的用户,就需要增加服务器的数量,这样硬件成本就变高了。

    nodejs 只使用一个线程,用户连接就触发一个内部事件,通过非阻塞 I/O、事件驱动机制,让程序宏观上是并行的。

    减少了内存开销,省去了操作系统创建/销毁线程的时间开销。

    当 Node.js 执行 I/O 操作时(例如从网络读取、访问数据库或文件系统),Node.js 会在响应返回时恢复操作,而不是阻塞线程并浪费 CPU 循环等待。

    这使 Node.js 可以在一台服务器上处理数千个并发连接,而无需引入管理线程并发的负担(这可能是重大 bug 的来源)。

    最后不管是新用户的请求还是老用户的 I/O 完成,都将以事件的方式加入事件循环,等待调度(注意不是排队,会优先执行老用户的回调函数)。

  2. 大量的库

npm 的简单结构有助于 Node.js 生态系统的激增,现在 npm 仓库托管了超过 1,000,000 个可以自由使用的开源库包。

基本满足了我们的所有需求

Node.js 能做什么

说 Node.js 能做什么,不如说说我用 Node.js 做了什么吧。

当然 Node.js 还能做

  • 后端
  • 深度学习
  • ...

Node.js 基础

首先先检查一下自己的 node 装好了没有

$ node -v
$ npm -v

CommonJS 规范(模块化)

引入模块

const fs = require('fs')

导出模块

两种方式

module.exports.add = (x, y) => x + y
module.exports = (x, y) => x + y

第一种导出方式相当于在module.exports上加了一个add属性,而第二种则是将整个模块变赋值为一个函数

第一种导出的使用方式

const add = require("./add")

console.log(add.add(1, 2)) // 3

而第二种

const add = require('./add')

console.log(add(1, 2)) // 3

应该很好理解吧

同时我们又有了一个问题,如果我的文件模块和核心模块 (node 提供的模块) 同名,node 会使用哪一个?

我们来测试一下

// fs.js
module.exports = () => console.log('fs')
const fs = require('fs')

console.log(fs)
/**
{
appendFile: [Function: appendFile],
appendFileSync: [Function: appendFileSync],
...
}
*/

使用了核心模块

const fs = require('./fs')

console.log(fs) // [Function (anonymous)]

使用了我们自己的文件模块

那么 node 寻找模块的机制到底是怎样的呢?

在 node 中引入模块,需要经历如下 3 个步骤

  1. 路径分析
  2. 文件定位
  3. 编译执行

核心模块部分在 node 源代码的编译过程中,编译成了二进制执行文件。在 node 进程启动的时候,部分核心模块就直接被加载进了内存中,所以这部分核心模块引入时,文件定位和编译执行这两个步骤直接被省略,并且在路径分析中优先判断,所以加载速度是最快的。

文件模块则是在运行时动态加载,需要完整走完 3 个步骤,加载较慢。

详情请看 深入浅出 Node.js

基本模块

node 内置了一些基本的模块供我们使用,默认使用CommonJS规范

fs

fs 模块就是文件系统模块,负责读写文件。nodeJs内置的fs 模块提供了异步和同步的方法

同步方法就是在异步 API 后面加一个 Sync 后缀

// 异步读文件
fs.readFile('./data.json', (error, data) => {
if (error) {
console.log(error)
} else {
console.log(data.toString())
}
})

const data = [
{
name: 'wjx',
age: '20',
},
{
name: 'wjx',
age: '20',
},
]

// 异步写文件
fs.writeFile('./data2.json', JSON.stringify(data), error => {
if (error) console.log(error)
})

//同步读文件
file = fs.readFileSync(filePath)

//同步写入文件
fs.writeFileSync(filename, data)

其他的一些方法

  • fs.stat(path[,options],callback)

返回一个文件或目录的信息

fs.stat('./index.js', (err, stats) => {
console.log(stats)
})

stats包含的参数很多,介绍几个常用的

export interface StatsBase<T> {
isFile(): boolean // 判断是否是一个文件
isDirectory(): boolean // 判断是否一个目录

size: T // 大小(字节数)
atime: Date // 访问时间
mtime: Date // 上次文件内容修改时间
ctime: Date // 上次文件状态改变时间
birthtime: Date // 创建时间
}

一般我们会使用 fs.stat 来取文件的大小,做一些判断逻辑,比如发布的时候可以检测文件大小是否符合规范。

  • fs.readdir(path[, options], callback)

fs.readdir(path) 获取 path 目录下的文件和目录,返回值为一个包含 filedirectory 的数组。

假设当前目录为:

.
├── a
│ ├── a.js
│ └── b
│ └── b.js
├── index.js
└── package.json

执行以下代码:

const fs = require('fs')

fs.readdir(process.cwd(), function (error, files) {
if (!error) {
console.log(files)
}
})

返回值为:

["a", "index.js", "package.json"]

可以看到这里只返回了根目录下的文件和目录,并没有去深度遍历。所以如果需要获取所有文件名,就需要自己实现递归。

//删除文件
fs.unlink(path , callback)
//截断文件
fs.truncate(path , len , callback) 返回 true 和 flase
//创建目录
fs.mkdir(path , [mode] , callback)
//删除目录
fs.rmkdir(path , callback)
...

path

  • path.join(...paths)

用于连接路径。该方法的主要用途在于,会正确使用当前系统的路径分隔符,Unix 系统是"/",Windows 系统是" \"。

console.log(path.join('/path', '/user')) //\path\user
  • path.resolve(...paths)

用于构建绝对路径,保证无论在什么目录下执行 node 程序都可以正确找到文件地址

const path = require('path')

console.log(path.resolve(__dirname, './data.json')) // D:\frontend\demo\class\node\data.json

http

  • http.createServer([requestListener])

创建 HTTP 服务器

  • server.listen()

监听端口

const http = require('http')

//创建 http 服务器
const server = http.createServer((req, res) => {
res.end('hello world')
})

//监听 8000 端口,等待连接
server.listen(8000, () => {
console.log('server is running at http://localhost:8000')
})

简易文件服务器

const http = require("http")
const fs = require("fs")
const path = require("path")

const fileShow = pathname => {
//查看输入路由对应的是文件还是路由
return new Promise(resolve => {
fs.stat(pathname, (err, stats) => {
if (err || !stats.isFile()) {
resolve(false)
}
fs.readFile(pathname, "utf-8", (err, data) => {
if (err) resolve(false)
resolve(data)
})
})
})
}

const server = http.createServer((req, res) => {
const { url, method } = req
if (method === "GET") {
fileShow(path.resolve(__dirname, "." + url)).then(data => {
if (!data) {
res.writeHead(404)
res.end("404 not found")
} else {
res.writeHead(200, {
"Content-Type": "text/plain; charset=utf-8",
})
res.end(data)
}
})
}
})
server.listen(8000, () => {
console.log("server is running at http://localhost:8000")
})

简易后端

服务端

const http = require('http')
const querystring = require('querystring')

const server = http.createServer((req, res) => {
const { url, method } = req
console.log(method, url)
const path = url.split('?')[0]
const query = querystring.parse(url.split('?')[1])

const responseData = {
url,
method,
path,
query,
}

res.writeHead(200, {
'Content-Type': 'application/json',
})

if (method === 'GET') {
res.end(JSON.stringify(responseData))
}
if (method === 'POST') {
let postData = ''
req
.on('data', chunk => {
postData += chunk
})
.on('end', () => {
responseData.postData = postData
res.end(postData)
})
}
})

//监听 8000 端口,等待连接
server.listen(8000, () => {
console.log('server is running at http://localhost:8000')
})

客户端

const http = require('http')
http.get('http://localhost:8000/data', res => {
let data = ''
res.setEncoding('utf-8')
res.on('data', chunk => {
data += chunk
})
res.on('end', () => {
res.statusCode = 200
console.log(JSON.parse(data))
})
})

const data = JSON.stringify([
{
name: 'wjx',
age: '20',
},
{
name: 'wjx',
age: '20',
},
{
name: 'wjx',
age: '20',
},
{
name: 'wjx',
age: '20',
},
{
name: 'wjx',
age: '20',
},
])

const options = {
hostname: 'localhost',
port: 8000,
path: '/data',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': data.length,
},
}
const req = http.request(options, res => {
res.on('data', d => {
process.stdout.write(d)
})
})

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

req.write(data)
req.end()

NPM

npm 是 JavaScript 世界的包管理工具,并且是 Node.js 平台的默认包管理工具

简单来说,npm 上面有很多的开源库可供我们使用,我们也可以将自己的库发布到 npm 供他人使用

cnpm

npm 默认的源的下载速度对于我们来说可能不太友好,可以使用cnpm或者更换 npm 的源为淘宝源来提升下载速度

淘宝 NPM 镜像

package.json

在安装依赖之后,会自动生成一个package.json文件,它是用于定义包的属性,模块的描述文件

我们也可以用下面的命令来初始化一个package.json

$ npm init -y

下面用一个例子介绍一下package.json一些字段的含义

{
"name": "book-spider",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"spider": "npx ts-node src/index.ts",
"test": "npx ts-node src/test.ts"
},
"devDependencies": {
"@types/node": "^15.3.1",
"@types/optimist": "^0.0.29",
"@types/signale": "^1.4.1",
"ts-node": "^9.1.1",
"typescript": "^4.2.4"
},
"dependencies": {
"@types/axios": "^0.14.0",
"@types/cheerio": "^0.22.28",
"axios": "^0.21.1",
"cheerio": "^1.0.0-rc.9",
"optimist": "^0.6.1",
"signale": "^1.4.0"
}
}
  • version 版本号 ^表示固定版本号
  • devDependencies 项目开发所需要的依赖 如一些打包工具、类型定义文件等
  • dependencies 项目运行所需要的依赖
  • scripts npm 脚本 npm run + [script] 执行

package-lock.json 只是为了确保依赖包的正常使用,比如锁定依赖树,包的版本等等

npm install

npm install/i <pkg>安装 node 包/模块

npm i # 安装所有依赖
npm i --save / -S <pkg> # 安装依赖到 dependencies,和默认情况一致
npm i --save-dev / -D <pkg> # 安装依赖到 devDependencies
npm i -g <pkg> #安装依赖到全局

install 命令总是安装模块的最新版本(指正式发布的最新版本),如果要安装模块的指定版本,可以在模块名后面加上 @和版本号。

node_modules

node_modules文件夹下面就是我们安装的依赖,提交到 git 的时候请用.gitignore文件忽略

refs

「万字整理 」这里有一份 Node.js 入门指南和实践,请注意查收 ❤️

Node.js 中文网