飞道软件工厂

插件式开发

Nodejs简介

官网

Node官网 Node中文网

简介

node是一个跨平台的运行环境,解释编译并运行js代码。

console.log('hello world');

以上为最著名的Hello World的示例代码,是每一个开发人员在第一堂课都会接触到的。我相信很多初始开发人员都会很直接地想了解以上的代码是干什么用的,它作为一个“程序”能干什么事。让我们来新手体验一下吧:

  1. 按官网的说明安装nodejs到你的电脑。
  2. 打开一个终端1,输入node并回车。
  3. 输入(粘贴亦可)上面的代码,并回车。
  4. 可以在终端上看到hello world的屏幕输出。

另一种运行这行代码的方法

  1. 新建一个文本文件,文件名叫hw.js
  2. 将上面的代码输入进hw.js,并保存文件。比如文件保存后的路径为/home/fd/hw.js
  3. 打开终端
  4. 输入node /home/fd/hw.js并回车
  5. 终端上会显示hello world

    说明:

    1. 终端在Windows上的名字可以是dos,可以是powershell,可以是命令行

Node是高效的

node内嵌v8引擎,运行效率非常高效,我们做一个示例来说明。

让我们来作一个求质数(也叫素数)的算法(因为只是为了验证运行效率,所以我们不讨论算法本身的高效与否)。

质数就是除了1和它自身,没有其它因数的整数。也就是说除了1和它自己,别的数它都除不尽。比如2,3就是质数,而4因为能被2整除,所以4不是质数,6能被2和3整除,也不是质数,而5是质数。

我们让nodejs版本与java作一个纯计算的对比:

function isprime(n) {
	for (let i = 2; i < n; i++) {
		if (n % i === 0) {
			return false;
		}
	}
	return true;
}

