Node.js 入门与进阶

作者:user 发布日期: 浏览量:5

简介

1、什么是服务端

什么是服务端:
- 服务端,又称后端、server 端
- 前端是用户可见、可操作的部分,如树枝树叶
- 服务端为前端提供“支撑”和“营养”,如树根

服务端的职责:
- 提供前端要展示的数据
- 接收前端要提交的数据
- 存储数据

服务端的表现形式:
- 前端 ajax 要调用某个接口
- 这个接口,就是服务端提供的
- 如 get 请求获取数据,post 请求提交数据

2、前端和服务端如何交互

  • 前端发起网络请求
  • 服务端接收网络请求
  • 服务端处理并返回数据

服务端处理数据返回前端的返回格式:
- errno: 0 (即 error number 简写)
- data: { …… }
- message: “……”

3、服务端处理并返回数据

  • 定义前端请求的 URL 规则 —— 路由
  • 用 Request 获取数据,用 Response 返回数据
  • 读取和存储数据 —— 数据库

路由是什么:
- router
- 作用是定义服务端的入口规则,入口即规则
- 路由定义 method,如 GET / POST
- 定义 url 规则,如 /api/list 或 /api/add
- 定义输入(Request body)和输出(Response body)格式

4、为何选择 NodeJs

  • 开发服务端,有很多种语言可以选择,常见的如 java、python、PHP、.net 等
  • 不同语言都能实现处理 http 请求、定义路由供前端 Ajax 访问、使用数据库存储和查询数据
  • node.js 对于前端工程师的优势
    node.js 使用 JS 语法
    node.js 使用 npm
    学习 node.js 只需要学习框架和 API
  • node.js 已经被普遍应用

一、NodeJs 基础入门

1、NodeJs 是什么

  • 一个基于 Chrome V8 引擎的 JavaScript 运行时(运行环境)
  • 2009 年发布,现已更新到 V14 版本
  • 现已广泛应用于开源社区和各种公司,特别是互联网公司

Chrome V8 引擎:
- Chrome 是一个浏览器,他可以执行 js 代码
- V8 就是 chrome 的 js 引擎,以速度著称
- nodejs 也是基于 js 语法的,因此也可以借用 V8 引擎

运行时:
- 代码的运行环境
- 有了运行时,代码才能被执行
- 没有运行时,代码就是一堆静态文本,就像 txt 一样

NodeJs 出现之前:
- nodejs 出现之前,只有浏览器可以执行 js 代码
- 浏览器主要是显示网页,所有的 js 也被当做网页的一部分
- 除此之外,没有其它应用场景,更不用说做服务端

NodeJs 出现之后:
- 除了浏览器,nodejs 又是一个新的 js 运行时
- 哪里安装 nodejs,哪里就可以运行 js 代码
- 可以用在本机(如使用 webpack 打包),也可以做服务端

如何使用 NodeJs 做服务端:
- 安装 nodejs
- 编写 js 代码(处理 http 请求)
- 使用 nodejs 执行 js 代码

2、NodeJs 下载和安装

node -v
npm -v
  • 使用 nodejs 运行 js 代码
// 在控制台编写执行 js 代码,使用 node 命令进入 js 运行环境
node

// 在控制台输入 node + js文件,是执行该 js 文件的代码
node index.js
  • 安装 cnpm 镜像:
npm install -g cnpm --registry=https://registry.npm.taobao.org
  • Node 升级
// 安装 n
npm install -g n

// 查看版本
n -V

// 安装最新稳定版本
n stable

// or 安装指定版本
n 9.10.0

3、包管理工具 npm

npm 是什么:
- node package manager,即 nodejs 软件包管理者
- nom 官网:https://www.npmjs.com/
- 有几百万的软件包,开源免费

开始使用 npm:
- npm 会随着 nodejs 一起被安装
- 通过 npm init 初始化环境
- 使用 npm install –save lodash 安装 lodash
- 使用 npm install –save-dev nodemon 安装 nodemon
使用 node 要手动重启服务,而使用 nodemon 会自动重启服务

