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安装完毕后,打开终端,在终端分别输入如下命令,检测是否安装成功。

 
   
   
 
  1. Last login: Tue Jun 27 09:19:38 on console

  2. liyuechun:~ yuechunli$ node -v

  3. v8.1.3

  4. liyuechun:~ yuechunli$ npm -v

  5. 5.0.3

  6. liyuechun:~ yuechunli$

如果能够正确显示node和npm的版本,说明Node.js安装成功。

2."Hello World"

  • 第一种输出方式

好了,“废话”不多说了,马上开始我们第一个Node.js应用:“Hello World”。

 
   
   
 
  1. liyuechun:~ yuechunli$ node

  2. > console.log("Hello World!");

  3. Hello World!

  4. undefined

  5. > console.log("从零到壹全栈部落!");

  6. 从零到壹全栈部落!

  7. undefined

  8. > process.exit()

  9. liyuechun:~ yuechunli$

在终端里面直接输入命令 node,接下来输入一句 console.log("Hello World!"); ,回车,即可输出 HelloWorld

简单解释一下为什么每一次打印后面都出现了一个 undefined,原因是因为你输入js代码并按下回车后,node会输出执行完该代码后的返回值,如果没有返回值,就会显示undefined,这个跟Chrome的调试工具相似。

如上代码所示,当输入 process.exit()并回车时,即可退出 node模式

  • 第二种输出方式

 
   
   
 
  1. Last login: Thu Jun 29 18:17:27 on ttys000

  2. liyuechun:~ yuechunli$ ls

  3. Applications        Downloads       Pictures

  4. Creative Cloud Files    Library         Public

  5. Desktop            Movies

  6. Documents        Music

  7. liyuechun:~ yuechunli$ cd Desktop/

  8. liyuechun:Desktop yuechunli$ mkdir nodejs入门

  9. liyuechun:Desktop yuechunli$ pwd

  10. /Users/liyuechun/Desktop

  11. liyuechun:Desktop yuechunli$ cd nodejs入门/

  12. liyuechun:nodejs入门 yuechunli$ pwd

  13. /Users/liyuechun/Desktop/nodejs入门

  14. liyuechun:nodejs入门 yuechunli$ vi helloworld.js

  15. liyuechun:nodejs入门 yuechunli$ cat helloworld.js

  16. console.log("Hello World!");

  17. liyuechun:nodejs入门 yuechunli$ node helloworld.js

  18. Hello World!

  19. 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文件里面写入以下内容:

 
   
   
 
  1. let http = require("http");

  2. http.createServer(function(request, response) {

  3.  response.writeHead(200, {"Content-Type": "text/plain"});

  4.  response.write("Hello World");

  5.  response.end();

  6. }).listen(8888);

上面的代码就是一个完整的Node.js服务器,如下图所示,点击VSCode左下脚按钮,打开VSCode终端,在终端中输入 node server.js来进行验证。

Node.js 原生开发入门完全教程(上)

如上图所示,一个基础的HTTP服务器搞定。

2.HTTP服务器原理解析

上面的案例中,第一行请求(require)Node.js自带的 http 模块,并且把它赋值给 http 变量。

接下来我们调用http模块提供的函数: createServer 。这个函数会返回一个对象,这个对象有一个叫做 listen 的方法,这个方法有一个数值参数,指定这个HTTP服务器监听的端口号。

咱们暂时先不管 http.createServer 的括号里的那个函数定义。

我们本来可以用这样的代码来启动服务器并侦听8888端口:

 
   
   
 
  1. var http = require("http");

  2. var server = http.createServer();

  3. server.listen(8888);

这段代码只会启动一个侦听8888端口的服务器,它不做任何别的事情,甚至连请求都不会应答。

3.进行函数传递

举例来说,你可以这样做:

 
   
   
 
  1. Last login: Thu Jun 29 20:03:25 on ttys001

  2. liyuechun:~ yuechunli$ node

  3. > function say(word) {

  4. ...   console.log(word);

  5. ... }

  6. undefined

  7. >

  8. > function execute(someFunction, value) {

  9. ...   someFunction(value);

  10. ... }

  11. undefined

  12. >

  13. > execute(say, "Hello");

  14. Hello

  15. undefined

  16. >

请仔细阅读这段代码!在这里,我们把 say 函数作为execute函数的第一个变量进行了传递。这里传递的不是 say 的返回值,而是 say 本身!

这样一来, say 就变成了execute 中的本地变量 someFunction ,execute可以通过调用 someFunction() (带括号的形式)来使用 say 函数。

