一篇入门Nodejs可不可行?【实打实的使用Node,就够了!】提供示例源码

1,004 阅读10分钟

本文已参与掘金创作者训练营第三期「话题写作」赛道,详情查看:掘力计划|创作者训练营第三期正在进行,「写」出个人影响力

这是一篇对Node使用和各个方面的整体介绍,力争进行足够详细的且精简的介绍,实现一篇就入门的目的。如有问题欢迎指正或批评!

Node的介绍和流行就不再赘述了,直接撸起袖子,实打实的使用Node,先从其概念和基本使用开始!

Node介绍

NodeJs官网

Node基于V8引擎开发,实现了脱离于浏览器运行js,使得JavaScript可以编写Web服务器后台应用,甚至现在,桌面应用(Electron...)、手机App(React Native、uni-app...)等市场也开始被js占领。

Webpack、Gulp,Npm等前端工程化的工具都是依赖于Node。node也被广泛应用于中间层服务器,负责IO读写、数据查询等。

Node.js 就是运行在服务端的 JavaScript,是一个基于Chrome JavaScript 运行时建立的一个平台。

Node.js是一个事件驱动I/O的服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。加上前端的蓬勃发展,js已经逐渐发展为一门全栈、全端的语言,也使得Node如此受欢迎!

Node使用

下载安装

nodejs.cn/download/cu… 下载最新的Nodejs版本。目前最新的长期支持版本为 v14.17.5 。

选择对应的版本下载即可。

下载好后。双击运行安装程序:

选择安装路径:

自定义安装中,选择需要的项。通常保持默认,都选择即可:

不要勾选自动安装所需工具:

install等待安装完成即可:

查看版本、运行js文件

> node -v
v14.17.0

进入node环境运行js和js文件

命令中,输入node,回车:

> node
Welcome to Node.js v14.17.0.
Type ".help" for more information.
> let a=1;
undefined
> a+1
2
> a++
1
> a
2
> .exit

Node环境的退出使用.exit命令。或者按键Ctrl+D,或两次按键Ctrl+C

执行一个js文件。新建test.js文件,文件内容为console.log("Hello World!");

test.js文件所在目录下,启动命令工具cmd或powershell。如下:

> node test.js
Hello World!
> node test
Hello World!

NPM(Node Package Manager)包管理器

NPM命令和CNPM使用

在使用和开发Nodejs项目时,通常使用npm管理用到的包及包之间的依赖。

下面是各npm命令的简要介绍和使用。

  • npm init,用于初始化一个项目.
  • npm install -g cnpm --registry=https://registry.npm.taobao.org 安装cnpm,cnpm是npm的国内镜像(淘宝),基本和npm保持同步,国内使用可以免除网络连接的限制。
  • cnpm i packagenamecnpm install packagename,安装一个包。

如安装jquery:cnpm i jquery

新版npm install仅表示将node包下载到node_modules文件夹,并不会将其作为依赖添加到package.json

  • cnpm un packagenamecnpm uninstall packagename,将包从node_modules文件夹下移除,并删除package.json对应依赖。
  • cnpm i packagename -S:安装包并将其添加到package.json的依赖中。
  • cnpm i packagename -D:安装包并将其添加到package.json的开发者依赖中。
  • npm update xxx更新某个包。
  • 直接运行cnpm icnpm install,可以将 package.json 中的依赖全部安装

package.json文件,里面包含用到的包信息(node或js库、框架等),用以管理需要的包和包的依赖。通过npm install安装包时,可以指定安装在哪个依赖下,这样会修改package.json中的内容,维护包及其依赖。

包依赖分类

开发依赖:devDependencies

开发环境依赖,仅次于 dependencies 的使用频率!它的对象定义和dependencies一样,只不过它里面的包只用于开发环境,不用于生产环境,这些包通常是单元测试或者打包工具等,例如gulp, grunt, webpack, moca, coffee等

  • 安装命令:
npm install package-name --save-dev

npm install package-name -D

生产依赖:dependencies

应用依赖,或者叫做业务依赖,这是最常用的依赖包管理对象!它用于指定应用依赖的外部包,这些依赖是应用发布后正常执行时所需要的,但不包含测试时或者本地打包时所使用的包

  • 安装命令