"script": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "nodemon index.js"
}

- -save 和 –save-dev 的区别:
- --save 是生产环境和开发环境都使用到的包;--save-dev 中的包只在开发环境中使用
- --save 安装的软件包需要在代码中直接使用,会放在 dependencies 里

"dependencies": {
    "lodash": "^4.17.15"
}
  • --save-dev 安装的软件包不会在代码中直接使用,会放在 devDependencies 里
"devDependencies": {
    "nodemon": "^2.0.2"
}

4、镜像源管理工具

  • Mac 安装
sudo npm install -g nrm
  • Windows 安装
npm install -g nrm
  • 查看版本(验证是否安装成功)
nrm -V

// or
nrm --version
  • 查看可选镜像源
nrm ls
  • 测试哪个源响应比较快
nrm test 镜像源名称
  • 切换镜像源
nrm use 镜像源名称
  • 查看当前正在使用的镜像源
nrm current
  • 增加镜像源
nrm add 镜像源名称 镜像源链接
  • 删除镜像源
nrm del 镜像源名称

5、CommonJS 和 ES6 Module 的区别

ES6 Module 回顾:
- export default 和 import … from … :一个模块只能有一个 export default
- export 和 import … from … :export 后面不能直接跟值,必须是声明或语句;导入时的名称不能随便取,必须与导出时的名称一致,且用花括号包裹名称

CommonJS 语法介绍:
- commonjs 使用 module.exports 输出内容
- 使用 require(…) 引入内容
- commonjs 主要用于 nodejs 开发

require(…) 的三个层级:
- 系统自带模块,如 require(‘http’)
- npm 包,如 require(‘lodash’)
- 自定义模块,如 require(‘./utils’);自定义模块一般要写相对目录用于区分
- require 会先判断是不是系统自带模块,然后再判断是不是 npm 引入的包,最后才判断是不是自定义模块

CommonJS 和 ES6 Module 的区别:
- 两者的语法不一样
- commonjs 是执行时引入,是动态的,可以在使用到的地方再引入
- ES6 Module 是打包时引入,是静态的 ,必须在代码最外层提前引入

为何要使用模块化:
- 模块拆分开,便于代码的组织和管理
- 便于多人协作开发,各写各的互不干扰
- 成熟的语言都支持模块化,如:C、C++、Java、PHP、python等

commonjs 使用示例:

// utils.js
// 单个
function sum(a, b) {
    return a + b;
}

module.exports = sum

// 多个
function sum(a, b) {
    return a + b;
}

function test() {
    console.log("this is test");
}

module.exports = {
    sum,
    test
}
// index.js
// 单个
const sum = require('./utils.js'). // .js 可写可不写

const result = sum(10, 20);

console.log(result);  // 30

// 多个
const { sum, test } = require('./utils');

const result = sum(10, 25);

console.log(result);  // 35
test();  // this is test

6、debug

什么是 debug:
- bug 即错误
- debug 即排错,也叫调试
- 编程语言必须有成熟的 debug 机制,否则将不可用

debug 的重要性:
- 程序出现 bug 很常见,因此 debug 也很常用
- 使用 debug 才能快速定位出错的位置

inspect 调试法:
- 修改 scripts,增加 –inspect,启动服务

// 9229 是默认端口号
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "nodemon --inspect=9229 index.js"
}
  • 打开 Chrome,访问:chrome://inspect,点击页面下方的 inspect
  • 增加 debugger,重启服务,即可调试
// 示例
const http = require('http')

const a = undefined

const server = http.createServer((req, res) => {
    debugger

    const url = req.url
    const path = url.split('?')[0]

    a()

    res.end(path)
})

server.listen(3000)

7、NodeJs 和前端 JS 的区别

  • 两者都使用 JS 语法
    变量的定义和类型
    函数的定义和执行
    ES6 的 Class Promise 等语法
  • 前端 JS 使用浏览器提供的 Web API
    如前端网页的 DOM BOM 事件 Ajax 等
    前端 JS 可以使用,因为在浏览器环境
    nodejs 则无法使用,因为是 nodejs 环境
  • NodeJs 使用 NodeJs API