当然,因为 say 有一个变量, execute 在调用 someFunction 时可以传递这样一个变量。

我们可以,就像刚才那样,用它的名字把一个函数作为变量传递。但是我们不一定要绕这个“先定义,再传递”的圈子,我们可以直接在另一个函数的括号中定义和传递这个函数:

 
   
   
 
  1. Last login: Thu Jun 29 20:04:35 on ttys001

  2. liyuechun:~ yuechunli$ node

  3. > function execute(someFunction, value) {

  4. ...   someFunction(value);

  5. ... }

  6. undefined

  7. >

  8. > execute(function(word){ console.log(word) }, "Hello");

  9. Hello

  10. undefined

  11. >

我们在 execute 接受第一个参数的地方直接定义了我们准备传递给 execute 的函数。

用这种方式,我们甚至不用给这个函数起名字,这也是为什么它被叫做 匿名函数 。

这是我们和我所认为的“进阶”JavaScript的第一次亲密接触,不过我们还是得循序渐进。现在,我们先接受这一点:在JavaScript中,一个函数可以作为另一个函数接收一个参数。我们可以先定义一个函数,然后传递,也可以在传递参数的地方直接定义函数。

4.函数传递是如何让HTTP服务器工作的

带着这些知识,我们再来看看我们简约而不简单的HTTP服务器:

 
   
   
 
  1. var http = require("http");

  2. http.createServer(function(request, response) {

  3.  response.writeHead(200, {"Content-Type": "text/plain"});

  4.  response.write("Hello World");

  5.  response.end();

  6. }).listen(8888);

  7. console.log("请在浏览器中打开 http://127.0.0.1:8888...");

现在它看上去应该清晰了很多:我们向 createServer 函数传递了一个匿名函数。

用这样的代码也可以达到同样的目的:

 
   
   
 
  1. /**

  2. * 从零到壹全栈部落,添加小精灵微信(ershiyidianjian)

  3. */

  4. //请求(require)Node.js自带的 http 模块,并且把它赋值给 http 变量。

  5. let http = require("http");

  6. //箭头函数

  7. let onRequest = (request, response) => {

  8.    response.writeHead(200, {"Content-Type": "text/plain"});

  9.    response.write("Hello World");

  10.    response.end();

  11. }

  12. //把函数当作参数传递

  13. http.createServer(onRequest).listen(8888);

  14. console.log("请在浏览器中打开 http://127.0.0.1:8888...");

也许现在我们该问这个问题了:我们为什么要用这种方式呢?

5.基于事件驱动的回调

事件驱动是Node.js原生的工作方式,这也是它为什么这么快的原因。

当我们使用 http.createServer方法的时候,我们当然不只是想要一个侦听某个端口的服务器,我们还想要它在服务器收到一个HTTP请求的时候做点什么。

我们创建了服务器,并且向创建它的方法传递了一个函数。无论何时我们的服务器收到一个请求,这个函数就会被调用。

这个就是传说中的回调 。我们给某个方法传递了一个函数,这个方法在有相应事件发生时调用这个函数来进行回调 。

我们试试下面的代码:

 
   
   
 
  1. /**

  2. * 从零到壹全栈部落,添加小精灵微信(ershiyidianjian)

  3. */

  4. //请求(require)Node.js自带的 http 模块,并且把它赋值给 http 变量。

  5. let http = require("http");

  6. //箭头函数

  7. let onRequest = (request, response) => {

  8.    console.log("Request received.");

  9.    response.writeHead(200, {"Content-Type": "text/plain;charset=utf-8"});

  10.    response.write("添加小精灵微信(ershiyidianjian),加入全栈部落");

  11.    response.end();

  12. }

  13. //把函数当作参数传递

  14. http.createServer(onRequest).listen(8888);

  15. console.log("Server has started.");

  16. console.log("请在浏览器中打开 http://127.0.0.1:8888...");

Node.js 原生开发入门完全教程(上)

在上图中,当我们执行 node server.js命令时,Server has started.正常往下执行。

我们看看当我们在浏览器里面打开 http://127.0.0.1:8888时会发生什么。

Node.js 原生开发入门完全教程(上)Node.js 原生开发入门完全教程(上)

大家会发现在浏览器中打开 http://127.0.0.1:8888时,在终端会输出 Requestreceived.,浏览器会输出 添加小精灵微信(ershiyidianjian),加入全栈部落这一句话。