npm install package-name --save

npm install package-name -S

参考npm--02--开发依赖和生产依赖

推荐后续node项目使用Yarn包管理器,后续文章会详细介绍yarn!

Node在多系统用户情况下的使用

一般在node安装后,切换到另一个用户下命令行下输入node -vnpm -v能够正确获取版本号。

但是通过npm全局安装的模块,无法在命令行下获取和运行。

解决办法是重新安装node或者修复安装node即可解决。

如果修复安装node后,npm全局安装模块后仍然无法使用,再次的解决办法是将%APPDATA%\npm路径添加到用户的环境变量中,如Administrator用户,路径为:C:\Users\Administrator\AppData\Roaming\npm,添加环境变量

也可以直接添加到系统的PATH环境变量中。

Node的模块

回调函数

首先说一下Node中的回调函数。因此在Node中编写或导入模块时,通常会用到很多的回调函数。

回调函数是JavaScript中对异步编程的实现,你会在Node或js中发现大量的回调函数的使用。

所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到需要执行第二段任务的时候,就直接调用这个函数。

Node 约定,回调函数的第一个参数,必须是错误对象err(如果没有错误,该参数就是null)

原因是执行分成两段,第一段执行完以后,任务所在的上下文环境就已经结束了。在这以后抛出的错误,原来的上下文环境已经无法捕捉,只能当作参数,传入第二段。

Generator 函数的异步应用

全局模块(对象)

随时随地都可以访问,不需要引用。如process

process.env 环境变量

比如根据不同环境变量输出不同内容:

// process.env.dev=true;
if(process.env.dev){
    console.log("我是开发环境");
}
else{
    console.log("我是生产环境");
}

process.argv 输入的命令和参数

如下新建test.js文件,内容为:

console.log(process.argv);

let num1=parseInt(process.argv[2]);
let num2=parseInt(process.argv[3]);
console.log(num1+num2);

执行:

>node test 20 40
[
  'C:\\Program Files\\nodejs\\node.exe',
  'E:\\vscode\\Node接触\\Node接触使用1\\test',
  '20',
  '40'
]
60

__dirname 路径名

__dirname 用于获取输出当前执行的文件所在的路径。

Buffer 对象

Buffer对象是Node处理二进制数据的一个接口。它是Node原生提供的全局对象,可直接使用。

Buffer代表一个缓冲区,主要用于操作二进制数据流。是一个类似数组的对象,成员是8位的一个字节(取值为0到255的整数值)。

  • Buffer对象使用

如下,实例化一个Buffer对象,并赋值和取值。

// 生成一个256字节的Buffer实例
var bytes = new Buffer(256);

// 遍历每个字节,写入内容
for (var i = 0; i < bytes.length; i++) {
  bytes[i] = i;
}


// 生成一个buffer的view
// 取出从240字节到256字节
var end = bytes.slice(240, 256);

end[0] // 240
end[0] = 0;
end[0] // 0
  • Buffer拷贝

如下,copy 方法将 bytes 实例的4号到7号成员的这一段,拷贝到more实例从0号开始的区域。

var bytes = new Buffer(8);

for (var i = 0; i < bytes.length; i++) {
  bytes[i] = i;
}

var more = new Buffer(4);
bytes.copy(more, 0, 4, 8); // bytes.length
more[0] // 4
  • Buffer和字符串的转换

将字符串转换为Buffer对象,可以使用:

new Buffer(str[,encoding]) —— 根据一个字符串和编码格式创建buffer,不指定编码时默认使用utf8。

将Buffer对象转换为字符串:

toString方法将Buffer实例,按照指定编码(默认为utf8)转为字符串。