二、NodeJs 处理 HTTP 请求

1、认识 Request(req)和 Response(res)

nodejs 启动 web 服务:
- 使用 http 模块,启动服务
- 本机的 IP:127.0.0.1
- 本机域名:localhost

nodejs 如何监听 http 请求:

const http = require('http')

const server = http.createServer((req, res) => {
    // 对监听到的请求进行处理

    // 如果此时请求地址为:http://localhost:3000/insex.html
    const url = req.url
    console.log(url)  // 则此处得到的 url 为:/index.html

    // res 将服务端的响应返回给前端
    res.end('返回的内容')
})

server.listen(3000)  // 可以监听 http 请求 

3、定义路由

路由包含什么:
- 路由定义 method,如 GET / POST
- 定义 url 规则,如 /api/list 或 /api/add
- 定义输入(Request body)和输出(Response body)格式

nodejs 定义路由:
- 从 req 中获取 url 和 method
- 判断 method 是否符合
- 看 url 是否符合规则

拿到 req 中的 url 和 method:
- method 中的方法 GET / POST 必须大写

const http = require('http')

const server = http.createServer((req, res) => {
    // 获取到请求地址中的 url
    const url = req.url
    // 这一步是为了去掉 url 中的参数,从而获得真正的 url
    const path = url.split('?')[0]
    // 获取到请求地址中的 method
    const method = req.method

    // 模拟获取信息的 url
    if(path === '/api/list' && method === 'GET') {
        // 命中该 url 后响应
        res.end('响应的内容')
    }

    // 模拟发送信息的 url
    if(path === '/api/create' && method === 'POST') {
        // 命中该 url 后响应
        res.end('响应的内容')
    }

    // 不命中 url 后的响应
    res.end('404')
})

server.listen(3000)  // 可以监听 http 请求 

4、querystring

什么是 querystring:
- http://localhost:3000/api/list?keyword=abc&lang=en&a=120
- url 问号 ? 后面的都是 querystring(也叫 url 参数)
- 使用 & 符号分割,以 key=value 的形式存在,可继续扩展

querystring 的作用:
- 服务端拿到 querystring
- 根据参数(querystring)的不同,返回不同的内容,实现动态网页
- 即变化 querystring,就是变换内容(只要服务端支持)

const http = require('http')

const server = http.createServer((req, res) => {
    // 获取到请求地址中的 url
    const url = req.url
    // 这一步是为了去掉 url 中的参数,从而获得真正的 url
    const path = url.split('?')[0]
    // 获取 querystring
    const queryStr = url.split('?')[1] // type=0 或 1
    // 获取到请求地址中的 method
    const method = req.method

    // querystring 解析原理
    const query = {}
    if(queryStr) {
        queryStr.split('&').forEach(item => {
            const key = item.split('=')[0]  // type
            const val = item.split('=')[1]  // 0 或 1
            query[key] = val  // {'type':'0'}
        })
    }

    // 模拟获取信息的 url
    if(path === '/api/list' && method === 'GET') {
        if(query.type === '0') {
            // 当类型为 0 时执行的操作
            res.end('响应的内容')
        }
        if(query.type === '1') {
            // 当类型为 1 时执行的操作
            res.end('响应的内容')
        }
    }

    // 不命中 url 后的响应
    res.end('404')
})

server.listen(3000)  // 可以监听 http 请求 

直接使用 nodejs 自带的 querystring:

const http = require('http')
// nodejs 自带的 querystring
const querystring = require('querystring')

const server = http.createServer((req, res) => {
    // 获取到请求地址中的 url
    const url = req.url
    // 这一步是为了去掉 url 中的参数,从而获得真正的 url
    const path = url.split('?')[0]
    // 获取到请求地址中的 method
    const method = req.method

    // 使用 nodejs 自带的 querystring 解析
    const query = querystring.parse(queryStr || '')

    // 模拟获取信息的 url
    if(path === '/api/list' && method === 'GET') {
        if(query.type === '0') {
            // 当类型为 0 时执行的操作
            res.end('响应的内容')
        }
        if(query.type === '1') {
            // 当类型为 1 时执行的操作
            res.end('响应的内容')
        }
    }

    // 不命中 url 后的响应
    res.end('404')
})

server.listen(3000)  // 可以监听 http 请求 

url 的 hash 是否能起 querystring 同样的作用:
- hash 不能让服务端获取,以实现动态网页
- hash 形式:http://localhost:3000/api/list/#/home
- “#”后面的就是 hash

论:结构化与非结构化:
- 结构化的数据,易于通过程序访问和分析,如:对象、数组
- 非结构化的数据,不易通过程序分析,如:字符串
- 编程中的数据,都尽量结构化

5、res 返回数据

使用 res 设置返回状态码、Content-type、Body:
- res.writeHead(状态码, 请求头参数)

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

如何返回 JSON 数据:
- res.end() 只能返回字符串形式的内容
- 字符串的 Content-type 值是: text/plain

const http = require('http')
// nodejs 自带的 querystring
const querystring = require('querystring')

const server = http.createServer((req, res) => {
    // 获取到请求地址中的 url
    const url = req.url
    // 这一步是为了去掉 url 中的参数,从而获得真正的 url
    const path = url.split('?')[0]
    // 获取到请求地址中的 method
    const method = req.method

    // 使用 nodejs 自带的 querystring 解析
    const query = querystring.parse(queryStr || '')

    // 模拟获取信息的 url
    if(path === '/api/list' && method === 'GET') {
        const result = {
            errno: 0,
            data: [
                { username: 'zll', content: 'content1'},
                { username: 'lgk', content: 'content2'}
            ]
        }
        res.writeHead(200, { 'Content-type': 'application/json' })
        // JSON.stringify(result) 将 JSON 转为 字符串形式
        res.end(JSON.stringify(result))
    }

    // 模拟发送信息的 url
    if(path === '/api/create' && method === 'POST') {
        const result = {
            errno: 0,
            message: '发送信息成功'
        }
        res.writeHead(200, { 'Content-type': 'application/json' })
        // JSON.stringify(result) 将 JSON 转为 字符串形式
        res.end(JSON.stringify(result))
    }

    // 不命中 url 后的响应
    res.writeHead(404, { 'Content-type': 'text/plain' })
    res.end('404 Not Found')
})

server.listen(3000)  // 可以监听 http 请求 

如何返回 html 数据:
- 设置 Content-type: text/html
- res.end(…)
- 浏览器会根据 Content-type 识别出 html 格式

// 示例
res.writeHead(404, { 'Content-type': 'text/html' })
res.end(`
    <!DOCTYPE html>
    <html>
        <head>
            <title>404</title>
        </head>
        <body>
            <h1>404 Not Found</h1>
        </body>
    </html>
`)

6、获取 Request Body

流 stream 数据:
- 服务端 res.end(),会自动以 流 的形式返回
- 浏览器会识别到 流,并持续接收信息
- 待全部接收完,再做处理(视频是一段一段的播放)

演示获取 Request Body:

    // 模拟发送信息的 url
    if(path === '/api/create' && method === 'POST') {

        const reqType = req.headers['content-type']

        let bodyStr = ''

        // req.on('data') 方法用于服务端去识别“流”,并接受数据
        req.on('data', chunk => {
            // chunk 即“流”的每一段数据
            // 拼接每一段流形式完整的数据
            // 这里要使用 toString() 将每一段流转为字符串形式
            bodyStr = bodyStr + chunk.toString()
        })
        // 该方法可以使服务端知道数据传完了
        req.on('end', () => {
            if(reqType === 'application/json') {
                // 将数据转为 JSON 格式
                const body = JSON.parse(bodyStr)
            }

            res.end('接收完成') // 异步
        })

        return
    }

总结