function test(n) {
	console.time(n);
	// const primes = [];
	let count = 0;
	for (let i = 2; i < n; i++) {
		if (isprime(i)) {
			++count;
	}
	console.timeEnd(n);
}

test(1000);
test(10000);
test(100000);
import java.util.Date;
import java.util.ArrayList;

public class Test {
	public static void main(String[] args) {
		Test t = new Test();
		t.test(1000);
		t.test(10000);
		t.test(100000);
	}

	private boolean isprime(int n) {
		for (int i = 2; i < n; i++) {
			if (n % i == 0) {
				return false;
			}
		}
		return true;
	}

	private void test(int n) {
		long tm = (new Date()).getTime();
		long count = 0;
		for (int i = 2; i < n; i++) {
			if (isprime(i)) {
				++count;
			}
		}
		System.out.println(n + ":" + ((new Date()).getTime() - tm) + "ms");
	}
}

测试方法如下:

js:

  1. 将代码保存进一个文件,如test.js
  2. 输入命令node test.js

java

  1. 将代码保存进一个文件,文件如为Test.java
  2. 输入命令javac Test.java编译,生成Test.class文件
  3. 输入命令java Test执行

下面列出对比结果

引擎 版本 内存占用(m) 1000(ms) 10000(ms) 100000(ms) 1000000(s)
java openjdk 11.0.8 13.7 2 35 1996 166
node 15.0.1 6.9 2.37 36.946 2417 205
node 12.19.0(Erbium) 8.0 2.408 36.078 2691 220
node 10.22.1(Dubnium) 8.6 3.180 33.443 2177 189
node 8.17.0(Carbon) 7.6 3.563 33.106 2241 177
node 6.17.1(Boron) 6.4 0.807 26.101 1990 166
node 4.9.1(Argon) 7.2 1 26 2010 165

可以看出,nodejs与java纯计算的运行效率相比较慢。但也能说明其运行效率。因为工具的原因,学徒未能测试多个版本的java,有兴趣的老师可以自行测试。

单进程

这是个优点也是缺点。 因为没有多线程,所以不用考虑并发加锁的问题。多数初级开发人员甚至中高级开发人员对于线程,纤程,携程的概念理解不到位。在生产遇到此类问题极难排查和解决。 缺点是单个大量计算任务是处理速度慢并会阻塞整个引擎。不能最大限度利用多核CPU。如果有此类计算任务,需要交有多线程的引擎来计算。所幸在多数业务业务系统中这种情况极少发生。

非阻塞io

非阻塞io高并发,既轻量又高效。

最适合用来处理网络请求。可谓是网络开发人员的福音。多数网络服务器需要处理的事物都是单量任务小而数量巨大。

Node.js提供的能力使得前端开发人员可以快速变为全栈开发人员,这是一个很大的优点,能够极大节省企业成本。

事件驱动。与客户端开发模式相同,减少开发人员学习负担。

事件驱动vs线程驱动

事件驱动编程主要思想是通过事件或状态的变化来进行应用程序的流程控制,一般通过事件监听完成,一旦事件被检测到,则调用相应的回调函数。事件驱动主要执行过程是当进来的一个新的请求的时候,请求将会被压入队列中,然后通过一个循环来检测队列中的事件状态变化,如果检测到有状态变化的事件,那么就执行该事件对应的处理代码,一般都是回调函数或Promise(实际本质上还是回调函数)。

线程驱动是当收到一个请求的时候,将会为该请求开一个新的线程来处理请求。而线程主要是由线程池来管理的。当线程池中有空闲的线程,会从线程池中拿取线程来处理,如果线程池中没有空闲的线程,新来的请求将会进入队列排队,直到线程池中空闲线程

总结

个人认为,node使得前端开发人员可以低成本转换为全栈开发人员,可以极大节省企业开发成本。

目前为止,能够使用一种开发语言开发多个端点应用的只有JavaScript。而在node中,经过简单学习就可以使用JavaScript完成服务的开发。

Node.js 命令

命令行(cli)

在终端输入node,回车进入nodejs命令行界面

>

在这里可以输入JavaScript代码,回车执行。跟在浏览器的开发者工具里控制台部分不同的是,这里可以使用require加载node或npm的模块。

如果想输入多行代码,直接回车即可,Node.js会自动添加三个点,表示前面的语句输入还没结束,可以继续前面的输入。

如果要退出Node命令行,有以下几种办法:

  1. 连续按2次ctrl+c
  2. ctrl+d
  3. 输入process.exit()也能退出,该方法有点儿Hack的味道。

运行一个文件

比如我们有一个hw.js文件

function hw() {
	console.log('hello world');
}

hw();

使用命令执行它:

node hw.js

就会在控制台输出’hello world’

测试代码

有时候我们想在当前的js文件中写一段测试(Python的风格),我们可以这样做

function hw() {
	console.log('hello world');
}

if (require.main === module) {
	hw();
}

这样,只有当运行命令node + 文件名的方式hw()才会被调用。而被另外其它的文件require的时候,是不会执行的。

Node.js模块介绍

介绍几个常用的Node.js的模块。

Http

相信这个模块差不多是最容易被接触到的模块,原因之一是因为Node.js用来做WebServer的时候经常直接拿该模块实现简单的一个Http服务。

送上一个官方的例子hello world:

const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World!\n');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

事件上这种用法我们还真不常用,通常如果要做一个WebServer,大家通常会使用类似express这样一些做过一些封装的模块。这个模块最常用的倒是发起一个http请求,向第三方站点(典型的有微信公众号)进行通信。比如以下例子请求淘宝ip地址库,获取某个已知ip的信息。

import { get } from 'http';

export default function atom(msg: ICommonParams, headers: IHeaders) {
	const ipAddress = '127.0.0.1';
	const server_address = 'http://ip.taobao.com/service/getIpInfo.php?ip=' + ipAddress;
	return new Promise<IWebResult>((resolve, reject) => {
		const req = get(server_address, (rs) => {
			let buf = '';
			rs.on('data', (chunk) => {
				buf += chunk;
			});
			rs.on('end', () => {
				const data = JSON.parse(buf);
				if (data.code === 0) {
					resolve(data.data);
				} else {
					reject(data);
				}
			});
		});
		req.on('error', (e) => {
			reject(e);
		});
	});
}

File System

这个模块我们一般用它来做文件的读和写,在浏览器中,使用JavaScript是不可以访问本地磁盘文件的,而在Node.js中,你可以这样做。如果我们希望操作本机的一些磁盘文件,而又对系统的脚本编写不熟悉的话,刚好你的电脑上有Node.js,那么你就可以使用JavaScript脚本来做一些文件的处理,还是比较方便的,这里只介绍一下文件的读写(只介绍10.2版本才支持的所谓试验性功能)

读文件

const fs = require('fs').promises;
const content = await fs.readFile('/file/path.txt', 'utf8');

写文件

const fs = require('fs').promises;
await fs.writeFile('/file/path.txt', 'file content', 'utf8');

Path

获取文件名

const path = require('path');
path.basename('/foo/bar/baz/asdf/quux.html');
// Returns: 'quux.html'
path.basename('/foo/bar/baz/asdf/quux.html', '.html');
// Returns: 'quux'

获取文件路径

const path = require('path');
path.dirname('/foo/bar/baz/asdf/quux');
// Returns: '/foo/bar/baz/asdf'

获取文件后缀名

const path = require('path');
path.extname('index.html');
// Returns: '.html'

path.extname('index.coffee.md');
// Returns: '.md'

path.extname('index.');
// Returns: '.'

path.extname('index');
// Returns: ''

path.extname('.index');
// Returns: ''

拼接路径

const path = require('path');
path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');
// Returns: '/foo/bar/baz/asdf'

其它

NPM 介绍

npm是随同Node.js一直安装的一个包管理工具,Node.js成为一个生态,npm功不可没。

npm is the package manager for JavaScript and the world’s largest software registry. Discover packages of reusable code — and assemble them in powerful new ways.

以上为npm官方对它的定义,一般情况下,我们没有必要单独安装或者升级

Yarn

我们使用阿里提供镜像安装依赖速度更快。我们使用它来进行模块的下载和安装。要想使用它,先要通过npm安装它

npm i -g yarn --registry=http://registry.npm.ifeidao.com
yarn config set registry http://registry.npm.ifeidao.com

当Node.js版本升级时,需要重新执行以上脚本重新安装yarn

安装模块

安装某个模块(xxx为模块名称)

yarn add xxx

安装过程有可能(依据具体模块而定)会到Python2和c++编译环境,如果缺少,就有可能造成安装过程失败。

安装成功后,会在当前目录下生成一个node_modules目录,所有在当前路径下执行npm install 命令下载生成的文件都存在于这个路径下。

安装项目依赖的模块

对于一个已经存在的项目,安装该项目的所有依赖模块,需要在该项目路径下执行

yarn

重装依赖模块

在项目路径下,删除整个node_modules目录,然后重新安装依赖即可

rm -rf ./node_modules/
yarn

当因为网络原因安装依赖失败,或是因为框架升级新版本需要重新安装依赖的情况,需要重新安装全部依赖。