var hello = new Buffer('Hello');
hello // <Buffer 48 65 6c 6c 6f>
hello.toString() // "Hello"
  • Buffer构造函数
  1. new Buffer(size),创建一个指定大小的buffer。
  2. new Buffer(array),根据一个字节数组来创建一个buffer,数组成员必须是整数值。
  3. new Buffer(str[,encoding]),根据一个字符串和编码格式创建buffer,不指定编码时默认使用utf8。
  4. new Buffer(buffer),根据buffer实例创建一个新的buffer。
  • 类方法
  1. Buffer.isEncoding('utf8') 检测是否为有效的编码参数。即检查 utf8 是否是有效的。
  2. Buffer.isBuffer(Date) 检查是否是一个Buffer对象。
  3. Buffer.byteLength('Hello', 'utf8') 返回字符串实际占据的字节长度,默认utf8。
  4. Buffer.concat() 将一组Buffer对象合并为一个Buffer对象
  • 实例方法
  1. write() 写入数据。第一个参数是所写入的内容,第二个参数(可省略)是所写入的起始位置(默认从0开始),第三个参数(可省略)是编码方式,默认为utf8。
  2. slice() 返回一个按照指定位置、从原对象引用出来的Buffer实例。它的两个参数分别为起始和终止位置。slice方法创造原内存的一个视图(view),返回的buffer跟原buffer引用的是同一块内存。
  3. toString() 转为字符串。
  4. toJSON() 将Buffer实例转为JSON对象。

系统模块

系统内置,需要require()引入,但不需要单独下载。

path

path:用于处理文件路径和目录路径的使用工具。

下面是path提供的几个方法的使用:

let path=require('path');

// 获取目录路径
let dir= path.dirname("/node/a/b/c/1.jpg");
// 文件名
let filename = path.basename("/node/a/b/c/1.jpg");
// 后缀名
let extname = path.extname("/node/a/b/c/1.jpg");

console.log(dir);
console.log(filename);
console.log(extname);

// resolve处理多个参数共同组合下的路径
console.log(path.resolve("/node/a/b/c/d","../","e/f/g","../../","h"));

运行输出:

$ node test.js
/node/a/b/c
1.jpg
.jpg
e:\node\a\b\c\e\h

fs

fs:用于文件读写操作。

如下是使用fs读取文件、覆盖写入文件、追加写文件,及同步读写文件的示例:

let fs=require("fs");

// 读取一个文件
fs.readFile("./a.txt",(err,data)=>{
    if (err) {
        console.log(err);
    }
    else{
        // 输出为二进制的 <Buffer 31 32 33 2c 0d 0a 61 62 63 2c 0d 0a e4 bd a0 e5 a5 bd e3 80 82>
        //console.log(data);
        console.log(data.toString());
    }
})
// 写入一个文件,文件不存在将创建,存在则覆盖内容
fs.writeFile("b.txt","你好,这是使用node写入一个文件(覆盖)",err=>{
    console.log(err);
})

// {flag:"a"} append 追加到一个文件
fs.writeFile("b.txt", "你好,这是使用node写入一个文件(追加)",{flag:"a"}, err => {
    console.log(err);
})

//append 追加到一个文件方法
// fs.appendFile(filename,data,[options],callback);
fs.appendFile("b.txt", '你好,这是使用node写入一个文件(追加)', function () {
    console.log('追加内容完成');
});

//同步读取
let fileContent=fs.readFileSync("a.txt");
console.log(fileContent.toString());

//同步写
let write=fs.writeFileSync("b.text","同步写文件方法");
console.log(write);

自定义模块

Nodejs支持自定义的模块,同时使用 require 可以引入自己封装的模块。以及引入和使用通过npm安装的第三方模块。

模块的导出

  • exports导出,exports 是一个对象,通过它可以将一个变量或参数暴露出去。

如,在一个js文件中:

exports.a=1;
exposts.b="你好,Node!";

在另一个文件中引入,并输出变量:

/* 引入自定义模块 */
let importData=require("./exportsUse");
console.log(importData.a);
console.log(importData.b);
  • module导出,通过 module.exports 可以导出变量、对象、函数等。
module.exports=class{
    constructor(name){
        this.name=name;
    }
    holle(){
        console.log("你好:"+this.name);
    }
}

引入

let mod = require("./exportsUse");
var m=new mod("张三");
m.holle();

require引入

上面已经演示了 require 引入的使用。主要介绍下引入时的注意点。

引入自定义模块时:必须使用 ./ 指定当前路径的模块,即必须指定模块的路径

否则报错 Cannot find module

不指定路径 将从 node_modules 路径下查找模块。