请注意,当我们在服务器访问网页时,我们的服务器可能会输出两次“Request received.”。那是因为大部分浏览器都会在你访问 http://localhost:8888/ 时尝试读取 http://localhost:8888/favicon.ico )

Node.js 原生开发入门完全教程(上)

6.服务器是如何处理请求的

好的,接下来我们简单分析一下我们服务器代码中剩下的部分,也就是我们的回调函数 onRequest()的主体部分。

当回调启动,我们的 onRequest()函数被触发的时候,有两个参数被传入: requestresponse

它们是对象,你可以使用它们的方法来处理HTTP请求的细节,并且响应请求(比如向发出请求的浏览器发回一些东西)。

所以我们的代码就是:当收到请求时,使用 response.writeHead()函数发送一个HTTP状态200和HTTP头的内容类型(content-type),使用 response.write()函数在HTTP相应主体中发送文本 添加小精灵微信(ershiyidianjian),加入全栈部落

最后,我们调用 response.end() 完成响应。

目前来说,我们对请求的细节并不在意,所以我们没有使用 request 对象。

7.服务端模块化

  • 何为 模块

 
   
   
 
  1. let http = require("http");

  2. ...

  3. http.createServer(...);

在上面的代码中,Node.js中自带了一个叫做“http”的模块,我们在我们的代码中请求它并把返回值赋给一个本地变量。

这把我们的本地变量变成了一个拥有所有 http 模块所提供的公共方法的对象。

给这种本地变量起一个和模块名称一样的名字是一种惯例,但是你也可以按照自己的喜好来:

 
   
   
 
  1. var foo = require("http");

  2. ...

  3. foo.createServer(...);

  • 如何自定义模块

server.js文件的内容改成下面的内容。

 
   
   
 
  1. /**

  2. * 从零到壹全栈部落,添加小精灵微信(ershiyidianjian)

  3. */

  4. //请求(require)Node.js自带的 http 模块,并且把它赋值给 http 变量。

  5. let http = require("http");

  6. //用一个函数将之前的内容包裹起来

  7. let start = () => {

  8.        //箭头函数

  9.    let onRequest = (request, response) => {

  10.        console.log("Request received.");

  11.        response.writeHead(200, {"Content-Type": "text/plain;charset=utf-8"});

  12.        response.write("添加小精灵微信(ershiyidianjian),加入全栈部落");

  13.        response.end();

  14.    }

  15.    //把函数当作参数传递

  16.    http.createServer(onRequest).listen(8888);

  17.    console.log("Server has started.");

  18.    console.log("请在浏览器中打开 http://127.0.0.1:8888...");

  19. }

  20. //导出`server`对象,对象中包含一个start函数

  21. //对象格式为

  22. /**

  23. * {

  24. *    start

  25. * }

  26. */

  27. //这个对象导入到其他文件中即可使用,可以用任意的名字来接收这个对象

  28. exports.start = start;

server.js当前的文件路径下新建一个 index.js文件。内容如下:

 
   
   
 
  1. /**

  2. * 从零到壹全栈部落,添加小精灵微信(ershiyidianjian)

  3. */

  4. //从`server`模块中导入server对象

  5. let server = require('./server');

  6. //启动服务器

  7. server.start();

如下图所示运行 index.js文件。

Node.js 原生开发入门完全教程(上)Node.js 原生开发入门完全教程(上)Node.js 原生开发入门完全教程(上)

一切运行正常,上面的案例中, server.js就是自定义的模块。

8.如何来进行请求的“路由”

我们要为路由提供请求的URL和其他需要的GET及POST参数,随后路由需要根据这些数据来执行相应的代码(这里“代码”对应整个应用的第三部分:一系列在接收到请求时真正工作的处理程序)。

因此,我们需要查看HTTP请求,从中提取出请求的URL以及GET/POST参数。这一功能应当属于路由还是服务器(甚至作为一个模块自身的功能)确实值得探讨,但这里暂定其为我们的HTTP服务器的功能。

我们需要的所有数据都会包含在request对象中,该对象作为onRequest()回调函数的第一个参数传递。但是为了解析这些数据,我们需要额外的Node.JS模块,它们分别是url和querystring模块。

 
   
   
 
  1.                               url.parse(string).query

  2.                                           |

  3.           url.parse(string).pathname      |

  4.                       |                   |

  5.                       |                   |

  6.                     ------ -------------------

  7. http://localhost:8888/start?foo=bar&hello=world

  8.                                ---       -----

  9.                                 |          |

  10.                                 |          |

  11.              querystring(string)["foo"]    |

  12.                                            |

  13.                         querystring(string)["hello"]

当然我们也可以用querystring模块来解析POST请求体中的参数,稍后会有演示。

现在我们来给onRequest()函数加上一些逻辑,用来找出浏览器请求的URL路径:

Node.js 原生开发入门完全教程(上)

接下来我在终端执行 node index.js命令,如下所示:

 
   
   
 
  1. bogon:如何来进行请求的“路由” yuechunli$ node index.js

  2. Server has started.

  3. 请在浏览器中打开 http://127.0.0.1:8888...

我先在 Safari浏览器中打开 http://127.0.0.1:8888,浏览器展示效果如下:Node.js 原生开发入门完全教程(上)

控制台效果如下:

 
   
   
 
  1. bogon:如何来进行请求的“路由” yuechunli$ node index.js

  2. Server has started.

  3. 请在浏览器中打开 http://127.0.0.1:8888...

  4. 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/starthttp://127.0.0.1:8888/upload,我们看看控制台展示的内容是什么。

 
   
   
 
  1. bogon:如何来进行请求的“路由” yuechunli$ node index.js

  2. Server has started.

  3. 请在浏览器中打开 http://127.0.0.1:8888...

  4. Request for /start received.

  5. Request for /upload received.

好了,我们的应用现在可以通过请求的URL路径来区别不同请求了--这使我们得以使用路由(还未完成)来将请求以URL路径为基准映射到处理程序上。

在我们所要构建的应用中,这意味着来自 /start/upload的请求可以使用不同的代码来处理。稍后我们将看到这些内容是如何整合到一起的。

现在我们可以来编写路由了,建立一个名为 router.js的文件,添加以下内容:

 
   
   
 
  1. /**

  2. * 从零到壹全栈部落,添加小精灵微信(ershiyidianjian)

  3. */

  4. function route(pathname) {

  5.  console.log("About to route a request for " + pathname);

  6. }

  7. exports.route = route;

如你所见,这段代码什么也没干,不过对于现在来说这是应该的。在添加更多的逻辑以前,我们先来看看如何把路由和服务器整合起来。

首先,我们来扩展一下服务器的 start()函数,以便将路由函数作为参数传递过去:

 
   
   
 
  1. /**

  2. * 从零到壹全栈部落,添加小精灵微信(ershiyidianjian)

  3. */

  4. //请求(require)Node.js自带的 http 模块,并且把它赋值给 http 变量。

  5. let http = require("http");

  6. let url = require("url");

  7. //用一个函数将之前的内容包裹起来

  8. let start = (route) => {

  9.        //箭头函数

  10.    let onRequest = (request, response) => {

  11.        let pathname = url.parse(request.url).pathname;

  12.        console.log("Request for " + pathname + " received.");

  13.        route(pathname);

  14.        response.writeHead(200, {"Content-Type": "text/plain;charset=utf-8"});

  15.        response.write("添加小精灵微信(ershiyidianjian),加入全栈部落");

  16.        response.end();

  17.    }

  18.    //把函数当作参数传递

  19.    http.createServer(onRequest).listen(8888);

  20.    console.log("Server has started.");

  21.    console.log("请在浏览器中打开 http://127.0.0.1:8888...");

  22. }

  23. exports.start = start;

同时,我们会相应扩展 index.js,使得路由函数可以被注入到服务器中:

 
   
   
 
  1. /**

  2. * 从零到壹全栈部落,添加小精灵微信(ershiyidianjian)

  3. */

  4. //从`server`模块中导入server对象

  5. let server = require('./server');

  6. let router = require("./router");

  7. //启动服务器

  8. server.start(router.route);

在这里,我们传递的函数依旧什么也没做。

如果现在启动应用(node index.js,始终记得这个命令行),随后请求一个URL,你将会看到应用输出相应的信息,这表明我们的HTTP服务器已经在使用路由模块了,并会将请求的路径传递给路由:

 
   
   
 
  1. bogon:如何来进行请求的“路由” v2.0 yuechunli$ node index.js

  2. Server has started.

  3. 请在浏览器中打开 http://127.0.0.1:8888...

  4. Request for / received.

  5. About to route a request for /



以上是关于Node.js 原生开发入门完全教程(上)的主要内容,如果未能解决你的问题,请参考以下文章

Node.js 入门教程 :准备

node.js 使用教程-2.Gulp 打包构建入门与使用

Node.Js开发实战详解

nodejs 入门

node.js视频教程

Node.js 入门教程 :模块