什么是Node.js
官网介绍:
Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.
Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.
Node.js' package ecosystem, npm, is the largest ecosystem of open source libraries in the world.
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。
Node.js 的包管理器 npm,是全球最大的开源库生态系统。
Node.js不是一个语言,也不是一个库,更不是一个框架。只是一个运行环境,也就是平台。在Node.js这个平台上,我们可以使用JavaScript 来编写程序,实现相应的功能。
使用Node.js 可以轻松地进行服务器端应用开发,PHP、Python、Ruby能做的事情,Node.js几乎都能做,而且可以做得更好。那么既然已经有了PHP等后台语言,为什么还需要Node.js?这里我们要知道一个理念——任何一个有点小规模的产品,都不会只使用一门技术或一种语言,结合多种语言来实现,然后在不同的场景、需求上,使用相应的语言和技术去实现。目的就是为了提高产品的性能。每一张语言都有自己的优缺点,我们要利用好它们的优点。
我们在考虑是否应该使用这门语言的时候,就是要搞清楚它的优缺点。时效性要求比较高的应用,Node.js是最佳的。
Node.js 初体验
官网下载安装好Node.js,接下来我们来初步感受一下。
Hello world
打开cmd窗口,输入node
命令,就可以进入Node.js的运行环境,在这里任何的JavaScript
代码都可以编写并执行,除了BOM 和 DOM 的内容。如果输入了 BOM 和 DOM 的一些内容,在 Node 平台就会出错。但是,node 平台也提供了一个全局对象——console
。
顺便说一句,像这种 cmd 的窗口,有一个专有名词 REPL
- R: read,读取,等待用户的输入
- E: eval,执行代码,输入代码完成之后,按回车键,可以执行代码
- P: print,打印,输出结果
- L: loop,循环,重复这个过程
执行js文件
上述的命令窗口模式操作node,平时很少会那样用。我们还是用编辑器编写js代码,然后会通过cmd窗口来执行这个文件。
过程也很简单,不需要进入node的REPL
环境,打开cmd
窗口,输入 “node + 文件名(完整路径)” 执行即可。
搭建服务器
下面我们用Node.js
来搭建HTTP服务器,
第一步:编写server.js文件如下:
<!--创建一个HTTP服务器-->
// 载入http核心模块
const http = require("http");
// 创建一个server对象
const server = http.createServer((req,res)=>{
// 通过res对象,输出一些内容
res.writeHead(200,{"Content-Type":"text/html;charset=utf-8"});
res.write("<h1>http服务器</h1>");
res.write("<p>使用Node.js创建一个http服务器</p>");
res.end();
});
// 开启server的监听
server.listen(3000,()=>{
console.log("http server is listening in port 3000...");
});
第二步:打开cmd窗口,执行server.js文件
第三步:在浏览器中,输入 localhost:3000
访问,即可看到结果页面。
模块机制
在Node.js中,所谓的模块,其实就是一个文件。一般而言,就是js文件/json文件。一个文件就是一个模块,模块是Node.js应用程序的基本组成部分。
模块分类
简单划分,可以将Node.js中的模块分成两大类:
- 系统模块(核心模块)
- 用户模块
其中,系统模块是Node.js自带的模块,比如http、fs、net、url、path等,可以直接使用。核心模块,Node.js中是内置好的。
用户模块不是Node.js本身的,又可以分为两种:
- 第三方模块,一些比较通用的,但是Node.js自身没有提供的,这一类数量很庞大
- 自定义模块,通常是在当前项目中,需要根据需求自己编写的js代码
加载模块
如何在Node.js中加载模块呢?
根据模块类型的不同,加载的方式略有不同。相同的是,都会使用 require
函数。格式:require(模块路径);
模块路径的写法,根据模块类型的不同,写法也不同:
- 核心模块和第三方模块的写法一样,只需要写上模块名即可。
- 自定义模块,需要使用相对路径来引入,必须使用
./
或者../
开头。
(1)核心模块的载入
核心模块是Node.js自带的,本身就具备的,直接载入就可以使用。
(2)第三方模块的载入
首先,需要保证有一个第三方模块,需要先安装第三方模块,使用npm 命令安装即可。
npm install 模块名。
本地安装的时候,需要先进入对应的目录,使用命令来安装。
虽然,加载模块的写法和核心模块一样,但是原理不太一样。如果是第三方模块,它最加载的时候,一定会在当前目录下,寻找 node_modules文件夹,在里面找对应的这模块,如果找到,就直接使用。如果没有找到,则会到父级目录中找node_modules文件夹,找是否有该模块,重复这个过程,直到根目录。如果到根目录,也没有找到,就会报错。
(3)加载自定义模块
先创建一个模块,其实就是一个js文件:mymodule.js
然后,使用require引入,const myModule = require('./mymodule.js');
如果没有使用相对路径,会报错。
还有几个细节需要注意一下:
- 模块就是文件,一般就是js文件,js文件是有后缀的,后缀是可以省略的,核心模块和第三方模块必须省略,自定义模块比较随意,两种都行。
- 如果没有写后缀,
require
方法在加载的时候,当文件不带后缀,Node.js会依照 目录 ->.js-> .json-> .node 的顺序进行查找,如果所有模块路径都找不到该文件,则抛出异常。
自定义模块的实现
默认情况下,任何一个模块,被载入时,得到的是一个空对象,就是 module.exports
;我们可以直接在js文件中使用module.exports
。
在Node.js中,还有一个对象exports
,它实际上是module.exports
的一个引用,相当于exports = module.exports
。
如果使用的是module.exports
,那么直接赋值即可,如果使用的是exports
,不能直接赋值,原因很简单嘛,又涉及到了对象数值的传递了。所以小结一下:
任何一个模块(js文件)被 require
时,返回的是 module.exports
对象,默认为空。Node.js为了方便,提供了一个exports
对象,指向module.exports
,相当于执行了exports=module.exports
。如果需要提供对外接口,需要给module.exports
赋值为一个新的对象,或者使用exports.属性名=值
的形式。
为了保险起见,我们可以写成:exports = module.exports = {}
。
Node.js进行Web开发的核心
Server 对象
作用:用于创建服务器对象,提供HTTP服务,在Node.js中,Server对象充当了HTTP服务器的角色。它提供了一个监听端口的底层套接字和接收请求,然后发送响应给客户端链接的处理程序。
创建对象:http.createServer();
核心方法:listen;启动监听,启动 http 服务,提供给用户来访问,有一个关键参数port,指定监听的端口
重要事件:request:接收请求时触发,传递两个参数,IncommingMessage 对象和 ServerResponse 对象;
listening:调用listen 时触发,同理也可以作为listen的回调函数来进行绑定。
实际上,用户输入url打开网页就是一个请求事件,如果我们需要响应用户的请求,那么必须先注册该事件。需要对server绑定一个request事件,在Node.js中,绑定事件使用 on 来完成。为了简化代码的书写,Node.js将注册request事件,使用回调函数的方式来实现:
const server = http.createServer();
server.on('request',(req,res)=>{...});
简化成
const server = http.createServer((req,res)=>{...});
ServerResponse 对象
在刚才的 createServer 的回调函数中,使用了 res 对象,这个对象实际上就是 ServerResponse 对象,服务器端的响应对象。
作用:用来写http响应,包括响应状态行、响应头信息和响应正文。
创建:是自动创建的,作为回调函数的参数,自动创建好的一个对象。在createServer方法中的回调函数中,作为第二个参数来出现的。
重要属性和方法:
- writeHead用于写 响应状态行和响应头。只能调用一次
- write负责写 响应正文。可以调用多次
- end,响应完毕,只能调用一次,必须要调用。
- setHeader,也可以通过这个方法设置头信息
- getHeader,用来获取头信息的。
IncommingMessage 对象
作用:要理解其作用,需要看看HTTP请求和响应的过程:浏览器到服务器端的请求过程,作为服务器端需要获取来自客户端的信息;服务器端到浏览器端的响应过程,作为浏览器端也需要获取来自服务器的一些信息。需要有一个对象,来表示这个信息,这个对象就是 incomingMessage对象。简单来说,它的作用就是获取对方(浏览器端或者服务器端)的一些信息。
如何创建:不需要手动创建,也是作为回调函数的参数出现的。站在服务器端的角度来看,这个incomingMessage 就是 createSever回调函数中的req参数。
重要的属性和方法:
- httpVersion:请求/响应的HTTP版本
- headers:请求/响应头信息
- rawHeaders:原始请求/响应头信息
- method:请求方式,仅对Server获得到的请求(request)有效
- url:请求的url字符串,仅对Server获得到的请求(request)有效
- statusCode:响应状态码,仅对从ClientRequest获得响应(response)有效
重要事件:
- data:数据传递时触发
- end:没有更多数据提供时触发
由Readable Stream 提供了一下接口,然后 IncomingMessage实现了这些接口
ClientRequest 对象
什么是ClientRequest对象呢?就是客户端请求对象,表示一个已经产生且正在进项中的HTTP请求。
使用Node创建Web客户端,需要引入http模块。
如何创建:有两种方法,都是http对象的方法
- request
-
get
const cr = http.request(); const cr = http.get();
核心方法:
- write:把一个正文写入请求;
- end:完成请求,也可以写入数据,end方法,完成请求的动作,针对request方法创建的对象而言。
对于get请求,请求正文是空的,不用写,此时,不需要write方法;对于post请求,请求正文是需要写的,此时,需要写write方法。
重要事件:response,需要注册一个响应事件,当发出请求后,需要监听来自服务器端的响应。
在响应时的监听器中,有一个重要的对象 IncommingMessage 对象,这个对象有两个重要的事件。
- data,数据传递时触发,
- end,没有更多数据提供时触发
使用这两个事件就可以获取来自服务器端的响应内容。代码如下:
const http = require('http');
<!--创建一个ClientRequest对象-->
const cr = http.request({host:'www.baidu.com'});
//注册response事件
cr.on('response',(res)=>{
let str = '';
//注册data事件
res.on('data',(chunk)=>{
str += chunk;
});
res.on('end',()=>{
console.log(str);
});
});
//发出请求
cr.end();
针对上述过程,我们还可以使用get方法来简化,其中,get方法,会自动发出请求,不需要调用end方法。
const http = require('http');
http.get('http://www.baidu.com',(res)=>{
let str = "";
res.on('data',(chunk)=>{
str += chunk;
});
res.on('end',()=>{
console.log(str);
});
});
利用这个可以搞一下爬虫,爬一下网络资源。
简单应用
URL路由
URL:Uniform Resource Locator。统一资源定位器。还有一个就是URI(统一资源标识符),其中的I是指identifier。URL是基于URI的。在互联网中,任何一个资源(html、css、js、img、动画、视频、音频、word)需要保证它的唯一性。可以给每一个资源指定一个唯一的URL。简单来说,URL就是我们常说的网址。
在web当中,用户输入不同的URL,服务器就接收到这个信息,需要处理这个信息,根据不同的请求,返回相应的内容。这个过程就是URL路由。我们可以通过req对象(IncomingMessage对象)中的url属性,来获取相关的信息,并进行处理。
Node.js提供了一个url模块,可以解析url,得到更为详细的信息。url.parse(url)
得到一个对象,里边包含整个url中的各种信息。
代码片段:
浏览器输入 localhost:3000/user?username=admin&pwd=123
路由处理 let realUrl = "http://"+ req.headers.host + req.url;
let urlObj = url.parse(realUrl);//得到查询字符串query : 'username=admin&pwd=123'
解析查询字符串
打开一个网页,很多情况下路径名不发生变化,改变查询字符串,就会显示不同内容;附加的一些信息,需要根据这些信息,显示不同的内容给用户。
格式:?键1=值1&键2=值2&键n=值n
使用url.parse方法解析url之后,可以拿到查询字符串
Node.js提供了querystring 模块,其中有 parse 方法,可以将字符串解析成对象;这样就可以直接通过属性来获取相对应的值。
代码片段:
浏览器输入 localhost:3000/user?username=admin&pwd=123
const querystring = require('querystring');
let realUrl = "http://"+ req.headers.host + req.url;
let urlObj = url.parse(realUrl);//得到查询字符串query : 'username=admin&pwd=123'
let qObj = querystring.parse(urlObj.query);//得到{username: 'admin', pwd: '123'}
如何载入静态页面
在Node.js中,提供一个 web 服务器,有三种方式:
- 手动实现:结合 fs 文件操作;
- 第三方库实现:http-server
- 框架实现:express
看一下手动实现:
第一步:准备一个静态的HTML页面index.html;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<h1>载入静态页面</h1>
<p>可以结合fs操作,实现载入静态页面</p>
<img src="xx.jpg" alt="">
</body>
</html>
第二步:编码,创建一个 web 服务器,当用户输入url,需要显示静态页面;在这个过程中,由于图片和CSS都是独立的文件,对于它们,其实浏览器分别发送了单独的请求,而不是像之前那样直接去读取。所以,我们要在服务器端对这些静态文件进行处理,并通过res返回给浏览器端。
const http = require('http');
const fs = require('fs');
const url = require('url');
http.createServer( (req,res) => {
let realUrl = "http://" + req.headers.host + req.url;
let urlObj = url.parse(realUrl);
switch(urlObj.pathname) {
case "/" : //首页
case "/index":
//使用fs读取html文件,然后输出
fs.readFile('index.html','utf8',(err,data) => {
if (err) throw err;
//读取成功,写入响应中
res.writeHead(200,{"content-type":"text/html;charset=utf-8"});
res.end(data);
});
break;
default:
//res.writeHead(200,{"content-type":"text/html;charset=utf-8"});
//res.end('页面走丢了');
//将所有其他的资源,在这里做处理
if (fs.existsSync(__dirname + urlObj.pathname)) {
fs.readFile(__dirname + urlObj.pathname,(err,data) => {
if (err) throw err;
res.statusCode = 200;
res.end(data);
});
}
}
}).listen(3000,() => {
console.log('listening in port 3000...');
});
这里使用了一个魔术常量 __dirname
,表示当前代码执行的路径。
POST请求及响应
典型的动态交互,表单处理。通常就是post请求。看一下Node.js是如何实现post请求和回应的。
第一步,准备一个表单,注意:要写获取到表单数据,必须要为input添加name属性,否则提交到服务器中的数据无法确定哪个是哪个,如下:
<form action="/signin" method="post" >
<ul>
<li>
<label for="">用户名:</label>
<input type="text" name="username">
</li>
<li>
<label for="">密码:</label>
<input type="password" name="password">
</li>
<li>
<label for=""></label>
<input type="submit" value="登录">
</li>
</ul>
</form>
第二步,创建一个web server,载入这个表单;此时,会需要使用 IncommingMessage 对象的两个事件 data 和 end,
switch (urlObj.pathname) {
case '/login' : //登录页面
fs.readFile('login.html','utf8',(err,data)=>{
if (err) throw err;
res.writeHead(200, {"content-type" : "text/html"});
res.end(data);
});
break;
case '/signin':
//console.log('提交到这儿了');
//需要接受用户填写的信息,利用IncomingMesaage对象的data和end事件
let str = "";
req.on('data', (chunk)=> {
str += chunk;
} );
req.on('end',()=>{
//表示提交完毕,str就是提交的数据了
console.log(str); //username=admin&password=1234
//利用querystring对象的parse方法进行解析
let userObj = querystring.parse(str);
res.writeHead(200,{"content-type":"text/html;charset=utf-8"});
res.write(`<p>你输入的用户名是 ${userObj.username}</p>`);
res.write(`<p>你输入的密码是 ${userObj.password}</p>`);
res.end();
});
break;
default:
break;
}
}).listen(3005, () => {
console.log('listening in port 3005');
});
需要注册两个事件,data和end。数据提交的时候,并不是一次提交完毕,而是一块一块的提交。每提交一块,就触发一次,需要不停的累加。直到end触发了,说明提交完成了。
小结
以上是Node.js的基础介绍,接下来的学习更多的是不同模块的应用,和框架的学习了(express)。