Node学习笔记(一)

Posted by Ivens on January 29, 2020

一. Node.js 是什么?

Node.js® 是一个基于 Chrome V8 引擎 的 JavaScript 运行环境。

  • Node.js 不是语言
  • Node.js 不是框架、不是库
  • Node.js 可以解析和执行 JavaScript 代码

所谓“运行环境”有两层意思: 首先,JavaScript 语言通过 Node 在服务器运行,在这个意义上,Node有点像 JavaScript虚拟机;其次,Node 提供大量工具库,使得 JavaScript 语言与操作系统互动(比如读写文件、新建子进程),在这个意义上,Node又是 JavaScript的工具库

Node 内部采用 Google 司的V8引擎,作为 JavaScript 语言解释器;通过自行开发的 libuv 库,调用操作系统资源。

现在的 JavaScript 可以脱离浏览器运行, 这归功于 Node.js

与浏览器中 JavaScript 的区别?

  • 没有 DOM, 没有 BOM
  • Node.js 提供了一些服务器级别的操作 API
    • 例如文件读写
    • 网络通信, 等 …

二. Node.js 能做什么?

  • Web 服务器后台
  • 命令行工具
    • npm
    • Hexo
    • Webpack
    • Gulp
    • ……

三. Hello Node

创建 helloNode.js 文件, 写入:

1
2
var foo = 'hello Node'
console.log(foo)

打开终端定位 js 文件所属目录, 输入 node ./helloNode.js, 终端会显示结果:

PS F:\Software\Node.js\Node.js-Learning-Repository\src\2020-1-29> node .\helloNode.js

hello Node

四. 简单的文件 IO 操作

1. 读文件

1
2
3
4
5
6
7
8
9
10
11
12
13
// 1. 使用 require 方法加载 fs 核心模块
var fs = require('fs')

// 2. 读取文件
fs.readFile('../../README.md', function (err, data) {
  // 如果回调函数成功, err 返回 null, data 返回二进制数据
  // 如果回调函数失败, err 返回 Error 对象, data 返回 null
  if (err) {
    console.log(err)
  } else {
    console.log(data.toString())
  }
})

2. 写文件

1
2
3
4
5
6
7
8
9
10
11
12
// 1. 使用 require 方法加载 fs 核心模块
var fs = require('fs')

// 2. 写入文件
fs.writeFile('./write.txt', '这是用来写入的 txt 文件!', function (err) {
  // 如果回调函数成功, err 返回 null, 失败则返回 Error 对象.
  if (err) {
    console.log(error)
  } else {
    console.log('成功写入数据!')
  }
})

五. 简单的 HTTP 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 1. 加载 http 核心模块
var http = require('http')

// 2. 使用 http.createSever() 创建一个 Web 服务器
var server = http.createServer()

server.on('request', (request, response) => {
  // 3.1. 接收请求
  console.log('收到客户端请求!' + request.url)

  // 3.2. 发出响应, 根据 url(统一资源定位符) 不同分配不同响应
  switch (request.url) {
    // 如果访问 'http://127.0.0.1:3000/', 如下响应:
    case '/':
      response.write('Hello this page is index')
      response.end()
      break;
    
    // 如果访问 'http://127.0.0.1:3000/b', 响应信息也可以这样写, 等于上面的 write + end, 如下响应:
    case '/b':
      response.end('Hello this b page')
      break;
  
    // 如果访问 'http://127.0.0.1:3000/goods', 响应信息为 json 格式, 我们需要将它转换成 String 或 Buffer 类型写入响应.
    case '/goods':
      let goods = [
        {
          name: 'apple',
          price: 10
        },
        {
          name: 'banana',
          price: 5
        }
      ]
   // 如果要响应的信息包含中文字符, 需要设置响应头
   // response.setHeader('Content-Type', 'text/plain; charset = utf-8')
      response.end(JSON.stringify(goods))
      break;
      
    default:
      break;
  }
})

// 4. 侦听 3000 端口, 启动服务器
server.listen(3000, function () {  
  console.log('服务器启动成功!')
})

在命令行中启动 js 文件

PS F:\Software\Node.js\Node.js-Learning-Repository\src\2020-1-29> node .\http.js
服务器启动成功!

1. 访问地址:http://127.0.0.1:3000/, 终端显示结果:

收到客户端请求!/
收到客户端请求!/favicon.ico

浏览器显示:

1
Hello this page is index

2. 访问地址: http://127.0.0.1:3000/b, 终端显示结果:

收到客户端请求!/b 
收到客户端请求!/favicon.ico

浏览器显示:

1
Hello this b page

3. 访问 http://127.0.0.1:3000/goods, 终端显示结果:

收到客户端请求!/goods
收到客户端请求!/favicon.ico

浏览器显示:

1
2
3
4
5
6
7
8
9
10
[
    {
        "name": "apple",
        "price": 10
    },
    {
        "name": "banana",
        "price": 5
    }
]

我们通过一个简单的 switch case 语句, 为 http 请求增加了转发功能.

当我们需要发送响应数据是中文字符串的时候, 我们需要在设置响应头中指定 utf-8 编码, 发送不同格式的文件对应不同的响应头, 可以参考以下网址:

常用对照表

六. Node 中的模块化

1. 核心模块

Node.js 为 JavaScript 提供了很多服务器级别的 API. 这些 API 绝大多数都被包装到了一个具名的核心模块中了.

例如:

  • 文件操作 => fs 模块
  • http 服务 => http 模块
  • 路径操作 => path 模块
  • 操作系统信息 => os 模块

2. 用户自定义模块

  • 从外部引入 => require

  • 导出到外部 => exports

新建两个文件: main.jsa.js, 目标在 main.js 中引用外部 js 文件.

main.js代码如下:

1
2
3
4
5
6
7
8
9
console.log('主函数开始执行');

// 引入本目录下的 a.js 文件, 拓展名可省略
var aRequire = require('./a')

// 打印 a.js 中 exports 的 add 方法的结果
console.log(aRequire.add(1, 2));

console.log('主函数结束执行');

a.js 代码如下:

1
2
3
4
5
6
7
8
9
10
11
console.log('现在执行 a.js');

// 定义一个 add 函数
var add = function (a, b)  {  
  return a + b;
}

// 将 add 函数挂载到 exports 上
exports.add = add

console.log('结束执行 a.js');

在命令行中执行 main.js 文件, 结果如下:

1
2
3
4
5
6
PS F:\Software\Node.js\Node.js-Learning-Repository\src\2020-1-30\require> node .\main.js
主函数开始执行
现在执行 a.js 
结束执行 a.js 
3
主函数结束执行

如果一个模块需要直接导出某个成员, 而非使用 exports 挂载, 必须使用以下方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = 'hello'

// 后面一个 module.exports 会替换前面一个导出
module.exports = function (x, y) {
  return x + y
}

// 可以使用这种格式一次导出多个接口成员
module.exports = {
  add: function (x, y) {
  	return x + y  
  },
  str: 'hello'
}

2.1. 优先从缓存加载

如果在主函数中多次 require 一个模块, Node 会优先从缓存中拿到接口对象, 而不会重复执行代码, 这有利于提高程序的性能.

3. 第三方库

require 加载第三方库文件的步骤:

  1. 先找到当前目录下的 node_modules 文件夹
  2. 找到对应的第三方库文件夹
  3. 根据 package.jsonmain属性进入对应的 js 文件入口

3.1. 如果找不到这个库的文件夹怎么办?

答: Node 会进入上一级目录中的 node_modules 目录查找, 如皋还找不到就继续向上级目录找.

3.2. 如果 package.json文件或 main 入口不存在怎么办?

答: Node 会自动执行目录下的 index.js 文件.

4. Node 模块化原理

Node.js 中, 每个模块内部都有一个自己的 module 对象, 在该对象中有一个成员叫 : exports

1
2
3
4
5
var module = {
  exports: {
    ...
  }
}

并且默认在代码最后有一句:

1
return module.exports

这就完成了模块的导出.

刚刚在上文看到有两种导出的方法

方法一:

1
2
3
4
5
6
module.exports = {
	str: 'hello',
  add: function (x, y) {
    return x + y;
  }
}

这种写法容易理解, 等于直接替换掉了原本要 return 出的对象.

方法二:

1
2
3
4
exports.str = 'hello'
exports.add = function (x, y) {
  return x + y;
}

乍一看这样写难以理解, 其实在 Node 中每个模块还有一句定义:

1
var exports = module.exports

上面挂载的语句也可以理解为:

1
2
3
4
module.exports.str = 'hello'
module.exports.add = function (x, y) {
  return x + y;
}

[1] 深入浅出 Node.js(三):深入 Node.js 的模块机制

Express 的使用

高度包容、快速而极简的 Node.js Web 框架.

1
2
// 安装
$ npm install express --save
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 0. 导包
var express = require('express')

// 1. 创建你的服务器应用程序, 相当于原生 Node 中的 http.createServer()
var app = express();

// 2. 指定公开目录
app.use('/public/', express.static('./public'))

// 3. 配置路由
app.get('/', function (req, res) {
  res.send('Hello World!');
});

// 4. 侦听 3000 端口
app.listen(3000, function () {  
  console.log('正在侦听 3000 端口');
})

1. 公开目录的三种方式

指定开放的目录,即可访问此目录下的所有文件。

1.可以访问此目录下的文件资源 , 即 http://localhost:3000/static/home.html

1
app.use('/static/',express.static('./static')); ---> 用的多

2.当没有第一个参数时,访问时也要删除第一个参数 , 即 http://localhost:3000/home.html

1
app.use(express.static('./static'));

3.可以给目录起别名 即 http://localhost:3000/public/home.htmlstatic 的别名是 public

1
app.use('/public/',express.static('./static'));

2. Express 中解析 Get 和 Post 的请求体

如果使用 Get 请求, 可以在路由中使用 req.query 打印请求体. 如果使用 Post 请求, 获取请求体相对麻烦, 没有现成的 API 可以调用.

获取 Post 请求体的步骤:

  1. 安装 body-parser
1
$ npm install --save body-parser
  1. 配置
1
2
3
4
5
6
7
8
9
10
11
var express = require('express')
// 0. 导包
var bodyParser = require('body-parser')

var app = express()

// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))

// parse application/json
app.use(bodyParser.json())
  1. 使用
1
2
3
4
5
app.post('/login', function (req, res) {
	var userInfo = req.body
	console.log(userInfo)
	res.render('index.html') 
})

art-template 的使用

art-template 是一个简约、超快的模板引擎

它采用作用域预声明的技术来优化模板渲染速度,从而获得接近 JavaScript 极限的运行性能,并且同时支持 NodeJS 和浏览器。

在 Express 中使用 art-template

安装:

1
2
npm install --save art-template
npm install --save express-art-template

配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var express = require('express');
var app = express();

// view engine setup, 默认会从目录下的 views 文件夹中寻找 .art 文件. 如果需要改成 .html 文件可以将 art 改成 html 即可.
app.engine('art', require('express-art-template'));

// routes
app.get('/', function (req, res) {
    res.render('index.art', {
        user: {
            name: 'aui',
            tags: ['art', 'template', 'nodejs']
        }
    });
});

公共顶部与公共底部

七. 代码风格

两种使用较多的标准风格

  • JavaScript Standard Style
  • Airbnb JavaScript Style

如果不采用无分号风格, 应注意:

当开头为以下三种时, 则需在前面补上一个分号用以避免一些语法解析错误

  1. (
  2. [
  3. `

MongoDB

Promise

回调地狱:

Node.js 的代码是异步执行的, 当我们需要代码按照顺序挨个执行的时候第一我们想到的是使用嵌套的方法使代码按照我们预想的顺序执行, 但是这会带来上图的这种情况.

ES6 给我们提供了更优雅的解决方法 Promise

其他

使用 nodemon 插件

可以实时更新代码变动, 不需要重启 Node 即可看到变化

1
2
3
4
// 安装
npm i -g nodemon
// 启动
nodemon app.js

一开始我没有选择全局安装, Git 报错, 我不知道是为什么. 后来换成全局安装错误就消失了.

package.json 与 package-lock.json 的关系

package-lock.json 会保存 node_modules 里所包含的所有第三方依赖信息.

  1. 可以提高 npm install 的速度
  2. 可以锁定依赖的版本号, 防止升级到新版本

__dirname__filename

  • dirname 动态获取当前文件模块所属目录的绝对路径
  • __filename 动态获取当前文件的绝对路径
  • __dirname__filename 是不受执行 node 命令所在的路径影响的

推荐使用: path.join() 来辅助拼接文件路径, 这样可以避免手动拼接的低级错误.