Node.js 全局对象
在 JavaScript 中全局对象通常是 window,而在 Node.js 中全局对象是 global。
全局变量
按照 ECMAScript 的定义,满足以下条件的变量是全局变量:
- 在最外层定义的变量。
- 全局对象的属性。
- 隐式定义的变量(未定义直接赋值的变量)。
下面介绍一些常用的全局变量和全局函数:
__filename表示当前正在执行的脚本的文件名。它将输出文件所在位置的绝对路径,且和命令行参数所指定的文件名不一定相同。如果在模块中,返回的值是模块文件的路径。比如创建一个叫 fnTest.js 的文件,输入以下代码:
console.log(__filename);
来看看运行效果:
__dirname表示当前执行脚本所在的目录。比如创建一个 dnTest.js 的文件,输入以下代码:
console.log(__dirname);
setTimeout(cb, ms)全局函数在指定的毫秒(ms)数后执行指定函数(cb),只执行一次函数。比如创建一个 st.js 的文件,输入以下代码:
function foo() {
console.log("Hello, syl!");
}
// 三秒后执行以上函数
setTimeout(foo, 3000);
来看看运行效果:
clearTimeout(t)用于停止一个之前通过setTimeout()创建的定时器。参数 t 是通过setTimeout()函数创建的定时器。比如清除上面案例的定时器:
function foo() {
console.log("Hello, syl!");
}
// 三秒后执行以上函数
var t = setTimeout(foo, 3000);
// 清除定时器
clearTimeout(t);
运行效果:
setInterval(cb, ms)与setTimeout(cb, ms)类似,不同的是这个方法会不停的执行函数。直到clearInterval()被调用或窗口被关闭,也可以按Ctrl + C停止。比如创建一个 sI.js 的文件,输入以下代码:
function foo() {
console.log("Hello, syl!");
}
// 三秒后执行以上函数
var t = setInterval(foo, 3000);
// 清除定时器
clearInterval(t);
运行效果:
console.log()是个全局函数用于进行标准输出流的输出,即在控制台中显示一行字符串,和 JavaScript 中的使用一样。
Node.js 创建第一个应用
所有语言的第一课都由 “hello world” 来开始,在使用我们的 Node.js 创建第一个应用之前,我们先来看看 Node.js 应用由哪几部分组成:
- 引入 required 模块:使用 required 指令来载入 Node.js 模块。
- 创建服务器:服务器可以监听客户端的请求,类似于 Apache、Nginx 等 HTTP 服务器。
- 接受请求与响应请求。
新建一个名为 server.js 的文件。
var http = require("http"); // 加载 http 模块,并将实例化的 HTTP 赋值给变量 http
http.createServer(function (request, response) {
// 发送 HTTP 头部
// HTTP 状态值: 200 : OK
// 内容类型: text/plain
response.writeHead(200, { "Content-Type": "text/plain" });
response.end("Hello World\n"); // 发送响应数据 "Hello World"
})
.listen(8080);
// 终端打印如下信息
console.log("Server running at http://127.0.0.1:8080/");
以上代码我们完成了一个可以工作的 HTTP 服务器。如果在本地计算机使用 node server.js 命令,你可以直接在浏览器中访问 http://127.0.0.1:8080/,你会看到一个写着 "Hello World"的网页。
node server.js
然后点击右侧的 Web 服务按钮启动 Web 服务(注意:只能使用 8080 端口,即默认端口,使用其它端口无法访问),可以在新打开的页面中查看运行效果。
浏览器运行效果:
Node.js 包
概述
包用于管理多个模块及其依赖关系,可以对多个模块进行封装,包的根目录必须包含 package.json 文件。package.json 文件是 CommonJS 规范用于描述包的文件,符合 CommonJS 规范的 package.json 文件一般包含以下字段:
- name:包名。包名是唯一的,只能包含小写字母、数字和下划线。
- version:包版本号。
- description:包说明。
- keywords:关键字数组,用于搜索。
- homepage:项目主页。
- bugs:提交 bug 的地址。
- license:许可证。
- maintainers:维护者数组。
- contributors:贡献者数组。
- repositories:项目仓库托管地址数组。
- dependencies:包依赖。
下面是一个 package.json 示例:
{
"name": "shiyanlou",
"description": "Shiyanlou test package.",
"version": "0.1.0",
"keywords": ["shiyanlou", "nodejs"],
"maintainers": [
{
"name": "test",
"email": "test@shiyanlou.com"
}
],
"contributors": [
{
"name": "test",
"web": "https://www.lanqiao.cn/"
}
],
"bugs": {
"mail": "test@shiyanlou.com",
"web": "https://www.lanqiao.cn/"
},
"licenses": [
{
"type": "Apache License v2",
"url": "http://www.apache.org/licenses/apache2.html"
}
],
"repositories": [
{
"type": "git",
"url": "http://github.com/test/test.git"
}
],
"dependencies": {
"webkit": "1.2",
"ssl": {
"gnutls": ["1.0", "2.0"],
"openssl": "0.9.8"
}
}
}
想要了解更多可以在:package.json 的介绍文档 查看。
其中版本说明
1、软件版本号
一般来讲大部分的软件版本号分3段,比如 A.B.C
- A 表示大版本号,一般当软件整体重写,或出现不向后兼容的改变时,增加A,A为零时表示软件还在开发阶段。
- B 表示功能更新,出现新功能时增加B
- C 表示小修改,如修复bug,只要有修改就增加C
2、(node.js中的)^和~区别
当我们查看项目配置文件package.json中已安装的库的时候,会发现他们的版本号之前都会加一个符号,有的是插入符号(^),有的是波浪符号(~)
- 当使用npm install 安装包时,默认会在包的版本号前面添加^符号
- 当在包的版本号前面插入波浪符号~时,表示当更新包时,锁定次版本,将补丁版本更至最新;例如 ~1.15.2 ,表示 >=1.15.2 && <1.16.0;
- 当在包的版本号前面插入符号^时,表示当更新包时,锁定主版本,将次版本更到最新;例如 \ ^3.3.4 ,表示 >=3.3.4 && <4.0.0
注:package.json 文件可以自己手动编辑,还可以通过 npm init 命令进行生成。你可以自己尝试在终端中输入 npm init 命令来生成一个包含 package.json 文件的包。直接输入 npm init --yes 跳过回答问题步骤,直接生成默认值的 package.json 文件。此外,我们在 github 上传自己项目的时候,通常是不会把 node_modules 这个文件夹传上去的(太大了),只需要有 package.json 就能通过 npm install 命令安装所有依赖。
包操作
通过命令 npm install xxx 来安装包。比如:
安装包:
npm install express
更新包:
npm update express
删除包:
npm uninstall express
想要学习更多 npm 的操作可以访问 npm 中文文档。
在 npm 社区中去查找包,再通过命令 npm install 模块名字 就可以安装。每个模块的名字全球唯一。
Node.js 中的函数
在 JavaScript 中,一个函数可以作为另一个函数的参数。我们可以先定义一个函数,然后把这个函数作为变量在另一个函数中传递,也可以在传递参数的地方直接定义函数。Node.js 中函数的使用与 Javascript 类似。
function sayHi(value) {
console.log(value);
}
function execute(someFunction, value) {
someFunction(value);
}
execute(sayHi, "hi");
这里的sayHi函数就可以称为回调函数
运行结果为:
上面的例子我们也可以写成:
function execute(someFunction, value) {
someFunction(value);
}
execute(function (value) {
console.log(value);
}, "hi");
匿名函数
匿名函数就是没有命名的函数。语法为:
function(){
}
箭头函数
ES6 标准新增了一种新的函数,箭头函数表达式的语法比函数表达式更短,并且没有自己的 this,arguments,super 或 new.target。这些函数表达式更适用于那些本来需要匿名函数的地方,并且它们不能用作构造函数。
语法为:
(参数 1, 参数 2, …, 参数 N) => {函数声明}
// 相当于:(参数 1, 参数 2, …, 参数 N) => {return 表达式;}
(参数 1, 参数 2, …, 参数 N) => 表达式(单一)
// 当只有一个参数时,圆括号是可选的
(单一参数) => {函数声明}
单一参数 => {函数声明}
// 没有参数的函数应该写成一对圆括号
() => {函数声明}
例子:
function(){
console.log('hello syl');
}
// 上面的匿名函数可以用箭头函数改写为下面的
() => console.log('hello syl');
因为我们在 Node.js 中会经常使用到匿名函数,为了缩减代码,我们可以使用箭头函数。一般来说上面语法中的简单使用就可以了,这里还是主要学习 Node.js 的操作,不过多的去深究箭头函数的其他用法,感兴趣的可以自行查阅 MDN|箭头函数。
Node.js 异步编程
Node.js 异步编程的直接体现就是回调。回调函数在完成任务后就会被调用,Node.js 使用了大量的回调函数,Node.js 所有 API 都支持回调函数。回调函数一般作为函数的最后一个参数出现。
阻塞代码实例
先创建一个 syl.txt 的文件,里面随便输入一段文本内容,比如:hello syl!。
创建 a.js 代码为:
var fs = require("fs");
var data = fs.readFileSync("syl.txt");
console.log(data.toString());
console.log("程序执行完毕!");
运行结果为:
非阻塞代码实例
把前面 a.js 的代码改为:
var fs = require("fs");
fs.readFile("syl.txt", function (err, data) {
if (err) return console.error(err);
console.log(data.toString());
});
console.log("程序执行完毕!");
运行结果为:
从这两个实例中我们可以初步地体验到阻塞和非阻塞的不同之处。第一个实例在文件读取完后才执行完程序。第二个实例我们不需要等待文件读取完,这样就可以在读取文件时同时执行接下来的代码,大大提高了程序的性能。我们将在后续 fs-文件系统章节中更加深入的去了解使用回调函数。
Node.js 事件
概述
大多数 Node.js 核心 API 构建于惯用的异步事件驱动架构,其中某些类型的对象(又称触发器,Emitter)会触发命名事件来调用函数(又称监听器,Listener)。比如:fs.readStream 打开文件时会发出一个事件。可以通过 require("events"); 获得 event 模块。通常,事件名采用“小驼峰式”(即第一个单词全小写,后面的单词首字母大写,其它字母小写)命名方式。
所有能触发事件的对象都是 EventEmitter 类的实例。这些对象有一个 eventEmitter.on() 函数,用于将一个或多个函数绑定到命名事件上。当 EventEmitter 对象触发一个事件时,所有绑定在该事件上的函数都会被同步地调用。
EventEmitter 类获取
// 引入 events 模块
var events = require("events");
// 创建 eventEmitter 对象
var eventEmitter = new events.EventEmitter();
添加监听器
emitter.on(eventName, listener)
使用 emitter.on(eventName, listener) 方法为指定事件注册一个监听器。添加 listener 函数到名为 eventName 的事件的监听器数组的末尾。不会检查 listener 是否已被添加。多次调用并传入相同的 eventName 与 listener 会导致 listener 会被添加多次。
参数说明:
- eventName:事件名称,string 类型。
- listener:回调函数。
例子:
// 引入 events 模块
var events = require("events");
// 创建 emitter 对象
var emitter = new events.EventEmitter();
// 为 connection 事件注册一个监听器
emitter.on("connection", function () {
console.log("已连接");
});
// 一秒后调用监听器
setTimeout(function () {
emitter.emit("connection");
}, 1000);
运行效果如下所示:
默认情况下,事件监听器会按照添加的顺序依次调用。emitter.prependListener() 方法可用于将事件监听器添加到监听器数组的开头。比如:
// 引入 events 模块
var events = require("events");
// 创建 server 对象
var emitter = new events.EventEmitter();
// 为 connection 事件注册一个监听器
emitter.on("connection", function () {
console.log("我是a");
});
// 箭头函数,有兴趣的可以自行了解一下
emitter.prependListener("connection", () => console.log("我是b"));
// 一秒后调用监听器
setTimeout(function () {
emitter.emit("connection");
}, 1000);
运行效果如下所示:
emitter.addListener(eventName, listener)
emitter.addListener(eventName, listener) 是 emitter.on(eventName, listener) 的别名。
调用监听器
使用 emitter.emit(eventName[, ...args]) 按照监听器注册的顺序,同步地调用每个注册到名为 eventName 的事件的监听器,并传入提供的参数。如果事件有注册监听返回 true,否则返回 false。
参数说明:
- eventName :事件名称
- args:传递的参数,多个,类型为任意。
例子:
// 引入 events 模块
var events = require("events");
// 创建 emitter 对象
var emitter = new events.EventEmitter();
// 定义一个回调函数
var callback1 = function (arg1, arg2) {
console.log("hello world", arg1, arg2);
};
var callback2 = function (arg3, arg4) {
console.log("hello stranger", arg3, arg4);
};
// 为 connection 事件注册监听器
emitter.on("connection", callback1);
emitter.on("connection", callback2);
// 调用监听器
emitter.emit("connection", "愿善良的人", "都能被温柔以待");
运行结果如下:
监听器在绑定后,每当命名事件被触发时,就会调用绑定的回调函数,可重复触发,如果我们想实现一个只执行一次的监听器该怎么做呢?
只执行一次的监听器
当使用 eventEmitter.on(eventName, listener) 注册监听器时,监听器会在每次触发命名事件时被调用。比如:
// 引入 events 模块
var events = require("events");
// 创建 emitter 对象
var emitter = new events.EventEmitter();
// 为 connection 事件注册一个监听器
var n = 0;
emitter.on("connection", function () {
++n;
console.log("调用第" + n + "次");
});
// 调用监听器
emitter.emit("connection");
emitter.emit("connection");
emitter.emit("connection");
emitter.emit("connection");
运行结果为:
使用 eventEmitter.once(eventName, listener) 可以注册最多可调用一次的监听器。当事件被触发时,监听器会被注销,然后再调用。比如:
// 引入 events 模块
var events = require("events");
// 创建 emitter 对象
var emitter = new events.EventEmitter();
// 为 connection 事件注册一个监听器
var n = 0;
emitter.once("connection", function () {
++n;
console.log("调用第" + n + "次");
});
// 调用监听器
emitter.emit("connection");
emitter.emit("connection");
emitter.emit("connection");
emitter.emit("connection");
运行结果为:
默认情况下,事件监听器会按照添加的顺序依次调用。emitter.prependOnceListener() 方法可用于将事件监听器添加到监听器数组的开头。用法与我们前面所学的 emitter.prependListener() 方法一致,区别在于这个方法注册的监听器最多只能调用一次,大家可以自行尝试写一下。
移除监听器
到目前为止,我们已经走完了过半的监听器的生命周期,接下来我们将学习如何移除监听器。
emitter.removeListener(eventName, listener)
使用 emitter.removeListener(eventName, listener) 移除监听器。
参数说明:
- eventName 事件名称。
- listener 监听器也就是回调函数名称。
例子:
// 引入 events 模块
var events = require("events");
// 创建 emitter 对象
var emitter = new events.EventEmitter();
// 定义一个回调函数
var callback = function () {
console.log("syl");
};
// 为 connection 事件注册一个监听器
emitter.on("connection", callback);
// 为 connection 事件移除监听器
emitter.removeListener("connection", callback);
// 调用监听器
emitter.emit("connection");
运行结果为:
注:removeListener() 最多只会从监听器数组中移除一个监听器。我们可以多次调用 removeListener() 的方式来一个个的移除我们需要移除掉的监听器。
一旦事件被触发,所有绑定到该事件的监听器都会按顺序依次调用。也就是说在事件触发之后、且最后一个监听器执行完成之前,removeListener() 或 removeAllListeners() 不会从 emit() 中移除它们。
例子:
// 引入 events 模块
var events = require("events");
// 创建 emitter 对象
var emitter = new events.EventEmitter();
// 定义回调函数
var callback1 = function () {
console.log("我是1");
emitter.removeListener("connection", callback2);
};
var callback2 = function () {
console.log("我是2");
};
// 为 connection 事件注册监听器
emitter.on("connection", callback1);
emitter.on("connection", callback2);
// 第一次调用监听器,callback1 移除了监听器 callback2,但它依然会被调用。触发时内部的监听器数组为 [callback1, callback2]
emitter.emit("connection");
// 第二次调用监听器,此时 callback2 已经被移除了。内部的监听器数组为 [callback1]
emitter.emit("connection");
运行结果为:
emitter.off(eventName, listener)
emitter.off(eventName, listener) 是 emitter.removeListener() 的别名。
emitter.removeAllListeners([eventName])
使用 emitter.removeAllListeners([eventName]) 移除全部监听器或指定的 eventName 事件的监听器。
例子:
// 引入 events 模块
var events = require("events");
// 创建 emitter 对象
var emitter = new events.EventEmitter();
// 定义回调函数
var callback1 = function () {
console.log("我是1");
};
var callback2 = function () {
console.log("我是2");
};
// 为 connection 事件注册监听器
emitter.on("connection", callback1);
emitter.on("connection", callback2);
// 移除 connection 事件的所有监听器
emitter.removeAllListeners("connection");
// 调用监听器
emitter.emit("connection");
运行结果为:
error 事件
当 EventEmitter 实例出错时,应该触发 'error' 事件。
如果没有为 'error' 事件注册监听器,则当 'error' 事件触发时,会抛出错误、打印堆栈跟踪、并退出 Node.js 进程。比如:
var events = require("events");
var emitter = new events.EventEmitter();
emitter.emit("error");
运行结果为:
通常我们要为会触发 error 事件的对象设置监听器,避免遇到错误后整个程序崩溃。比如:
// 引入 events 模块
var events = require("events");
// 创建 emitter 对象
var emitter = new events.EventEmitter();
// 设置监听器
emitter.on("error", (err) => {
console.error("错误信息");
});
emitter.emit("error");
运行结果为:
Node.js http
概述
Node.js 提供了 http 模块,http 模块主要用于搭建 HTTP 服务端和客户端,要使用 HTTP 服务器和客户端功能必须调用 http 模块,代码如下:
// 引入 http 模块
var http = require("http");
创建 http server
我们首先来回顾一下我们 Node.js 简介章节中创建的第一个应用的代码:
// 加载 http 模块
var http = require("http");
// 创建服务器
http
.createServer(function (request, response) {
// 发送 HTTP 头部
// HTTP 状态值: 200 : OK
// 内容类型: text/plain
response.writeHead(200, { "Content-Type": "text/plain" });
// 发送响应数据 "Hello World"
response.end("Hello World\n");
})
.listen(8080);
// 终端打印如下信息
console.log("Server running at http://127.0.0.1:8080/");
输入:
node server.js
启动 Web 服务(注意:只能使用 8080 端口,即默认端口,使用其它端口无法访问)。然后点击右侧工具中的 Web 服务按钮,即可访问 Web 服务。
最终的运行效果如下所示:
创建服务器
创建服务器使用如下代码:
http.createServer([requestListener]);
当然该方法属于 http 模块,所以我们要先引入 http 模块,requestListener 是一个请求函数,也就是我们上面所写的:
function(request, response){
// 函数内容
}
requestListener 请求函数是一个自动添加到 request 事件的函数(request 事件每次有请求时都会触发,初期学习我们清楚有这个东西就行,不过多的去追究)。函数传递有两个参数:request 请求对象 和 response 响应对象。我们调用 request 请求对象的属性和方法就可以拿到所有 HTTP 请求的信息,我们操作 response 响应对象的方法,就可以把 HTTP 响应返回给浏览器。
response 对象常用的方法有:
-
response.writeHead(statusCode[, statusMessage][, headers])。表示向请求发送响应头。
参数说明:
- statusCode:状态码,是一个 3 位 HTTP 状态码,如 404 表示网页未找到,200 表示正常。
- statusMessage:可选的,可以将用户可读的
statusMessage作为第二个参数。 - headers:响应头。也就是设置 Content-Type 的值,用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件。常用值有:(1)text/html:HTML 格式(2)text/plain:纯文本格式(3)application/x-www-form-urlencoded:数据被编码为名称/值对,这是标准的编码格式。其余的可以自行百度了解,比如 Content-Type 对照表。
比如:
response.writeHead(200, { "Content-Type": "text/plain;charset=UTF-8" });注:此方法只能在消息上调用一次,并且必须在调用
response.end()之前调用它。 -
response.write()发送一块响应主体,也就是说用来给客户端发送响应数据。可以直接写文本信息,也可以写我们的 html 代码,注意要设置 Content-Type 的值。write 可以使用多次,但是最后一定要使用 end 来结束响应,否则客户端会一直等待。 -
response.end()此方法向服务器发出信号,表示已发送所有响应头和主体,该服务器应该视为此消息完成。必须在每个响应上调用方法response.end()。
例子:
// 加载 http 模块
var http = require("http");
// 创建服务器
http
.createServer(function (request, response) {
// 发送 HTTP 头部
// HTTP 状态值: 200 : OK
// 内容类型: text/html
response.writeHead(200, { "Content-Type": "text/html;charset=UTF-8" });
// 发送响应数据 'hello syl'
response.write("hello syl");
// 发送数据 hello world 并且字体为 h1 格式
response.write("<h1>hello world</h1>");
// 结束
response.end();
// 上面的三行代码也可以直接写成 response.end('hello syl <h1>hello world</h1>');
})
.listen(8080);
// 终端打印如下信息
console.log("Server running at http://127.0.0.1:8080/");
最终的运行效果为:
request 对象:
request.url获取请求路径,获取到的是端口号之后的那一部分路径,也就是说所有的 url 都是以 / 开头的,判断路径处理响应。request.socket.localAddress获取 ip 地址。request.socket.remotePort获取源端口。
比如:
// 加载 http 模块
var http = require("http");
// 1. 创建 Server
var server = http.createServer();
// 2. 监听 request 请求事件,设置请求处理函数
server.on("request", function (req, res) {
console.log("收到请求了,请求路径是:" + req.url);
console.log(
"请求我的客户端的地址是:",
req.socket.remoteAddress,
req.socket.remotePort
);
var url = req.url;
res.writeHead(200, { "Content-Type": "text/html;charset=UTF-8" });
if (url === "/") {
res.end("<h1>Index page</h1>");
} else if (url === "/login") {
res.end("<h1>Login page</h1>");
} else {
res.end("404 Not Found.");
}
});
// 3. 绑定端口号,启动服务
server.listen(8080, function () {
console.log("服务器启动成功,可以访问了。。。");
});
运行效果为:
也就是说我们现在已经能够对不同的 url 做出相应的反应。
创建一个简单的网站
首先新建两个 html 文件,分别叫 index.html 和 register.html 案例代码很简单,大家可以自行尝试美化一下自己的 html 页面。
index.html 的代码为:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
</head>
<body>
<h1>42摄氏度:成都创出100年气象史上高温新纪录</h1>
2017年07月07日 <br />
10:43:96 来源: <strong>新华网</strong>
<hr />
<p>
<em
>新华网成都7月7日电(记者李小花)7日7时36分至47分这一时间段,成都气象观测站测得当日最高温达42摄氏度。这是成都有气象记录以来100年的高温新纪录,打破了此前1934年创下的40.2摄氏度的历史极值。</em
>
</p>
<p>
<ins>成都已经连续2天发出了最高等级的红色高温警报。</ins
>成都中心气象台首席服务官李刚说,今年副热带高压强度特别强,对成都地区的控制“实在太稳定了”,整个7月份基本上都处在它的势力范围之内。6日成都已出现了气象史上7月份“第四高”的高温值,这使得7日的“基础”气温就很高,超过了30摄氏度,然后不断地升温。此外,6日白天风小,又吹的是西南风,特别是在中午之后这一个最易出现高温的时段,光照又比较强,所以气温“直线飙升”,一举冲破历史极值,出现了“创纪录”的极端酷暑天。
</p>
<p>
在成都历史上,出现40摄氏度以上极端高温的几率并不大。根据相关资料,中心城区观测站
<font color="red" size="5">100年来仅出现了5次记录</font
>,除了这一次的新纪录,还有就是1934年7月12日的40.2摄氏度;1934年8月25日、2009年7月20日、2010年8月13日的40摄氏度。
</p>
<p>
<del
>由于气温实在太高,成都6日下午不少地区出现了热对流天气。气象台说,首先是双流地区,下起了热雷雨。到15时05分,全市大部分地区出现了分散性的雷电活动和热雷雨,中心城区徐家汇等地都响起了隆隆的雷声。</del
>
</p>
</body>
</html>
register.html 的代码为:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<form action="" method="post">
<fieldset>
<legend>注册</legend>
<!-- 文本输入框 -->
用户名:<input
type="text"
maxlength="4"
name="username"
value="实验楼"
/>
<br />
<br />
<!--邮箱-->
邮箱:<input type="email" id="email" name="email" multiple />
<br />
<br />
<!--电话号码-->
电话号码:<input type="tel" id="tel" name="tel" />
<br />
<br />
<!-- 密码输入框 -->
密码:<input type="password" name="pwd" />
<br />
<br />
<!-- 单选框 -->
<input type="radio" name="gender" checked="checked" />男
<input type="radio" name="gender" />女 <br /><br />
<!-- 下拉列表 -->
省(市):
<select>
<option>北京</option>
<option>上海</option>
<option>广东</option>
<option selected="selected">深圳</option>
</select>
<!-- 下拉列表多选 -->
<select multiple="multiple">
<option>北京</option>
<option>上海</option>
<option>广东</option>
<option selected="selected">深圳</option>
</select>
市(区):
<select>
<!-- 下拉列表信息分组 -->
<optgroup label="北京市">
<option>东城区</option>
<option>西城区</option>
<option>朝阳区</option>
<option>丰台区</option>
</optgroup>
<optgroup label="上海市">
<option>黄浦区</option>
<option>徐汇区</option>
<option>长宁区</option>
<option>静安区</option>
</optgroup>
</select>
<br /><br />
<!-- 多选框 -->
<input type="checkbox" checked="checked" />吃饭
<input type="checkbox" checked="checked" />睡觉
<input type="checkbox" checked="checked" />打豆豆
<!-- 多行文本框 -->
<textarea cols="130" rows="10"></textarea><br /><br />
<label for="myColor">你最爱的颜色是什么?</label>
<input type="text" name="myColor" id="myColor" list="mySuggestion" />
<datalist id="mySuggestion">
<option>black</option>
<option>blue</option>
<option>green</option>
<option>red</option>
<option>black</option>
<option>yellow</option>
</datalist>
<br /><br />
<!-- 文件上传控件 -->
<input type="file" />
<br /><br />
<!-- 文件提交按钮 -->
<input type="submit" />
<!-- 普通按钮 -->
<!-- <input type="button" value="普通按钮"> -->
<!-- 图片按钮 -->
<!--<input type="image" src="按钮.jpg">-->
<!-- 重置按钮 -->
<input type="reset" />
</fieldset>
</form>
</body>
</html>
然后我们新建一个 server.js 写我们的服务器代码,代码如下所示:
// 结合 fs 发送文件中的数据
// 引入 http 和 fs 模块
var http = require("http");
var fs = require("fs");
// 创建服务器
var server = http.createServer();
// 监听 request 请求事件,设置请求处理函数
server.on("request", function (req, res) {
// 处理 url
var url = req.url;
if (url === "/") {
// 下面注释代码的写法显然是不合理的
// res.end('<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Document</title></head><body><h1>首页</h1></body>/html>')
res.setHeader("Content-Type", "text/plain");
// 我们要发送的还是在文件中的内容
fs.readFile("./index.html", function (err, data) {
if (err) {
res.end("文件读取失败,请稍后重试!");
} else {
// data 默认是二进制数据,可以通过 .toString 转为咱们能识别的字符串
// res.end() 支持两种数据类型,一种是二进制,一种是字符串
res.writeHead(200, {
"Content-Type": "text/html",
});
res.end(data);
}
});
} else if (url === "/register") {
// url:统一资源定位符
// 一个 url 最终其实是要对应到一个资源的
fs.readFile("./register.html", function (err, data) {
if (err) {
res.end("文件读取失败,请稍后重试!");
} else {
//setHeader也是设置响应头,它们将与传递给 response.writeHead() 的任何响应头合并,其中 response.writeHead() 的响应头优先。
res.setHeader("Content-Type", "text/html");
res.end(data);
}
});
} else {
res.end("<h1>404 Not Found.</h1>");
}
});
server.listen(8080, function () {
console.log("Server is running...");
});
来看看运行效果:
也就是说我们可以通过更改请求路径来响应出不同的 html 页面,当然这是我们自己写的,用户的话可能是点击一个链接,比如:
<a href="/user/logout">注销</a>
然后就和我们上面设置的一样,写个判断语句,当请求路径是某个路径的时候,响应一个相应的页面出来。大家可以自行尝试一下,写一个导航栏页面,能够跳转到其他页面,并且能够跳转回来。
Node.js fs
异步打开文件的语法格式为:
fs.open(path, flags[, mode], callback);
参数说明:
- path:文件的路径
- flags:文件打开的行为。
- mode:设置文件模式(权限),文件创建默认权限为 0o666(可读写)。mode 设置文件模式(权限和粘滞位),但仅限于创建文件的情况。在 Windows 上,只能操作写权限。
- callback:回调函数,带有两个参数如:callback(err, fd)。
flags 参数可以是以下值:
- 'a' - 打开文件用于追加。如果文件不存在,则创建该文件。
- 'ax' - 与 'a' 相似,但如果路径存在则失败。
- 'a+' - 打开文件用于读取和追加。如果文件不存在,则创建该文件。
- 'ax+' - 与 'a+' 相似,但如果路径存在则失败。
- 'as' - 以同步模式打开文件用于追加。如果文件不存在,则创建该文件。
- 'as+' - 以同步模式打开文件用于读取和追加。如果文件不存在,则创建该文件。
- 'r' - 打开文件用于读取。如果文件不存在,则会发生异常。
- 'r+' - 打开文件用于读取和写入。如果文件不存在,则会发生异常。
- 'rs+' - 以同步模式打开文件用于读取和写入。指示操作系统绕开本地文件系统缓存。这对于在 NFS 挂载上打开文件非常有用,因为它允许跳过可能过时的本地缓存。它对 I/O 性能有非常实际的影响,因此除非需要,否则不建议使用此标志。这不会将
fs.open()或fsPromises.open()转换为同步的阻塞调用。如果需要同步操作,则应使用fs.openSync()之类的操作。 - 'w' - 打开文件用于写入。创建文件(如果它不存在)或截断文件(如果存在)。
- 'wx' - 与 'w' 相似,但如果路径存在则失败。
- 'w+' - 打开文件用于读取和写入。创建文件(如果它不存在)或截断文件(如果存在)。
- 'wx+' - 与 'w+' 相似,但如果路径存在则失败。
例子:
新建一个 input.txt 的文件,不写任何内容,然后创建 file.js 文件打开 input.txt 文件进行读写,代码如下:
// 引入 fs 模块
var fs = require("fs");
// 异步打开文件
fs.open("input.txt", "r+", function (err, fd) {
if (err) {
return console.error(err);
}
console.log("文件打开成功!");
});
运行结果为:
同步打开文件的语法格式为:
fs.openSync(path, flags[, mode])
注:参数和用法参照异步 fs.open()。在 Node.js 中我们大多是用异步的方式,因此对于同步的用法不做过多的讲解。
异步关闭文件的语法格式为:
fs.close(fd, callback);
参数说明:
- fd:通过
fs.open()方法返回的文件描述符。 - callback:回调函数,除了可能的异常,完成回调没有其他参数。
新建一个 test.txt 的文件,内容随意输入也可以不输入,再新建一个 closeFile.js 的文件写入以下代码:
// 引入 fs 模块
var fs = require("fs");
// 异步打开文件
fs.open("test.txt", "r+", function (err, fd) {
if (err) {
return console.error(err);
}
console.log("文件打开成功!");
// 异步关闭文件
fs.close(fd, function (err) {
if (err) {
console.log(err);
}
console.log("文件关闭成功");
});
});
运行结果为:
使用 fs.read 和 fs.write 读写文件
使用 fs.read() 和 fs.write() 读写文件需要使用 fs.open() 打开文件和 fs.close() 关闭文件。
使用 fs.read 读取文件
异步读取文件的语法格式为:
fs.read(fd, buffer, offset, length, position, callback);
参数说明:
- fd:通过
fs.open()方法返回的文件描述符。 - buffer:是数据写入的缓冲区。
- offset:是缓冲区中开始写入的偏移量。一般它的值我们写为 0。
- length:是一个整数,指定要读取的字节数。
- position:指定从文件中开始读取的位置。如果 position 为 null,则从当前文件位置读取数据,并更新文件位置。
- callback:回调函数,有三个参数
(err, bytesRead, buffer)。err 为错误信息,bytesRead 表示读取的字节数,buffer 为缓冲区对象。
例子:
新建一个 test.txt 的文件写入:hello syl。再新建一个 read.js 的文件,写上如下的代码:
// 引入 fs 模块
var fs = require("fs");
// 异步打开文件
fs.open("test.txt", "r+", function (err, fd) {
if (err) {
return console.error(err);
}
console.log("文件打开成功!");
console.log("准备读取文件:");
// 创建一个大小为 1024 字节的缓存区
var buf = Buffer.alloc(1024);
// 异步读取文件
fs.read(fd, buf, 0, buf.length, 0, function (err, bytes, buf) {
if (err) {
console.log(err);
}
console.log(bytes + "字节被读取");
// 仅输出读取的字节
if (bytes > 0) {
console.log(buf.slice(0, bytes).toString());
}
// 异步关闭文件
fs.close(fd, function (err) {
if (err) {
console.log(err);
}
console.log("文件关闭成功");
});
});
});
运行结果为:
使用 fs.write 写入文件
异步写入文件的语法格式为:
fs.write(fd, buffer, offset, length, position, callback);
参数说明:
- fd:从指定的文件写入数据。
- buffer:是数据写入的缓冲区。
- offset:指定要写入的 buffer 部分。
- length:是一个整数,指定要写入的字节数。
- position 指定应该写入此数据的文件开头的偏移量。如果
typeof position !== 'number',则从当前位置写入数据。 - callback:回调有三个参数
(err, bytesWritten, buffer),其中bytesWritten指定从buffer写入的字节数。
例子:
在前面例子的基础上,我们新建一个 write.js 的文件,写入以下代码:
// 引入 fs 模块
var fs = require("fs");
// 异步打开文件
fs.open("./test.txt", "a", function (err, fd) {
if (err) {
return console.error(err);
}
console.log("文件打开成功!");
console.log("准备写入文件:");
// 新写入内容为 hello world
var buffer = Buffer.from(new String(" hello world"));
// 异步写入文件
fs.write(fd, buffer, 0, 12, 0, function (err, bytes, buffer) {
if (err) {
throw err;
}
console.log("写入成功");
// 打印出 buffer 中存入的数据
console.log(bytes + "字节被写入");
console.log(buffer.slice(0, bytes).toString());
// 异步关闭文件
fs.close(fd, function (err) {
if (err) {
console.log(err);
}
console.log("文件关闭成功");
});
});
});
运行结果为:
还有一种语法是:
fs.write(fd, string[, position[, encoding]], callback);
参数说明:
- fd:从指定的文件写入数据。
- string:写入的数据,如果不是字符串,则该值将被强制转换为字符串。
- position 指定应该写入此数据的文件开头的偏移量。如果
typeof position !== 'number',则从当前位置写入数据。 - encoding:指定字符串的编码,默认为 'utf8'。
- callback:回调有三个参数
(err, written, string),其中 written 指定字符串中已写入文件的字节数。写入的字节数与字符串的字符数是不同的。
例子:
// 引入 fs 模块
var fs = require("fs");
// 异步打开文件
fs.open("test.txt", "a", function (err, fd) {
if (err) {
return console.error(err);
}
console.log("文件打开成功!");
console.log("准备写入文件:");
// 新写入数据为 ohaiyo
var data = " ohaiyo";
// 异步写入文件
fs.write(fd, data, 0, "utf-8", function (err, bytes, buffer) {
if (err) {
return console.error(err);
}
console.log(bytes + "字节被写入");
console.log(buffer);
// 异步关闭文件
fs.close(fd, function (err) {
if (err) {
console.log(err);
}
console.log("文件关闭成功");
});
});
});
运行结果为:
fs.read 和 fs.write 需要结合 fs.open 得到文件句柄来使用,我们再介绍另外一种读写方式。
readFile 读取文件
异步读取文件的语法格式为:
fs.readFile(path, [options], callback);
参数说明:
- path:文件名或文件描述符。
- options:该参数是一个对象,包含
{encoding, flag}。encoding 默认值为 null,flag 默认值为 'r'。 - callback:回调函数。
例子:
新建一个 test.txt 的文件,文件的内容为:
hello syl hello world
新建一个 readFile.js 的文件,写入如下代码:
// 引入 fs 模块
var fs = require("fs");
// 读取文件
fs.readFile("./test.txt", function (err, data) {
// 读取文件失败/错误
if (err) {
throw err;
}
// 读取文件成功
console.log(data);
});
运行结果为:
运行结果显示的是原始二进制数据在缓冲区中的内容。要显示文件内容可以使用 toString() 或者设置输出编码,readFile.js 可以改成这样:
使用 toString() 方法:
// 引入 fs 模块
var fs = require("fs");
// 读取文件
fs.readFile("./test.txt", function (err, data) {
// 读取文件失败/错误
if (err) {
throw err;
}
// 读取文件成功
console.log(data.toString());
});
运行结果为:
设置输出编码:
// 引入 fs 模块
var fs = require("fs");
// 读取文件
fs.readFile("./test.txt", "utf-8", function (err, data) {
// 读取文件失败/错误
if (err) {
throw err;
}
// 读取文件成功
console.log(data);
});
运行结果为:
fs.readFileSync(filename, [options]) 是 readFile 的同步方法。
writeFile 写入文件
异步写入文件的语法格式为:
fs.writeFile(file, data, [options], callback);
参数说明:
- file:文件名或文件描述符。
- data:要写入文件的数据,可以是 String(字符串)或 Buffer(缓冲)对象。
- options:该参数是一个对象,包含
{encoding, mode, flag}。encoding 默认值为:'utf8',mode 默认值为 0o666,flag 默认为 'w'。 - callback:回调函数。
例子:
新建一个 writeFile.js 的文件,写入如下代码:
// 引入 f s模块
var fs = require("fs");
// 写入文件内容(如果文件不存在会创建一个文件)
// 写入时会先清空文件
fs.writeFile("./test.txt", "我是新写入的内容", function (err) {
if (err) {
throw err;
}
console.log("Saved.");
// 写入成功后读取测试
fs.readFile("./test.txt", "utf-8", function (err, data) {
if (err) {
throw err;
}
console.log(data);
});
});
运行结果为:
我们可以通过设置 flag 的值,来改变默认的写入方式,比如设置为 'a',追加数据到文件中。新建一个 testFlag.js 代码如下:
// 引入 fs 模块
var fs = require("fs");
// 写入文件内容(如果文件不存在会创建一个文件)
// 传递了追加参数 { 'flag': 'a' }
fs.writeFile("./test.txt", "我是新加的内容", { flag: "a" }, function (err) {
if (err) {
throw err;
}
console.log("Saved.");
// 写入成功后读取测试
fs.readFile("./test.txt", "utf-8", function (err, data) {
if (err) {
throw err;
}
console.log(data);
});
});
运行结果为:
注:异步追加内容还有一个专门的方法叫 fs.appendFile。感兴趣的可以自行了解。
Express框架
Express 简介
Express 是一个高度包容,快速而极简的 Node.js Web 框架,提供了一系列强大特性帮助我们创建各种 Web 应用,和丰富的 HTTP 工具。我们可以通过 Express 可以快速地搭建一个完整功能的网站。使用框架的目的就是让我们更加专注于业务,而不是底层细节。
Express 安装
在实验楼的环境中使用以下命令安装 Express:
npm install express
安装好的界面如下所示:
第一个 Express 框架实例
我们的第一个实例是输出 “hello world”,新建 hello.js 文件,代码如下所示:
var express = require("express");
var app = express();
app.get("/", function (req, res) {
res.send("Hello World");
});
app.listen(8080, function () {
console.log("服务器启动了");
});
执行以上代码:
node hello.js
运行效果如下所示:
路由
路由用于确定应用程序如何响应客户端请求,包含一个 URI(路径)和一个特定的 HTTP 请求方法(GET、POST 等)。
每个路由可以具有一个或多个处理程序函数,这些函数在路由匹配时执行。
路由定义采用以下结构:
app.method(path, handler);
注:app 是 Express 的实例,method 是指 HTTP 请求方法(GET、POST 等),path 是指服务器上的路径,handler 是指路由匹配时执行的函数。
下面我们来看一下简单的例子来学习如何定义路由,新建 test.js 文件代码如下所示:
var express = require("express");
var app = express();
// GET 请求
app.get("/", function (req, res) {
console.log("GET 请求");
res.send("Hello,我是GET请求");
});
// POST 请求
app.post("/", function (req, res) {
console.log("POST 请求");
res.send("Hello,我是 POST 请求");
});
// /index 响应 index 页面 GET 请求
app.get("/index", function (req, res) {
console.log("/响应index页面 GET 请求");
res.send("Hello,我是 index 页面 GET 请求");
});
// 对页面 abcd, abxcd, ab123cd, 等响应 GET 请求
app.get("/ab*cd", function (req, res) {
console.log("/ab*cd GET 请求");
res.send("Hello,我是正则匹配");
});
app.listen(8080, function () {
console.log("服务器启动了");
});
运行上述代码:
node test.js
在浏览器中的运行效果为:
对比一下我们前面用原生的 http,少了很多的判断语句,代码量也大大减少了,而这也是我们 Express 的魅力所在。我们后面将继续学习如何使用 Express 框架,其实 Express 框架之与 Node.js 就相当于 jQuery 之与 JavaScript。
静态文件
Express 提供了内置的中间件 express.static 来设置静态文件如:图片,CSS,JavaScript 等。比如:
// 我们只有公开了 public 目录,才可以直接通过 /public/xx 的方式访问 public 目录中的资源
app.use("/public/", express.static("./public/"));
来看个完整的例子,首先新建 public 目录,在 public 目录下新建 test.txt 文,里面随便写一句话:我爱学习,身体棒棒!然后在 project 目录下新建一个 testStatic.js 的文件代码如下:
var express = require("express");
var app = express();
app.use(express.static("public"));
app.get("/", function (req, res) {
res.send("Hello World");
});
app.listen(8080, function () {
console.log("服务器启动了");
});
执行上面的代码:
node testStatic
在浏览器中的运行效果为:
大家可以自行尝试把 app.use(express.static('public')); 这行代码注释掉运行后会显示 Cannot GET /test.txt。
Express 框架处理 GET 请求 和 POST 请求
GET 请求
我们写一个简单的表单提交数据的案例,来演示如何用 Express 框架处理 GET 请求。
首先新建一个 getTest.html 文件,代码如下所示:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title></title>
</head>
<body>
<form action="/get_test" method="GET">
学号: <input type="text" name="stuNum" /><br />
姓名: <input type="text" name="stuNam" />
<input type="submit" value="提交" />
</form>
</body>
</html>
再新建一个 getTest.js 的文件,代码如下所示:
var express = require("express");
var app = express();
app.get("/", function (req, res) {
// 传送指定路径的文件-会自动根据文件 extension 设定 Content-Type
// 也可以用前面的 art-template 模板引擎
res.sendFile(__dirname + "/" + "getTest.html");
});
app.get("/get_test", function (req, res) {
// 输出 JSON 格式
var response = {
studentNumber: req.query.stuNum,
studentName: req.query.stuNam,
};
console.log(response);
// JSON.stringify() 方法是将一个 JavaScript 值(对象或者数组)转换为一个 JSON 字符串
res.end(JSON.stringify(response));
});
app.listen(8080, function () {
console.log("服务器启动了");
});
执行上面的代码:
node getTest.js
服务器中运行效果为:
浏览器中运行效果为:
注:上面的例子中也就简单实现了我们的表单用 get 方式的提交功能,大家注意观察 url 栏上,是把学号和姓名都附加在 url 地址上的,也就是说我们是能够看到的。
POST 请求
同样的我们首先新建一个 postTest.html 的文件,代码如下所示:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title></title>
</head>
<body>
<form action="/post_test" method="POST">
学号: <input type="text" name="stuNum" /><br />
姓名: <input type="text" name="stuNam" />
<input type="submit" value="提交" />
</form>
</body>
</html>
注:上面的代码就是就是把 method 改成了 post,还改了下 action 路由。
再新建一个 postTest.js 的文件,代码如下所示:
var express = require("express");
var app = express();
// 加载 body-parser
var bodyParser = require("body-parser");
// 创建 application/x-www-form-urlencoded 编码解析
var urlencodedParser = bodyParser.urlencoded({ extended: false });
app.get("/", function (req, res) {
// 传送指定路径的文件,会自动根据文件 extension 设定 Content-Type
// 也可以用前面的 art-template 模板引擎
res.sendFile(__dirname + "/" + "postTest.html");
});
app.post("/post_test", urlencodedParser, function (req, res) {
// 输出 JSON 格式
var response = {
studentNumber: req.body.stuNum,
studentName: req.body.stuNam,
};
console.log(response);
// JSON.stringify() 方法是将一个 JavaScript 值(对象或者数组)转换为一个 JSON 字符串
res.end(JSON.stringify(response));
});
app.listen(8080, function () {
console.log("服务器启动了");
});
执行上面的代码:
node postTest.js
在浏览器中的运行效果为:
改写创建一个简单的网站的例子
我们来用 Express 框架改写前面的创建一个简单的网站的例子,其中 index.html 和 register.html 的代码与前面的一致。
首先引入 art-template 模板引擎:
npm install express art-template express-art-template
在 project 目录下,新建一个 views 目录,然后在 views 目录下,创建 index.html 和 register.html 页面,最后新建一个 app.js 的文件。代码如下所示:
var express = require("express");
var app = express();
app.engine("html", require("express-art-template"));
app.get("/", function (req, res) {
res.render("index.html");
});
app.get("/register", function (req, res) {
res.render("register.html");
});
app.listen(8080, function () {
console.log("服务器启动了");
});
服务器中运行效果为:
在浏览器中的运行效果为: