Skip to content

1、Node.js是什么

Node.js 是基于开源 v8 引擎的 JavaScript 运行时。

我们都知道HTMLCSSJavascript是运行在浏览器中的,这种情况下浏览器就是一个Javascript运行时,事实上Javascript代码不仅仅能在浏览器中运行,它还可以在另外一个Javascript Runtime(例如Node.js)中运行。所谓的Javascript Runtime就是一个可以运行javascript代码的容器、环境javascript代码能够在node.js环境中运行,主要依赖的就是谷歌开源的V8引擎。通俗的讲:Node.js就是一个开源的,跨平台的javascript运行环境

基于此,我们可以使用Node.js开发服务器应用(这意味着在网页开发的服务端(就是平常所说的后端)使用javascript),除此以外,使用Node.js还可以开发工具类应用(例如webpackvitebabel)、桌面端应用(例如vsCodeFigmaPostman都是基于electron(一个基于Node的桌面级开发框架)开发的)。

单线程基于事件驱动非阻塞式IO模型这三个特点使得node.js非常轻量化和高效,非常适合构建一个快速的、高伸缩性的数据密集型应用程序。例如基于NoSQL数据库(MongoDB)构建数据库API,构建一个数据流的应用程序(如:YouTube等),构建实时聊天应用程序,构建服务端应用程序等等。但node.js并不适合构建需要在服务端进行大量诸如图片处理、文件压缩等操作的应用程序,这种在服务端有着重量级操作的应用程序应该使用JavapythonPHP这类编程语言构建。使用node.js有以下的一些优点:

  • 使用一种语言(javascript)进行全栈开发
  • 非常活跃的社区
  • 有着非常丰富的开源库npm)可以被任何人使用

总结

  • Node.js是一个可以运行javascript代码的容器环境(称为JavaScript运行时)。
  • 基于Node.js可以开发服务器应用工具类应用桌面端应用
  • Node.js三大核心特性:单线程基于事件驱动非阻塞式IO模型

使用Node.js的注意事项:

  • 在node中是不能够使用和DOM、BOM相关的Web API的。
  • node中提供的API多是和文件读写、网络请求相关的。
  • node中的顶级对象为global,与global指向同一个对象的是globalThis变量。

2、Node.js初体验——使用fs模块对文件进行读写

引入fs模块:

javascript
const fs = require("fs"); // node.js默认使用CommonJS作为模块导入规范

开始读写文件;

javascript
// Blocking, synchronous way
const textIn = fs.readFileSync("./txt/input.txt", "utf-8");
console.log(textIn);

const textOut = `This is what we konw about the avocado: ${textIn}.\nCreate on ${Date.now()}`;
fs.writeFileSync("./txt/output.txt", textOut);
console.log("File written!");

/* 以下是输出结果:
The avocado 🥑 is popular in vegetarian cuisine as a substitute for meats in sandwiches and salads because of its high fat content 😄
File written!
*/

以上是node.js基于同步的方式对文件进行读写操作:注意到第2行与第6行代码,是通过fs模块的readFileSync(path [, options])以及writeFileSync(file, data[, options])API完成文件同步文件读写的。

所谓同步的操作就是代码逐行执行,下一行代码要等上一行代码执行完毕才会开始执行,这被称作是阻塞

但当要进行的操作非常缓慢时(例如读取一个非常大的文件),就会是一个严重的问题。因此node.js提供了一种以异步非阻塞的方式解决这个问题,异步操作是在后台运行的,这意味着下一行代码无需等待上一行代码执行完毕就会开始执行。如下所示:

javascript
// Non-blocking, asynchronous way
fs.readFile("./txt/start.txt", "utf-8", (err, data1) => {
  if (err) return console.log("ERROR! 💥");

  fs.readFile(`./txt/${data1}.txt`, "utf-8", (err, data2) => {
    console.log(data2);
    fs.readFile(`./txt/append.txt`, "utf-8", (err, data3) => {
      console.log(data3);

      fs.writeFile("./txt/final.txt", `${data2}\n${data3}`, "utf-8", (err) => {
        console.log("Your file has been written 😄");
      });
    });
  });
});
console.log("Will read file!");

/* 以下是输出结果:
Will read file!
The avocado 🥑 is also used as the base for the Mexican dip known as guacamole, as well as a spread on corn tortillas or toast, served with spices.
APPENDIX: Generally, avocados 🥑 are served raw, but some cultivars can be cooked for a short time without becoming bitter.
Your file has been written 😄
*/

注意上述代码中高亮行是通过fs模块中异步APIreadFile(path [, options], callback)writeFile(file, data[, options], callback)完成文件读写的。

可以看到node.js的异步代码块中处处充满回调函数,事实上上面这种层层嵌套的写法会使得代码难以阅读,这种现象被称作是回调地狱,可以使用ES6中的Promise或者ES8中的async/await解决,从而避免这种层层嵌套的代码格式。

3、Node.js初体验——使用http模块构建一个Web服务器

引入http模块:

javascript
const http = require("http");

创建一个简易的Web服务器并监听8080端口:

javascript
const server = http.createServer((req, res) => {
  console.log(req.url);
  // return a response
  res.end("Hello from the server!");
})

server.listen(8000, "127.0.0.1", () => {
  console.log("Listening to requests on port 8000");
});

在浏览器中访问127.0.0.1:8000会返回以下页面:

![返回页面](./images/node/简易Web服务器返回页面.png)

4、Node.js初体验——使用url模块完成页面路由的映射

引入url模块:

javascript
const url = require("url");

使用if/else基于不同的url执行不同的逻辑代码:

javascript
const server = http.createServer((req, res) => {
  const pathName = req.url;
  if (pathName === "/" || pathName === "/overview") {
    res.end("This is the OVERVIEW");
  } else if (pathName === "/product") {
    res.end("This is the PRODUCT");
  } else {
    res.writeHead(404, {
      "Content-type": "text/html",
      "my-own-header": "hello-world",
    });
    res.end("<h1>Page not found!</h1>");
  }
});

server.listen(8000, "127.0.0.1", () => {
  console.log("Listening to requests on port 8000");
});

基于上述代码,浏览器访问不同的路径便会得到不同的响应结果。

  • 获取请求中的路径参数

    javascript
    // 使用ES6的解构语法获取请求路径以及路径参数
    const { pathname, query } = url.parse(req.url, true);
    // true表示获取跟在请求url中的路径参数

练手小项目

结合HTML、CSS与上述的入门知识的一个小Demo:

首页截图

详情页截图

5、NPM:包管理工具

NPM是包含在Node中用来安装和管理开源包的包管理工具。可以使用npm init命令初始化一个Node项目,从而达到更加方便管理该项目的目的。如下图所示:

初始化Node项目

运行npm init命令以后在该项目文件夹中生成一个名为package.json的文件。如下所示:

Package.json文件

5.1、使用NPM安装依赖包

一个Node项目包含两种依赖,分别是普通依赖(dependencies)和开发依赖(devDependencies),普通依赖是项目运行中所需要的依赖,例如expressslugify这样的依赖,而开发依赖只是方便我们开发项目的工具,例如nodemon这样的依赖,在项目打包发布过程中并不会包含开发依赖。

使用NPM安装依赖的命令是:npm i packageName(安装普通依赖),事实上上述命令是npm install packageName --save的简写形式。使用npm i packageName --dev-save命令安装开发依赖。安装以后会发现项目文件夹中多出一个名为node_modules的文件夹,其中内容正是我们的项目所依赖的所有依赖包。

通常情况下我们所安装的依赖包只会在当前项目中生效,如果想要全局安装(使依赖包在整个计算机用户环境中生效),可以使用npm i packageName -g命令安装,事实上上面的命令是npm install packageName --global命令的简写形式。全局依赖和项目依赖的使用方式稍有不同,全局依赖可以直接在任何命令行终端中使用,而项目依赖需要在package.json文件中配置scripts的内容,将依赖命令写在其中,之后使用npm run ScriptName运行依赖工具。以nodemon举例:

json
{
  "name": "node-farm",
  "version": "1.0.0",
  "description": "Learning node.js",
  "main": "index.js",
  "scripts": {
    "start": "nodemon index.js"
  },
  "author": "WhiteVenus",
  "license": "ISC",
  "dependencies": {
    "slugify": "^1.6.6"
  },
  "devDependencies": {
    "nodemon": "^3.0.2"
  }
}

package.json文件中配置依赖启动命令之后,就可以使用npm run start运行本项目中配置好的依赖工具,甚至可以使用npm start简写命令来完成相同的事情。

5.2、使用NPM对包进行管理

使用npm outdated检查项目中过时的依赖包(有新版本存在),例如:

bash
 npm outdated
Package  Current  Wanted  Latest  Location              Depended by
slugify    1.0.0   1.6.6   1.6.6  node_modules/slugify  1-node-farm

另外package.json文件中依赖的版本前面如果是^,则表示接受一切更新(包括主版本、次版本、补丁版本),例如:

json
{
	...
  "dependencies": {
    "slugify": "^1.0.0"
  },
  "devDependencies": {
    "nodemon": "^3.0.2"
  }
}

~表示只接受补丁版本的更新,当上述代码第4行被修改为:

json
"slugify": "~1.0.0"

再次运行npm outdated命令会得到以下结果:

bash
 npm outdated
Package  Current  Wanted  Latest  Location              Depended by
slugify    1.0.0   1.0.2   1.6.6  node_modules/slugify  1-node-farm

使用npm update packageName可以更新依赖、npm uninstall packageName删除依赖。当向别人分享项目代码或者上传GitHub时,不需要将node_modules一起上传,别人拿到项目目录结构后,只需要执行npm install命令即可自动根据package.json文件安装相应依赖。