Node.js 原生开发入门完全教程(上)
Posted SegmentFault
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Node.js 原生开发入门完全教程(上)相关的知识,希望对你有一定的参考价值。
一、关于
本篇文章致力于教会你如何用Node.js来开发应用,过程中会传授你所有所需的“高级”javascript知识。Node入门看这篇文章就够了。
二、代码状态
所有代码为春哥亲测,全部正确通过。
三、阅读文章的对象
1.有编程基础 2.想转向Node.js后端的技术爱好者 3.Node.js新手
四、进入正题
1.环境安装
请直接移步Node.js官网,如下图所示,直接点击最新版下载并进行安装。
Node.js安装完毕后,打开终端,在终端分别输入如下命令,检测是否安装成功。
Last login: Tue Jun 27 09:19:38 on console
liyuechun:~ yuechunli$ node -v
v8.1.3
liyuechun:~ yuechunli$ npm -v
5.0.3
liyuechun:~ yuechunli$
如果能够正确显示node和npm的版本,说明Node.js安装成功。
2."Hello World"
第一种输出方式
好了,“废话”不多说了,马上开始我们第一个Node.js应用:“Hello World”。
liyuechun:~ yuechunli$ node
> console.log("Hello World!");
Hello World!
undefined
> console.log("从零到壹全栈部落!");
从零到壹全栈部落!
undefined
> process.exit()
liyuechun:~ yuechunli$
在终端里面直接输入命令 node
,接下来输入一句 console.log("Hello World!");
,回车,即可输出 HelloWorld
。
简单解释一下为什么每一次打印后面都出现了一个 undefined
,原因是因为你输入js代码并按下回车后,node会输出执行完该代码后的返回值,如果没有返回值,就会显示undefined,这个跟Chrome的调试工具相似。
如上代码所示,当输入 process.exit()
并回车时,即可退出 node模式
。
第二种输出方式
Last login: Thu Jun 29 18:17:27 on ttys000
liyuechun:~ yuechunli$ ls
Applications Downloads Pictures
Creative Cloud Files Library Public
Desktop Movies
Documents Music
liyuechun:~ yuechunli$ cd Desktop/
liyuechun:Desktop yuechunli$ mkdir nodejs入门
liyuechun:Desktop yuechunli$ pwd
/Users/liyuechun/Desktop
liyuechun:Desktop yuechunli$ cd nodejs入门/
liyuechun:nodejs入门 yuechunli$ pwd
/Users/liyuechun/Desktop/nodejs入门
liyuechun:nodejs入门 yuechunli$ vi helloworld.js
liyuechun:nodejs入门 yuechunli$ cat helloworld.js
console.log("Hello World!");
liyuechun:nodejs入门 yuechunli$ node helloworld.js
Hello World!
liyuechun:nodejs入门 yuechunli$
命令解释:ls:查看当前路径下面的文件和文件夹。pwd:查看当前所在路径。cd Desktop:切换到桌面。mkdir nodejs入门:在当前路径下面创建 nodejs入门
文件夹。cd nodejs入门:进入 nodejs入门
文件夹。vi helloworld.js:创建一个 helloworld.js
文件,并在文件里面输入 console.log("Hello World!")
,保存并退出。cat helloworld.js:查看 helloworld.js
文件内容。node helloworld.js:在当前路径下面执行 helloworld.js
文件。
PS:如果对命令行不熟悉的童鞋,可以用其他编辑器创建一个 helloworld.js
文件,在里面输入 console.log("Hello World!")
,将文件保存到桌面,然后打开终端,直接将 helloworld.js
文件拖拽到终端,直接在终端中执行 node helloworld.js
即可在终端输出 HelloWorld!
。
好吧,我承认这个应用是有点无趣,那么下面我们就来点“干货”。
下面我们将通过VSCode来进行Node.js的编码。
五、一个完整的基于Node.js的web应用
1.用例
我们来把目标设定得简单点,不过也要够实际才行:
用户可以通过浏览器使用我们的应用。
当用户请求http://domain/start时,可以看到一个欢迎页面,页面上有一个文件上传的表单。
用户可以选择一个图片并提交表单,随后文件将被上传到http://domain/upload,该页面完成上传后会把图片显示在页面上。
差不多了,你现在也可以去Google一下,找点东西乱搞一下来完成功能。但是我们现在先不做这个。
更进一步地说,在完成这一目标的过程中,我们不仅仅需要基础的代码而不管代码是否优雅。我们还要对此进行抽象,来寻找一种适合构建更为复杂的Node.js应用的方式。
2.应用不同模块分析
我们来分解一下这个应用,为了实现上文的用例,我们需要实现哪些部分呢?
我们需要提供Web页面,因此需要一个HTTP服务器
对于不同的请求,根据请求的URL,我们的服务器需要给予不同的响应,因此我们需要一个路由,用于把请求对应到请求处理程序(request handler)
当请求被服务器接收并通过路由传递之后,需要可以对其进行处理,因此我们需要最终的请求处理程序
路由还应该能处理POST数据,并且把数据封装成更友好的格式传递给请求处理入程序,因此需要请求数据处理功能
我们不仅仅要处理URL对应的请求,还要把内容显示出来,这意味着我们需要一些视图逻辑供请求处理程序使用,以便将内容发送给用户的浏览器
最后,用户需要上传图片,所以我们需要上传处理功能来处理这方面的细节
现在我们就来开始实现之路,先从第一个部分--HTTP服务器着手。
六、构建应用的模块
1.一个基础的HTTP服务器
用VSCode创建一个 server.js
的文件,将文件保存到桌面的 nodejs入门
文件夹里面。
在 server.js
文件里面写入以下内容:
let http = require("http");
http.createServer(function(request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Hello World");
response.end();
}).listen(8888);
上面的代码就是一个完整的Node.js服务器,如下图所示,点击VSCode左下脚按钮,打开VSCode终端,在终端中输入 node server.js
来进行验证。
如上图所示,一个基础的HTTP服务器搞定。
2.HTTP服务器原理解析
上面的案例中,第一行请求(require)Node.js自带的 http 模块,并且把它赋值给 http 变量。
接下来我们调用http模块提供的函数: createServer 。这个函数会返回一个对象,这个对象有一个叫做 listen 的方法,这个方法有一个数值参数,指定这个HTTP服务器监听的端口号。
咱们暂时先不管 http.createServer 的括号里的那个函数定义。
我们本来可以用这样的代码来启动服务器并侦听8888端口:
var http = require("http");
var server = http.createServer();
server.listen(8888);
这段代码只会启动一个侦听8888端口的服务器,它不做任何别的事情,甚至连请求都不会应答。
3.进行函数传递
举例来说,你可以这样做:
Last login: Thu Jun 29 20:03:25 on ttys001
liyuechun:~ yuechunli$ node
> function say(word) {
... console.log(word);
... }
undefined
>
> function execute(someFunction, value) {
... someFunction(value);
... }
undefined
>
> execute(say, "Hello");
Hello
undefined
>
请仔细阅读这段代码!在这里,我们把 say 函数作为execute函数的第一个变量进行了传递。这里传递的不是 say 的返回值,而是 say 本身!
这样一来, say 就变成了execute 中的本地变量 someFunction ,execute可以通过调用 someFunction() (带括号的形式)来使用 say 函数。
当然,因为 say 有一个变量, execute 在调用 someFunction 时可以传递这样一个变量。
我们可以,就像刚才那样,用它的名字把一个函数作为变量传递。但是我们不一定要绕这个“先定义,再传递”的圈子,我们可以直接在另一个函数的括号中定义和传递这个函数:
Last login: Thu Jun 29 20:04:35 on ttys001
liyuechun:~ yuechunli$ node
> function execute(someFunction, value) {
... someFunction(value);
... }
undefined
>
> execute(function(word){ console.log(word) }, "Hello");
Hello
undefined
>
我们在 execute 接受第一个参数的地方直接定义了我们准备传递给 execute 的函数。
用这种方式,我们甚至不用给这个函数起名字,这也是为什么它被叫做 匿名函数 。
这是我们和我所认为的“进阶”JavaScript的第一次亲密接触,不过我们还是得循序渐进。现在,我们先接受这一点:在JavaScript中,一个函数可以作为另一个函数接收一个参数。我们可以先定义一个函数,然后传递,也可以在传递参数的地方直接定义函数。
4.函数传递是如何让HTTP服务器工作的
带着这些知识,我们再来看看我们简约而不简单的HTTP服务器:
var http = require("http");
http.createServer(function(request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Hello World");
response.end();
}).listen(8888);
console.log("请在浏览器中打开 http://127.0.0.1:8888...");
现在它看上去应该清晰了很多:我们向 createServer 函数传递了一个匿名函数。
用这样的代码也可以达到同样的目的:
/**
* 从零到壹全栈部落,添加小精灵微信(ershiyidianjian)
*/
//请求(require)Node.js自带的 http 模块,并且把它赋值给 http 变量。
let http = require("http");
//箭头函数
let onRequest = (request, response) => {
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Hello World");
response.end();
}
//把函数当作参数传递
http.createServer(onRequest).listen(8888);
console.log("请在浏览器中打开 http://127.0.0.1:8888...");
也许现在我们该问这个问题了:我们为什么要用这种方式呢?
5.基于事件驱动的回调
事件驱动是Node.js原生的工作方式,这也是它为什么这么快的原因。
当我们使用 http.createServer
方法的时候,我们当然不只是想要一个侦听某个端口的服务器,我们还想要它在服务器收到一个HTTP请求的时候做点什么。
我们创建了服务器,并且向创建它的方法传递了一个函数。无论何时我们的服务器收到一个请求,这个函数就会被调用。
这个就是传说中的回调 。我们给某个方法传递了一个函数,这个方法在有相应事件发生时调用这个函数来进行回调 。
我们试试下面的代码:
/**
* 从零到壹全栈部落,添加小精灵微信(ershiyidianjian)
*/
//请求(require)Node.js自带的 http 模块,并且把它赋值给 http 变量。
let http = require("http");
//箭头函数
let onRequest = (request, response) => {
console.log("Request received.");
response.writeHead(200, {"Content-Type": "text/plain;charset=utf-8"});
response.write("添加小精灵微信(ershiyidianjian),加入全栈部落");
response.end();
}
//把函数当作参数传递
http.createServer(onRequest).listen(8888);
console.log("Server has started.");
console.log("请在浏览器中打开 http://127.0.0.1:8888...");
在上图中,当我们执行 node server.js
命令时,Server has started.正常往下执行。
我们看看当我们在浏览器里面打开 http://127.0.0.1:8888
时会发生什么。
大家会发现在浏览器中打开 http://127.0.0.1:8888
时,在终端会输出 Requestreceived.
,浏览器会输出 添加小精灵微信(ershiyidianjian),加入全栈部落
这一句话。
请注意,当我们在服务器访问网页时,我们的服务器可能会输出两次“Request received.”。那是因为大部分浏览器都会在你访问 http://localhost:8888/ 时尝试读取 http://localhost:8888/favicon.ico )
6.服务器是如何处理请求的
好的,接下来我们简单分析一下我们服务器代码中剩下的部分,也就是我们的回调函数 onRequest()
的主体部分。
当回调启动,我们的 onRequest()
函数被触发的时候,有两个参数被传入: request
和 response
。
它们是对象,你可以使用它们的方法来处理HTTP请求的细节,并且响应请求(比如向发出请求的浏览器发回一些东西)。
所以我们的代码就是:当收到请求时,使用 response.writeHead()
函数发送一个HTTP状态200和HTTP头的内容类型(content-type),使用 response.write()
函数在HTTP相应主体中发送文本 添加小精灵微信(ershiyidianjian),加入全栈部落
。
最后,我们调用 response.end()
完成响应。
目前来说,我们对请求的细节并不在意,所以我们没有使用 request
对象。
7.服务端模块化
何为
模块
?
let http = require("http");
...
http.createServer(...);
在上面的代码中,Node.js中自带了一个叫做“http”的模块,我们在我们的代码中请求它并把返回值赋给一个本地变量。
这把我们的本地变量变成了一个拥有所有 http 模块所提供的公共方法的对象。
给这种本地变量起一个和模块名称一样的名字是一种惯例,但是你也可以按照自己的喜好来:
var foo = require("http");
...
foo.createServer(...);
如何自定义模块
将 server.js
文件的内容改成下面的内容。
/**
* 从零到壹全栈部落,添加小精灵微信(ershiyidianjian)
*/
//请求(require)Node.js自带的 http 模块,并且把它赋值给 http 变量。
let http = require("http");
//用一个函数将之前的内容包裹起来
let start = () => {
//箭头函数
let onRequest = (request, response) => {
console.log("Request received.");
response.writeHead(200, {"Content-Type": "text/plain;charset=utf-8"});
response.write("添加小精灵微信(ershiyidianjian),加入全栈部落");
response.end();
}
//把函数当作参数传递
http.createServer(onRequest).listen(8888);
console.log("Server has started.");
console.log("请在浏览器中打开 http://127.0.0.1:8888...");
}
//导出`server`对象,对象中包含一个start函数
//对象格式为
/**
* {
* start
* }
*/
//这个对象导入到其他文件中即可使用,可以用任意的名字来接收这个对象
exports.start = start;
在 server.js
当前的文件路径下新建一个 index.js
文件。内容如下:
/**
* 从零到壹全栈部落,添加小精灵微信(ershiyidianjian)
*/
//从`server`模块中导入server对象
let server = require('./server');
//启动服务器
server.start();
如下图所示运行 index.js
文件。
一切运行正常,上面的案例中, server.js
就是自定义的模块。
8.如何来进行请求的“路由”
我们要为路由提供请求的URL和其他需要的GET及POST参数,随后路由需要根据这些数据来执行相应的代码(这里“代码”对应整个应用的第三部分:一系列在接收到请求时真正工作的处理程序)。
因此,我们需要查看HTTP请求,从中提取出请求的URL以及GET/POST参数。这一功能应当属于路由还是服务器(甚至作为一个模块自身的功能)确实值得探讨,但这里暂定其为我们的HTTP服务器的功能。
我们需要的所有数据都会包含在request对象中,该对象作为onRequest()回调函数的第一个参数传递。但是为了解析这些数据,我们需要额外的Node.JS模块,它们分别是url和querystring模块。
url.parse(string).query
|
url.parse(string).pathname |
| |
| |
------ -------------------
http://localhost:8888/start?foo=bar&hello=world
--- -----
| |
| |
querystring(string)["foo"] |
|
querystring(string)["hello"]
当然我们也可以用querystring模块来解析POST请求体中的参数,稍后会有演示。
现在我们来给onRequest()函数加上一些逻辑,用来找出浏览器请求的URL路径:
接下来我在终端执行 node index.js
命令,如下所示:
bogon:如何来进行请求的“路由” yuechunli$ node index.js
Server has started.
请在浏览器中打开 http://127.0.0.1:8888...
我先在 Safari
浏览器中打开 http://127.0.0.1:8888
,浏览器展示效果如下:
控制台效果如下:
bogon:如何来进行请求的“路由” yuechunli$ node index.js
Server has started.
请在浏览器中打开 http://127.0.0.1:8888...
Request for / received.
接着我在 Google
浏览器里面打开 http://127.0.0.1:8888... ,浏览器效果图如下:
控制台效果如下:
为什么在 Safari
浏览器中进行请求时,只打印了一个 Requestfor/received.
,而在 Google
浏览器中访问时,会多打印一个 Requestfor/favicon.ico received.
,如上图所示,原因是因为在 Google
浏览器中,浏览器的原因会去尝试请求 favicon.ico
小图标。
为了演示效果,还有不受 Google
浏览器的 favicon.ico
请求的干扰,我接着在 Safari
里面请求 http://127.0.0.1:8888/start
和 http://127.0.0.1:8888/upload
,我们看看控制台展示的内容是什么。
bogon:如何来进行请求的“路由” yuechunli$ node index.js
Server has started.
请在浏览器中打开 http://127.0.0.1:8888...
Request for /start received.
Request for /upload received.
好了,我们的应用现在可以通过请求的URL路径来区别不同请求了--这使我们得以使用路由(还未完成)来将请求以URL路径为基准映射到处理程序上。
在我们所要构建的应用中,这意味着来自 /start
和 /upload
的请求可以使用不同的代码来处理。稍后我们将看到这些内容是如何整合到一起的。
现在我们可以来编写路由了,建立一个名为 router.js
的文件,添加以下内容:
/**
* 从零到壹全栈部落,添加小精灵微信(ershiyidianjian)
*/
function route(pathname) {
console.log("About to route a request for " + pathname);
}
exports.route = route;
如你所见,这段代码什么也没干,不过对于现在来说这是应该的。在添加更多的逻辑以前,我们先来看看如何把路由和服务器整合起来。
首先,我们来扩展一下服务器的 start()
函数,以便将路由函数作为参数传递过去:
/**
* 从零到壹全栈部落,添加小精灵微信(ershiyidianjian)
*/
//请求(require)Node.js自带的 http 模块,并且把它赋值给 http 变量。
let http = require("http");
let url = require("url");
//用一个函数将之前的内容包裹起来
let start = (route) => {
//箭头函数
let onRequest = (request, response) => {
let pathname = url.parse(request.url).pathname;
console.log("Request for " + pathname + " received.");
route(pathname);
response.writeHead(200, {"Content-Type": "text/plain;charset=utf-8"});
response.write("添加小精灵微信(ershiyidianjian),加入全栈部落");
response.end();
}
//把函数当作参数传递
http.createServer(onRequest).listen(8888);
console.log("Server has started.");
console.log("请在浏览器中打开 http://127.0.0.1:8888...");
}
exports.start = start;
同时,我们会相应扩展 index.js
,使得路由函数可以被注入到服务器中:
/**
* 从零到壹全栈部落,添加小精灵微信(ershiyidianjian)
*/
//从`server`模块中导入server对象
let server = require('./server');
let router = require("./router");
//启动服务器
server.start(router.route);
在这里,我们传递的函数依旧什么也没做。
如果现在启动应用(node index.js,始终记得这个命令行),随后请求一个URL,你将会看到应用输出相应的信息,这表明我们的HTTP服务器已经在使用路由模块了,并会将请求的路径传递给路由:
bogon:如何来进行请求的“路由” v2.0 yuechunli$ node index.js
Server has started.
请在浏览器中打开 http://127.0.0.1:8888...
Request for / received.
About to route a request for /
以上是关于Node.js 原生开发入门完全教程(上)的主要内容,如果未能解决你的问题,请参考以下文章