Node.js

介绍

为什么要学 Node.js

  • 废话,你学前端得知道这个,招聘信息里 xx 没写么(具有服务端开发经验,拥有基本的网站开发能力,服务端,前端,运维部署),愣着干嘛学啊?
  • 帮助打开服务端。
  • 更好配合服务端开发人员进行协同开发。

Node.js 是什么

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

  • 不是一门语言,不是库,不是框架

  • 是一个 JavaScript 运行时的环境

  • 可以做服务端的语言,假如后台服务器是个黑盒子,那么 Node 就是帮你打开这个黑盒子的工具。当然,不仅仅是 Node 可以,以下的也可以

    • Java
    • PHP
    • Python
    • Ruby (GitHub 网站)
    • .Net (C#)
    • Node.js (采用 JavaScript,作为一个前端,不需要为了学后台而去学习新的语言了)
  • 凡是能用 JavaScript 来实现的,最终都能用 JavaScript 来实现。

  • https://nodejs.org/zh-cn/ 这个 Node 的中文网站

浏览器的 JavaScript Node.js 中的 JavaScript
ECMAScript ECMAScript
BOM -
Dom -
- 提供服务器级别 API
  • 开源生产库npm,他是世界上最大的开源库生态系统,你可以用它来安装各种包。
  • 构建在 Chrome 的 V8 引擎上
    • 我们写的代码就是字符串而已,然而浏览器引擎可以帮你去解析和执行,目前 Chrome V8 是公认最快的引擎
    • Node.js 的作者把 V8 引擎移植出来,开发了独立的运行环境。

Node.js 能做什么

  • Web 服务器后台
  • 游戏/结果服务器
  • 命令行工具
    • npm(node)
    • git(c 语言)
    • Hexo(node)

要学会的

  • B/S 编程模型
    • Browser-Server
    • Node 只是我们学习 BS 编程模型的工具
  • 模块化编程
    • CSS @import('文件路径')
    • 在 Node 中可以像@import()一样来引用加载 JavaScript
  • Node 的常用 API
  • 异步编程
    • 回调函数
  • Express 开发框架
  • EcmaSctipt6 语法

起步

安装 Node

  • 确认 Node 环境是否安装成功
    • 打开命令行,输入node -v,如果有版本号显示,就说明成功了。

Hello World

  1. 创建并编写 JavaScript 脚本文件
  2. 打开终端,定位脚本文件所在目录
  3. 输入node 文件名,执行脚本(注意不要把文件名字起成 node.js)

NPM

  • node package manager 是包管理工具

安装命令

  • npm install 包名 [–save]

  • –save 会将安装的包记录到 package.json 的 dependencies 中

  • npm i 包名 -D //-d 是–save-dev 缩写

  • 将包安装记录到 devDependencies 中,执行 npm install 时,可以指定安装哪些包

  • package.json用于记录项目中用了那些包,方便管理

语义化版本

  • ^version:中版本

​ ^1.0.1 -> 1.x.x

  • ~version:小版本

​ ~1.0.1 -> 1.0.x

  • version:指定版本

命令行工具

npm 常用命令

  • npm --version npm 版本号

  • npm install --global npm 升级 npm

  • npm init 构建项目

1
2
3
4
5
6
7
8
9
package name:包的名字,默认为当前目录名称 (名字不能有大写)
version:版本,默认从 1.0.0 开始
description:自述文件的信息或空字符串 ""
entry point: 入口文件
test command: 可以为空,npm test 为测试模块的命令
git repository:git 仓库
keywords:关键词
author:作者
license: 许可证 ISC
  • npm init -y 跳过向导快速生成

  • npm install根据 package.json 自动下载所需的包,也可以简写成npm i

  • npm install 包名 --save

npm i -S

​ 根据包名下载,并保存依赖在项目中的 package.json文件中

  • npm uninstall 包名 --save

npm un -S

​ 根据包名删除,并且删除依赖项

  • npm help查看帮助

  • npm 命令 --help 查看命令帮助

npm 镜像

自定义工具包

  • 你可以自己写一个工具,然后再你的项目中导入,你的工具不要放在node-module里,不然别人会以为你的是第三方包

API

核心模块

Node 为 JavaScript 提供了很多服务器级别的 API ,这些 API 绝大多数都被包装到了一个具名的核心模块中了。例如文件操作的fs核心模块,http 服务构建的 http模块,path路径操作模块,os操作系统模块等。

读取文件

  • 浏览器中的 JavaScript 是没有文件操作的能力的,但是 Node 中的 JavaScript 具有文件操作的能力

  • fs 是 Filesystem 的简写,就是文件系统的意思
    在 Node 中如果想要进行文件操作,就必须引入 fs 这个核心模块
    在 fs 这个核心模块中,就提供了所有的文件操作相关的 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1. 加载使用 require 方法加载 fs 核心模块
var fs = require("fs");

// 2. 读取文件
/*
第一个参数:文件路径
第二个参数:回调函数(err,data)
如果成功读取文件:data=数据, err=null
失败:data=null,err=错误对象
相对路径必须加./
如果想要返回上层目录用 ../
*/
fs.readFile("./a.txt", function (error, data) {
console.log(error);
console.log(data);
//<Buffer 6e 69 68 61 6f>

// 文件中存储的其实都是二进制数据 0 1
// 这里为什么看到的不是 0 和 1 呢?原因时二进制转为 16 进制
// 我们可以通过toString()转化
console.log(data.toString());
});

写文件

  • fs.writeFile('文件路径', '写入内容', '回调函数')

  • 写文件的文件名中不能有特殊字符,比方说?,<,’>’之类的。

1
2
3
4
5
6
7
8
9
// 写文件
var fs = require('fs')
fs.writeFile('../data/nihao.md', '你好,我叫Node', function (error) {
if(error === null){
return
}else{
console.log('文件写入成功')
}
});

创建个简单的服务器

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
// 非常轻松的构建一个 Web 服务器

// 1. 加载 http 核心模块
var http = require("http");

// 2. 创建服务
// 返回一个 Server 实例
var server = http.createServer();

// 3. 服务器能干吗
// 提供服务:对数据的服务
// 注册 request 请求事件
server.on("request", function (request, response) {
var url = request.url;
response.write(url);
response.end();
// console.log('收到客户端的请求了')

// response 对象有一个方法:write 可以用来给客户端发送相应程序
// write 可以使用多次,但是最后一定要使用 end 来结束响应
});

// 凡是需要网路通信的都需要端口号
server.listen(3000, function () {
console.log("服务器启动成功了!");
});

设置编码

  • 我们需要告诉浏览器我是中文操作系统,你得给我翻译成中文,不然我看不懂啊
  • Content-Type 告诉对方我给你发送的数据类型是什么类型
  • text/plain https://tool.oschina.net/commons
  • https://tool.oschina.net/ 在这里可以对应查找一些有用的
1
2
3
4
5
6
7
8
9
server.on("request", function (req, res) {
// 服务器默认发送的数据,其实是 utf8 编码的内容
// 但是浏览器不知道你是 utf8 编码的内容
// 世界上所有的语言韩文日文等,都是 utf8
// 中文操作系统默认是 gbk
// 所以我们要告诉浏览器,我给你发送的内容是什么
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.end("hello 世界");
});

读取目录

1
2
3
4
5
6
fs.readdir("D:\\study\\前端\\Node重学", function (err, files) {
if (err) {
return "目录不存在";
}
console.log(files);
});

回调地域

问题描述:代码
在这里插入图片描述

解决的方案可以这样

1
2
3
4
5
6
7
8
9
10
11
12
var fs = require("fs");
fs.readFile("./text1.txt", function (err, data) {
setTimeout(() => {
console.log(data.toString());
fs.readFile("./text2.txt", function (err, data) {
console.log(data.toString());
fs.readFile("./text3.txt", function (err, data) {
console.log(data.toString());
});
});
}, 2000);
});

但是这样一层层的嵌套,多了的话会出现如下情况,非常的不美观回调地域

如何解决呢,用promise

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
var fs = require("fs");
// resolve 承诺成功,reject承诺失败

new Promise((resolve, reject) => {
setTimeout(() => {
fs.readFile("./text1.txt", function (err, data) {
resolve(data);
});
}, 1000);
})
.then(function (data) {
console.log(data.toString());
return new Promise(function (resolve, reject) {
fs.readFile("./text2.txt", function (err, data) {
resolve(data);
});
});
})
.then(function (data) {
console.log(data.toString());
return new Promise(function (resolve, reject) {
fs.readFile("./text3.txt", function (err, data) {
resolve(data);
});
});
})
.then(function (data) {
console.log(data.toString());
});

模块化

简单的模块化

  • require这个方法专门用来引入模块

我有三个文件a.js b.js c.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// a.js

// require 是一个方法
// 他的作用就是用来加载模块
// 在Node 中,模块有三种:
// 1.核心 2.用户自己编写的 3.
// 相对路径必须加./
console.log("开始执行a");
require("./b.js");
console.log("结束执行a");

//开始执行a
// b.js文件被执行
// 结束执行a
1
2
3
4
// b.js
console.log("b.js start");
require("./c.js");
console.log("b.js end");
1
2
// c.js
console.log("ccccc");
1
2
3
4
5
# 开始执行a
# b.js start
# ccccc
# b.js end
# 结束执行a

模块作用域

  • 简单的说,外部访问不到内部,内部也访问不到外部,就在这个文件里面。
  • 同名的变量名字,在不同的模块里,也不会相互污染

模块间的通信

我现在又有两个a.js,b.js,分别是

1
2
3
4
5
6
7
8
9
10
// a.js

var app = require("./b.js");
// require 方法有两个最哦用
// 1.加载文件模块并执行里面的代码
// 2. 拿到被加载文件模块导出的接口对象

// 在每一个文件模块中都提供一个对象:expore
console.log("app:", app);
console.log("app:", app.foo);
1
2
3
4
5
// b.js
var foo = "bbb";
exports.foo = foo;
// 导出的是一个对象,不论是变量还是方法,都需要a文件里点出来使用
console.log("exports:", exports);

运行之后的结果是

1
2
3
exports: {}
app: {}
# 他俩是等价的

如果你想引用的话,就要在 b 中把想要暴露的变量挂载到exports


Express 模块

  • 就是 http 原生的封装,为了便于开发

响应头

  • 如果访问的网站,之后下载了,这是响应头的问题
  • res.set('Content-Type, 'text/html')

指定公开路径

1
2
3
4
5
app.use('/public',express.static('./public/'));
访问路径:http://localhost:8000/public/test.html

app.use(express.static('./public/'));
访问路径:http://localhost:8000/test.html

模板引擎

1
2
npm install --save art-template
npm install --save express-art-template
  • 默认是在views这个文件夹名称下寻找页面
  • 使用的方法如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var express = require("express");
var app = express();
//配置模板引擎 以.art结尾时使用模板引擎
app.engine("art", require("express-art-template"));
app.set("view options", {
debug: process.env.NODE_ENV !== "production", // 配置生产模式
});

app.get("/", function (req, res) {
//express为response提供了一个方法:render,此方法默认不可以使用,配置了模板引擎才可以使用
//res.render('模板名',{模板数据})
//第一个参数, 默认去项目的views目录中查找,如果想修改默认路径:app.set("views",'路径')
res.render("index.art", {
user: {
name: "aui",
tags: ["art", "template", "nodejs"],
},
});
});

中间件

中间件(Middleware) 是一个函数,它可以访问请求对象(request obj ect (req)), 响应对象(response object (res)), 和 web 应用中处于请求-响应循环流程中的中间件,一般被命名为 next 的变量。

  • 新版本中的express内置集成了中间件
  • express中没有内置获取表单psot请求体的Api:使用中间件
  • 应用级中间件绑定到 app 对象 使用 app.use()

热启动

  • 改代码的时候就不需要在启动一次服务器了

  • 安装

1
npm i --global nodemon

​ 使用

1
nodemon  app.js

端口号

  • 所有联网的程序都需要进行网络通信
  • 计算机中只有一个物理网卡,同一个局域网中,网卡的地址必须唯一的。网卡是通过唯一的 IP 地址来进行定位的。
  • 每一个联网的应用对应一个端口号
  • IP 地址用来定位计算机,端口号用来定位具体的联网应用程序
  • 获取访问我的另一条电脑的 IP 地址和端口号req.socket.remoteAddress req.socket.remotePort
  • 端口号的范围从 0~65536 之间
  • 网站上线部署的时候默认是 80 端口,所以 80 的话尽量不要用

跨域以及同源策略

  • 只有当协议(http,https),域名,和端口号三者都一直的时候,才是同源
  • 同源安全是浏览器行为,请求到后台的话后台也会处理,但是返回到前台浏览器不允许展示
  • 如何解决跨域:第一种方法是后台设置响应头res.header('Access-Control-Allow-Origin', '*') ,其中的*可以写成同意跨域的 IP 地址,第二种方法是前台设置 JsonP。

其他

  • GFM Markdown 的语法标准
  • 代码风格
    • JavaScript Standard Style
    • 爱彼迎
  • 代码风格

    • 当你采用了无分号的代码风格的时候,只需要注意一下情况就不会有上面的问题
1
2
3
4
5
say();
//当一行代码是以( [ ` 开头的时候,则在前面补上一个分号用以鼊一些语法解析错误
(function () {
console.log("hello");
})();