require引入时的路径查找

  1. 默认从当前路径的node_modules下查找。
  2. 如果没有则从node安装目录下查找。
  3. 其他路径的模块,必须指定模块的路径,否则无法找到。

http模块

http模块是由 Node 提供的用于创建web服务的系统模块。可以很方便的启用一个处理http请求的web服务器。

http模块使用

  • 创建服务器对象:http.createServer()

如下,httpSerer.js文件中创建一个web服务器,监听8080端口:

let http=require("http");

http.createServer((req,res)=>{
    console.log("这是node创建的Web Server");//会在服务器终端打印

    res.write("hello,i'm a Web Server!");
    res.end();
}).listen(8080);

node运行:

$ node httpServer
这是node创建的Web Server
这是node创建的Web Server

在浏览器中访问本地的8080端口: http://localhost:8080,显示结果如下:

http web server根据请求返回页面

如下,创建一个node server,根据请求的url返回对应的文件。

let http=require("http");
let fs=require("fs");

http.createServer((req,res)=>{
    fs.readFile(`./${req.url}`,(err,data)=>{
        if (err) {
            res.writeHead(404);
            res.end("404 Not Found");
        }else{
            res.end(data);
        }
    })

}).listen(8080);
console.log('Web服务已启动,访问 http://localhost:8080 测试');

在命令中启动 httpServer.js

$ node httpServer.js
Web服务已启动,访问 http://localhost:8080 测试

当请求到存在文件的路径时,会返回文件中内容:

1.html 是与 httpServer.js 位于同级目录的新建的HTML文件。

其他情况则返回404的提示:

http服务的数据交互

下面对web请求的处理,均基于上面的 httpSerer.js 文件中 createServer 回调。

GET方法

通过url模块,处理get请求中url的查询字符串及路由信息。

let fs=require("fs");
let url=require("url");

// 输出解析的请求的url
console.log(url.parse(req.url));

/*
Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: '?username=zhangsan&password=123456',
  query: 'username=zhangsan&password=123456',
  pathname: '/login',
  path: '/login?username=zhangsan&password=123456',
  href: '/login?username=zhangsan&password=123456'
}
*/

// 解析url时格式化查询字符串,query格式化为json对象
console.log(url.parse(req.url,true));
/* 
Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: '?username=zhangsan&password=123456',
  query: [Object: null prototype] { username: 'zhangsan', password: '123456' },
  pathname: '/login',
  path: '/login?username=zhangsan&password=123456',
  href: '/login?username=zhangsan&password=123456'
}
*/

//获取请求路由和查询字符串对象
let {pathname,query}=url.parse(req.url,true);
    console.log(pathname,query);
/* 结果
/login [Object: null prototype] { username: 'zhangsan', password: '123456' }
*/

POST方法

POST方法将请求的数据放在请求体中,因此可以传输大数据(<2G,有的是1G)。

因此在POST实际传输中数据是被一段一段的传输到服务器的,利用这一点,可以在Node Server端接受每一段的数据,最后组合成发送过来的完整数据。

req.on("data",(data)=>{...})方法,可以接受每一段传输的数据;req.on("end",()=>{...})方法,会在数据接收完后执行。

let http=require("http");
let querystring=require("querystring");

let result=[];
//接收每一段的buffer
req.on("data",buffer=>{
    result.push(buffer);
})
// 接收完成,处理数据
req.on("end",()=>{
    let data=Buffer.concat(result);
    console.log(data);
    console.log(data.toString());
    console.log(querystring.parse(data.toString()));
})
/* 输出
<Buffer 75 73 65 72 6e 61 6d 65 3d 7a 68 61 6e 67 73 61 6e 26 70 61 73 73 77 6f 72 64 3d 31 32 33 34 35 36>
username=zhangsan&password=123456
*/
/* querystring.parse 输出
[Object: null prototype] { username: 'zhangsan', password: '123456' }
*/

参考

介绍的目录结构和顺序主要参考自前端面试加分福音--node基础(内容有些老,但是介绍的流程和思路结构还是非常棒的)、Buffer对象,以及参考了网上其他的资料,除了文中给出的参考链接,不再一一列出,所有内容都是结合实际示例进行介绍。

本篇示例源码下载