nodejs
Wei Jieyang Lv4

工具

NVM

nodejs的版本管理工具

nvm ls

截屏2021-03-23 上午10.54.30
  • 列出电脑下所有的nodejs版本,并指出当前版本

nvm use 10.15.0

  • 切换当前使用的nodejs版本

nvm install 10.15.0

  • 下载并使用版本10.15.0

NPM

nodejs的包管理工具

npm init

  • 可以初始化一个package.json文件。在初始化的过程中,会叫用户输入name, version等等信息,当然,你都可以忽略。一路点回车,就生成了下面这样一个初始化的package.json。

package.json

1
2
3
4
5
6
7
8
9
10
11
{
"name": "test", // 假如项目叫做test
"version": "1.0.0",
"description": "this is my test",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
  • name: 这个很好理解,就是package的名称。不过需要注意的是,name有长度限制(虽然一般都不会超),而且name不能以 【点】 或者 【下划线】开头,name中不能有大写字母。这个是每一个package必须的。在业务代码中,通过require(${name})就可以引入对应的程序包了。
  • version: package的版本。对于业务项目来说,这个往往不太重要,但是如果你要发布自己的项目,这个就显得十分重要了。name和version共同决定了唯一一份代码。npm是用[npm-semver]来解析版本号的。我们一般见到的都是大版本.次要版本.小版本这种版本号,比如16.1.0。版本号的规则、含义其实蛮多的,可以参考这篇文章
  • desription:包的描述。开发组件库时必需,简明的向库的使用者介绍这个库是干嘛的。对于公司的业务项目,这个配置项一般无所谓。
  • keywords:关键词。一个字符串数组,对这个npm包的介绍。组件库必需,便于使用者在npm中搜索。对于公司业务项目,这个配置一般无所谓。
  • homepage: 项目主页。对于开发组件库来说挺有用的。
  • bugs:开发者的联系方式,代码库的issues地址等。如果代码使用者发现了bug,可以通过这个配置项找到提bug的地方。
  • license:开源协议。对于开源组件库,这个十分重要。之前react还因为这事儿没少被社区嫌弃。开源协议略微复杂,用阮一峰前辈的一张图来说明一下吧。注:图里少了ISC, ISC和BSD差不多
截屏2020-12-22 下午7.11.30
  • author:项目的作者。可以为字符串,对象。

  • contributors:项目的贡献者。author的数组。

  • main:代码入口。这个十分重要,特别是对于组件库。当你想在node_modules中修改你使用的某个组件库的代码时,首先在node_modules中找到这个组件库,第一眼就是要看这个main,找到组件库的入口文件。在这个入口文件中再去修改代码吧。

  • scripts:指定了运行脚本命令的npm命令行缩写。十分重要。

    • 在命令行输入:npm run dev , 对应的命令就会被执行。这里有一个地方需要注意,当执行npm run xxx的时候,node_modules/.bin/目录会在运行时被加入系统的PATH变量。
    • 上面的例子,当我们在命令行输入:npm run build时,其实真正执行的命令是node_modules/.bin/webpack而不是webpack。所以,当你的webpack并未全局安装时,直接在命令行输入:webpack是会报错的。因为你的webapck是安装在node_modules/.bin/下面的。
    1
    2
    3
    4
    5
    6
    7
    "scripts": {
    "dev": "NODE_ENV=dev webpack-dev-server --progress --hot --host 0.0.0.0 --port 8089",
    "test": "NODE_ENV=test webpack --config webpack.test.config.js --progress",
    "online": "NODE_ENV=production webpack --config webpack.online.config.js --progress",
    "build": "webpack",
    "node": "node server.js"
    },
  • directories:对整个代码结构的描述。告诉代码包使用者可以在哪里找到对应的文件。

  • files:数组。表示代码包下载安装完成时包括的所有文件。

  • repository:对于组件库很有用。让组件库使用者找到你的代码库地址。这个配置项会直接在组件库的npm首页生效。例子:

    1
    2
    3
    4
    "repository": {
    "type": "git",
    "url": "git+https://github.com/CoyPan/react-scroll-to-show-cb.git"
    },
  • config:用于添加命令行的环境变量。具体用法见这里

  • dependencies:项目的依赖。通过npm install --save安装的包会出现在这里。

    • 注意,不要把测试工具、代码转换器或者打包工具等放在这里。当你在命令行里面使用npm install react --save时,react就会出现在dependencies。默认是安装最新的版本。如果想安装某个特定的版本,可以npm install react@15.6.2

以下的dependencies,格式都是合法的,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"dependencies" : { 
"foo" : "1.0.0 - 2.9999.9999",
"bar" : ">=1.0.2 <2.1.2",
"baz" : ">1.0.2 <=2.3.4",
"boo" : "2.0.1", // 指定了就是2.0.1版本
"qux" : "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0",
"asd" : "http://asdf.com/asdf.tar.gz",
"til" : "~1.2.2", // 安装版本号不低于1.2.2的1.2.x的最新版本
// 例如:1.2.3, 1.2.4等等。1.2.1 ,1.3.x 等就不行了
"elf" : "^1.2.3", // 安装版本号不低于1.2.2的1.x.x的最新版本
// 例如: 1.2.7,1.7.8等,1.2.1 ,2.0.0 等就不行了。
// 注意,如果配置是^0.x.x,则和~0.x.x的效果一样。
"two" : "2.x",
"thr" : "3.3.x",
"lat" : "latest", // 安装最新版本
"dyl" : "file:../dyl",
//"foo": "git+ssh://git@github.com:foo/foo.git#v1.0.1" // 还可以这样
}
  • foo组件的地址为git+ssh://{foo代码库的ssh地址}#v{foo的版本号}
    • 好处:组内的许多项目都有同一个功能,把这个功能抽出来做成组件是很自然的想法。但是每个项目都有自己的代码库,公司也没有内部的npm库,组件应该放在哪里呢?可以专门为组件新建一个代码仓库,将组件放在这里开发、迭代。这样,各个项目都可以引用该组件:只需要在dependencies中将组件配置成上述的形式。至于组件的版本,可以通过git tag来控制。
  • dependencies还有其他的配置方式,具体在这里查看。

  • devDependencies:项目的依赖。通过npm run install --save-dev安装的包会出现在这里。主要是在开发过程中依赖的一些工具。用法与dependencies相似。
  • bundledDependencies:数组,打包时的依赖。如果配置了bundledDependencies,在项目中执行 npm pack将项目打包时,最后生成的.tgz包中,会包含bundledDependencies中配置的依赖。bundledDependencies中的依赖必须在devDependencies或者dependencies中声明过。
  • peerDependencies: 指定当前组件的依赖以其版本。如果组件使用者在项目中安装了其他版本的同一依赖,会提示报错。
  • engines:指定项目所依赖的node环境、npm版本等。
  • private:如果设为true,无法通过npm publish发布代码。
  • bin:用来指定各个内部命令对应的可执行文件的路径。具体用法这里不多讲了。详情可以点击这里

npm install

  • 根据配置文件package.json来安装环境
  • 会在当前路径下自动创建文件夹node_modules,里面放所有下载的依赖包

npm install name --save

  • 下载包name,同时加载到配置文件package.json的依赖"dependencies"里面

教程

语法

数据类型

Number

1
2
3
4
5
6
123; // 整数123
0.456; // 浮点数0.456
1.2345e3; // 科学计数法表示1.2345x1000,等同于1234.5
-99; // 负数
NaN; // NaN表示Not a Number,当无法计算结果时用NaN表示
Infinity; // Infinity表示无限大,当数值超过了JavaScript的Number所能表示的最大值时,就表示为Infinity
  • JavaScript不区分整数和浮点数,统一用Number表示,以上都是合法的Number类型
  • Number的四则运算同数学
  • 注意
    • NaN这个特殊的Number与所有其他值都不相等,包括它自己:
      • NaN === NaN //false
      • isNaN(NaN); //true

字符串

大小写转换

1
2
3
str = 'User';
lowStr = str.toLowerCase(); // 'user'
upStr = str.toUpperCase(); // 'USER'

比较运算符

  • ==:它会自动转换数据类型再比较,很多时候,会得到非常诡异的结果;
  • ===:它不会自动转换数据类型,如果数据类型不一致,返回false,如果一致,再比较。
    • 由于JavaScript这个设计缺陷,不要使用==比较,始终坚持使用===比较。

null和undefined:

  • null表示一个“空”的值,它和0以及空字符串''不同,0是一个数值,''表示长度为0的字符串,而null表示“空”。
  • 在其他语言中,也有类似JavaScript的null的表示,例如Java也用null,Swift用nil,Python用None表示。但是,在JavaScript中,还有一个和null类似的undefined,它表示“未定义”。
  • JavaScript的设计者希望用null表示一个空的值,而undefined表示值未定义。事实证明,这并没有什么卵用,区分两者的意义不大。
  • 大多数情况下,我们都应该用nullundefined仅仅在判断函数参数是否传递的情况下有用。

数组

对象

strict模式

  • JavaScript在设计之初,为了方便初学者学习,并不强制要求用var申明变量。这个设计错误带来了严重的后果:如果一个变量没有通过var申明就被使用,那么该变量就自动被申明为全局变量
  • 为了修补JavaScript这一严重设计缺陷,ECMA在后续规范中推出了strict模式,在strict模式下运行的JavaScript代码,强制通过var申明变量,未使用var申明变量就使用的,将导致运行错误。

容器

Map
Set
Iterable

变量

  • 变量名是大小写英文、数字、$_的组合,且不能用数字开头。
  • 变量名也不能是JavaScript的关键字,如ifwhile等。
  • 申明一个变量用var语句

变量输出

  • console.log(x)
  • alert(x) –html弹窗

环境变量 process.env

1
2
3
var http_port = process.env.HTTP_PORT || 3001   //默认设置为3001
var p2p_port = process.env.P2P_PORT || 6001
var initialPeers = process.env.PEERS ? process.env.PEERS.split(',') : []
  • process:是一个 global (全局变量),提供有关信息,控制当前 Node.js 进程。
    作为一个对象,它对于 Node.js 应用程序始终是可用的,故无需使用 require()。

    • process(进程)其实就是存在nodejs中的一个全局变量,所有模块都可以调用。
  • 终端设置变量值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //win
    $ set HTTP_PORT=3002
    $ set P2P_PORT=6002
    $ set PEERS=ws://localhost:6001

    //ios
    $ export HTTP_PORT=3002
    $ export P2P_PORT=6002
    $ export PEERS=ws://localhost:6001

    //启动
    $ node file.js
    • ios

标准对象

  • 在JavaScript的世界里,一切都是对象。
  • 但是某些对象还是和其他对象不太一样。为了区分对象的类型,我们用typeof操作符获取对象的类型,它总是返回一个字符串
1
2
3
4
5
6
7
8
9
typeof 123; // 'number'
typeof NaN; // 'number'
typeof 'str'; // 'string'
typeof true; // 'boolean'
typeof undefined; // 'undefined'
typeof Math.abs; // 'function'
typeof null; // 'object'
typeof []; // 'object'
typeof {}; // 'object'
  • numberstringbooleanfunctionundefined有别于其他类型。
  • 特别注意null的类型是objectArray的类型也是object,如果我们用typeof将无法区分出nullArray和通常意义上的object——{}

包装对象

1
2
3
var n = new Number(123); // 123,生成了新的包装类型
var b = new Boolean(true); // true,生成了新的包装类型
var s = new String('str'); // 'str',生成了新的包装类型
  • 类型为object

总结一下,有这么几条规则需要遵守:

  • 不要使用new Number()new Boolean()new String()创建包装对象;

    • 如果没写newNumber()BooleanString()被当做普通函数,把任何类型的数据转换为numberbooleanstring类型(注意不是其包装类型)
  • parseInt()parseFloat()来转换任意类型到number

  • String()来转换任意类型到string,或者直接调用某个对象的toString()方法;

    • nullundefined就没有toString()方法;Object一些情况下也不能使用

    • number对象调用toString()SyntaxError:

    • 123.toString();    // SyntaxError
      123..toString();   // '123', 注意是两个点!
      (123).toString();  // '123'
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64

      - 通常不必把任意类型转换为`boolean`再判断,因为可以直接写`if (myVar) {...}`;

      - `typeof`操作符可以判断出`number`、`boolean`、`string`、`function`和`undefined`;

      - 判断`Array`要使用`Array.isArray(arr)`;

      - 判断`null`请使用`myVar === null`;

      - 判断某个全局变量是否存在用`typeof window.myVar === 'undefined'`;

      - 函数内部判断某个变量是否存在用`typeof myVar === 'undefined'`。



      ### Date





      ### RegExp







      ### JSON

      * JSON实际上是JavaScript的一个子集。
      * JSON字符集必须是UTF-8
      * 为了统一解析JSON的字符串规定必须用双引号`""`,Object的键也必须用双引号`""`。
      * 在JSON中,一共就这么几种数据类型:
      1. **number**:和JavaScript的`number`完全一致;
      2. **boolean**:就是JavaScript的`true`或`false`;
      3. **string**:就是JavaScript的`string`;
      4. **nul**l:就是JavaScript的`null`;
      5. **array**:就是JavaScript的`Array`表示方式——`[]`;
      6. **object**:就是JavaScript的`{ ... }`表示方式。



      **序列化:** 对象 -> JSON

      * `JSON.stringfy(var, null, ' ')`:将nodejs对象输出成标准JSON格式

      * 第二个参数用于控制如何筛选对象的键值,如果我们只想输出指定的属性,可以传入`Array`

      * `JSON.stringify(xiaoming, ['name', 'skills'], ''); `
      * 只输出对象 xiaoming 的 name 和 skills 属性

      * 第二个参数还能是一个函数,这样对象的每个键值对都会被函数先处理,

      * ```javascript
      function convert(key, value) {
      if (typeof value === 'string') {
      return value.toUpperCase();
      }
      return value;
      }

      JSON.stringfy(xiaoming, convert, ' ');
    • 第三个参数控制按缩进输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var xiaoming = {
name: '小明',
age: 14,
gender: true,
height: 1.65,
grade: null,
'middle-school': '\"W3C\" Middle School',
skills: ['JavaScript', 'Java', 'Python', 'Lisp']
};

var s = JSON.stringify(xiaoming);
console.log(s);

// 输出结果
{"name":"小明","age":14,"gender":true,"height":1.65,"grade":null,"middle-school":"\"W3C\" Middle School","skills":["JavaScript","Java","Python","Lisp"]}
  • 如果我们还想要精确控制如何序列化小明,可以给xiaoming定义一个toJSON()的方法,直接返回JSON应该序列化的数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var xiaoming = {
name: '小明',
age: 14,
gender: true,
height: 1.65,
grade: null,
'middle-school': '\"W3C\" Middle School',
skills: ['JavaScript', 'Java', 'Python', 'Lisp'],
toJSON: function () {
return { // 只输出name和age,并且改变了key:
'Name': this.name,
'Age': this.age
};
}
};

JSON.stringify(xiaoming); // '{"Name":"小明","Age":14}'

反序列化: JSON -> 对象

  • JSON.parse():把JSON变成一个JavaScript对象
1
2
3
4
JSON.parse('[1,2,3,true]'); // [1, 2, 3, true]
JSON.parse('{"name":"小明","age":14}'); // Object {name: '小明', age: 14}
JSON.parse('true'); // true
JSON.parse('123.45'); // 123.45
  • JSON.parse()还可以接收一个函数,用来转换解析出的属性:
1
2
3
4
5
6
7
var obj = JSON.parse('{"name":"小明","age":14}', function (key, value) {
if (key === 'name') {
return value + '同学';
}
return value;
});
console.log(JSON.stringify(obj)); // {name: '小明同学', age: 14}

面向对象

构造

1
2
3
4
5
6
7
8
class ClassName {
constructor(){
this.type = 'class';
}
func(){
console.log("this is a func");
}
}

继承

1
2
3
4
5
6
7
8
9
10
class Son extends Father{
constructor(name){
super(); // 调用 father 的类构造函数
this.name = name;
}

func(){
console.log("this is a son func");
}
}

实例化

1
var son = new Son('cindy');

use strict

  • 在第一行加上 'use strict' 表示启用严格模式
    • ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";
  • 主要限制:
    • 变量必须声明后再使用
    • 函数的参数不能有同名属性,否则报错
    • 不能使用with语句
    • 不能对只读属性赋值,否则报错
    • 不能使用前缀0表示八进制数,否则报错
    • 不能删除不可删除的属性,否则报错
    • 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
    • eval不会在它的外层作用域引入变量
    • evalarguments不能被重新赋值
    • arguments不会自动反映函数参数的变化
    • 不能使用arguments.callee
    • 不能使用arguments.caller
    • 禁止this指向全局对象
    • 不能使用fn.callerfn.arguments获取函数调用的堆栈
    • 增加了保留字(比如protectedstaticinterface

模块

module

用来提供nodejs的模块化功能

1
2
3
4
5
6
7
8
9
10
11
12
/* a.js */
module.exports = function() {
console.log('My name is Lemmy Kilmister');
};
// 或者
exports = function() {
console.log('My name is Lemmy Kilmister');
};

/* b.js */
const a = require('a.js');
a(); // My name is Lemmy Kilmister
  • 所有的exports收集到的属性和方法,都赋值给了**Module.exports。当然,这有个前提,就是Module.exports本身不具备任何属性和方法。如果,Module.exports已经具备一些属性和方法,那么exports**收集来的信息将被忽略.

module.exports

模块规范

CommonJS

  • Node应用由模块组成,采用CommonJS模块规范。
  • 根据这个规范,每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
  • CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。
  • 下面代码通过module.exports输出变量x和函数addX。
1
2
3
4
5
6
var x = 5;
var addX = function (value) {
return value + x;
};
module.exports.x = x;
module.exports.addX = addX;
  • require方法用于加载模块。
1
2
3
4
var example = require('./example.js');

console.log(example.x); // 5
console.log(example.addX(1)); // 6
  • exports 与 module.exports
    • 为了方便,Node为每个模块提供一个exports变量,指向module.exports。这等同在每个模块头部,有一行这样的命令。
    • var exports = module.exports;
    • 于是我们可以直接在 exports 对象上添加方法,表示对外输出的接口,如同在module.exports 上添加一样。注意,不能直接将exports变量指向一个值,因为这样等于切断了exports与module.exports的联系。

ES6

  • 不同于CommonJS,ES6使用 export 和 import 来导出、导入模块。
1
2
3
4
5
6
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export {firstName, lastName, year};
  • 需要特别注意的是,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
1
2
3
4
5
6
7
8
9
10
// 写法一
export var m = 1;

// 写法二
var m = 1;
export {m};

// 写法三
var n = 1;
export {n as m};

export default

  • 使用export default命令,为模块指定默认输出。
1
2
3
4
// export-default.js
export default function () {
console.log('foo');
}

fs

  • 文件系统模块,负责读写文件
  • 和所有其它JavaScript模块不同的是,fs模块同时提供了异步和同步的方法。
  • 参考

异步读文件

1
2
3
4
5
6
7
fs.readFile('sample.txt', 'utf-8', function (err, data) {
if (err) {
console.log(err);
} else {
console.log(data);
}
});
  • 异步读取时,传入的回调函数接收两个参数,当正常读取时,err参数为nulldata参数为读取到的String。当读取发生错误时,err参数代表一个错误对象,dataundefined
  • 这也是Node.js标准的回调函数:第一个参数代表错误信息,第二个参数代表结果。后面我们还会经常编写这种回调函数。

读取图片

1
2
3
4
5
6
7
8
fs.readFile('sample.png', function (err, data) {
if (err) {
console.log(err);
} else {
console.log(data);
console.log(data.length + ' bytes');
}
});
  • 当读取二进制文件时,不传入文件编码时,回调函数的data参数将返回一个Buffer对象。在Node.js中,Buffer对象就是一个包含零个或任意个字节的数组(注意和Array不同)。
    • Buffer对象可以和String作转换,例如,把一个Buffer对象转换成String:
1
2
3
// Buffer -> String
var text = data.toString('utf-8');
console.log(text);
  • 或者把一个String转换成Buffer
1
2
3
// String -> Buffer
var buf = Buffer.from(text, 'utf-8');
console.log(buf);

同步读文件

  • 同步读取的函数和异步函数相比,多了一个Sync后缀,并且不接收回调函数,函数直接返回结果。
1
2
3
4
var fs = require('fs');

var data = fs.readFileSync('sample.txt', 'utf-8');
console.log(data);
  • 如果同步读取文件发生错误,则需要用try...catch捕获该错误:
1
2
3
4
5
6
try {
var data = fs.readFileSync('sample.txt', 'utf-8');
console.log(data);
} catch (err) {
// 出错了
}

path

path 模块的默认操作会因 Node.js 应用程序运行所在的操作系统而异。 具体来说,当在 Windows 操作系统上运行时, path 模块会假定正被使用的是 Windows 风格的路径。

path.dirname()

path.join([…paths])

path.join() 方法会将所有给定的 path 片段连接到一起(使用平台特定的分隔符作为定界符),然后规范化生成的路径。

1
2
3
4
5
6
const path = require('path');
path.join('/目录1', '目录2', '目录3/目录4', '目录5', '..');
// 返回: '/目录1/目录2/目录3/目录4'

path.join('目录1', {}, '目录2');
// 抛出 'TypeError: Path must be a string. Received {}'
  • 参数
    • **[…paths]**:路径片段的序列。
    • 返回:string
  • 如果任何的路径片段不是字符串,则抛出 TypeError
  • 长度为零的 path 片段会被忽略。 如果连接后的路径字符串为长度为零的字符串,则返回 '.',表示当前工作目录。

path.resolve([…paths])

path.resolve() 方法会将路径或路径片段的序列解析为绝对路径。

给定的路径序列会从右到左进行处理,后面的每个 path 会被追加到前面,直到构造出绝对路径

  • 例如,给定的路径片段序列:[/目录1、/目录2、目录3],调用 path.resolve('/目录1', '/目录2', '目录3') 会返回 /目录2/目录3,因为 '目录3' 不是绝对路径,但 '/目录2' + '/' + '目录3' 是。
1
2
3
4
5
6
7
8
9
10
const path = require('path');
path.resolve('/目录1/目录2', './目录3');
// 返回: '/目录1/目录2/目录3'

path.resolve(__dirname, '/目录3/目录4/');
// 返回: '当前目录路径/目录3/目录4'

path.resolve('目录1', '目录2/目录3/', '../目录4/文件.gif');
// 如果当前工作目录是 /目录A/目录B,
// 则返回 '/目录A/目录B/目录1/目录2/目录4/文件.gif'
  • 参数:
    • **[…paths]**:路径片段的序列。
    • 返回:string
  • 如果在处理完所有给定的 path 片段之后还未生成绝对路径,则会使用当前工作目录。
  • 生成的路径会被规范化,并且尾部的斜杠会被删除(除非路径被解析为根目录)。
  • 零长度的 path 片段会被忽略。
  • 如果没有传入 path 片段,则 path.resolve() 会返回当前工作目录的绝对路径。

path.relate(from, to)

path.relative() 方法根据当前工作目录返回 fromto 的相对路径。

  • 如果 fromto 各自解析到相同的路径(分别调用 path.resolve() 之后),则返回零长度的字符串。

  • 如果将零长度的字符串传入 fromto,则使用当前工作目录代替该零长度的字符串。

1
2
3
4
5
6
7
/* POSIX上 */
path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb');
// 返回: '../../impl/bbb'

/* Windows上 */
path.relative('C:\\orandea\\test\\aaa', 'C:\\orandea\\impl\\bbb');
// 返回: '..\\..\\impl\\bbb'
  • 参数:
    • from:string
    • to:string
    • 返回:string
  • 如果 fromto 不是字符串,则抛出 TypeError

stream

http

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// 导入http模块:
var http = require('http');

// 创建http server,并传入回调函数:
var server = http.createServer(function(req, res){
if(req.method == "GET"){
doGet(req, res)
}else if(req.method == "POST"){
doPost(req, res)
}else{
res.end();
}
});
// 让服务器监听8080端口:
server.listen(8080);

function doGet(request, response){
// 回调函数接收request和response对象,
// 获得HTTP请求的method和url:
console.log(request.method + ': ' + request.url);
// 解析请求的Url,返回一个标准的对象
var params = url.parse(request.url, true);

// 向http请求返回一个表单
// 协议头,将HTTP响应200写入response, 同时设置Content-Type: text/html:
response.writeHead(200, {'Content-Type': 'text/html'});
response.write('<html>');
response.write('<head>');
response.write('<meta charset="utf-8">');
response.write('<title>');
response.write('</title>');
response.write('</head>');
response.write('<body>');
response.write('<form method="post">'); //表单post输出传入下面的函数
response.write('username:<input name="username">');
response.write('password:<input name="password"><input type="submit">');
response.write('</form>');
response.write('</body>');
response.write('</html>');
// 将HTTP响应的HTML内容写入response:
response.end()
});

function doPost(req, res){
var formDate = '';

// 通过req的data事件监听函数,每当接受到请求体的数据,就累加到formDate变量中
req.on('data', function(data){
formDate += data;
console.log(data);
//<Buffer 75 73 65 72 6e 61 6d 65 3d 25 45 39 25 41 44 25 38 46 25 45 36 25 42 34 25 38 31 25 45 36 25 39 44 25 41 38 26 70 61 73 73 77 6f 72 64 3d 34 35 36 37>
})

// 在end事件触发后,通过querystring.parse将formDate解析为真正的POST请求格式,然后向客户端返回。
req.on('end', function(){
var body = querystring.parse(formDate);
res.end(util.inspect(body));
// [Object: null prototype] { username: 'wjy', password: '456' }
})
}

console.log('Server is running at http://127.0.0.1:8080/');
  • request:封装了HTTP请求,我们调用request对象的属性和方法就可以拿到所有HTTP请求的信息;
    • {‘Content-Type’: ‘text/html’}:设置类型为html页面
    • 方法
      • req.on('data', function(data){}):data为一个buffer数据,需要将所有数据包的字节都存储起来,再来分析
      • req.on('end', function(data){ res.end(); }):在出发结束的函数后,再在他的function里面结束相应res.end();
  • response:封装了HTTP响应,我们操作response对象的方法,就可以把HTTP响应返回给浏览器。
    • 方法
      • response.writeHead():设置http协议头,给浏览器看的
      • response.write():仅发送数据
      • response.end():发送并结束请求,必须有,可以发送空

编码模式

  • 浏览器默认urlencode字符集编码,不支持中文
    • 接收浏览器的请求数据res,在nodejs中输出是中文适配的
    • 但是如果直接将中文传到浏览器输出将会出现乱码,所以设置html页面属性meta为utf-8
1
2
3
4
5
res.write('<head><meta charset="utf-8"/></head>');

------ urlcode 加解码 ------
encodeURI("魏洁杨"); // %E9%AD%8F%E6%B4%81%E6%9D%A8
decodeURI("%E9%AD%8F%E6%B4%81%E6%9D%A8") //魏洁杨

请求内容

  • GET:
    • 由于GET请求直接被嵌入在路径中,URL是完整的请求路径,包括了?后面的部分,因此你可以手动解析后面的内容作为GET请求的参数。
    • 下面 url 模块中的 parse函数提供了这个功能。
  • POST:
    • POST 请求的内容全部的都在请求体中,http.ServerRequest 并没有一个属性内容为请求体,原因是等待请求体传输可能是一件耗时的工作。
    • 比如上传文件,而很多时候我们可能并不需要理会请求体的内容,恶意的POST请求会大大消耗服务器的资源,所以 node.js 默认是不会解析请求体的,当你需要的时候,需要手动来做。
    • 下面的querystring和util模块提供了相应的处理

url

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var url = require('url');

console.log(url.parse('http://user:pass@host.com:8080/path/to/file?query=string#hash'));

/* 输出 */
Url {
protocol: 'http:',
slashes: true,
auth: 'user:pass',
host: 'host.com:8080',
port: '8080',
hostname: 'host.com',
hash: '#hash',
search: '?query=string',
query: 'query=string',
pathname: '/path/to/file',
path: '/path/to/file?query=string',
href: 'http://user:pass@host.com:8080/path/to/file?query=string#hash' }

querystring

模块提供用于解析和格式化 URL 查询字符串的实用工具

1
2
3
4
5
6
7
8
9
10
11
var querystring = require('querystring');
/*
{ unescapeBuffer: [Function],
unescape: [Function: qsUnescape],
escape: [Function],
encode: [Function],
stringify: [Function],
decode: [Function],
parse: [Function] }
*/
console.log(querystring);

序列化:url -> 对象

querystring.parse(str[, sep[, eq[, options]]])

  • 把一个URL查询字符串(str)解析成一个键值对的集合

  • 参数

    • str:要解析的 URL 查询字符串

    • sep:用于在查询字符串中分隔键值对的子字符串。默认值: '&'

    • eq:用于在查询字符串中分隔键和值的子字符串。默认值: '='

    • Options

      • decodeURIComponent 当解码查询字符串中的百分比编码字符时使用的函数。默认值: querystring.unescape()
      • maxKeys 指定要解析的键的最大数量。指定 0 可移除键的计数限制。默认值: 1000
      • 注:默认情况下,会假定查询字符串中的百分比编码字符使用 UTF-8 编码。 如果使用其他的字符编码,则需要指定其他的 decodeURIComponent 选项
      1
      2
      3
      4
      5
      6
      var str = "foo=bar&abc=xyz&abc=123"
      var body = querystring.parse(str)
      //body = '{ foo: 'bar', abc: [ 'xyz', '123' ] }'

      querystring.parse('w=%D6%D0%CE%C4&foo=bar', null, null,
      { decodeURIComponent: gbkDecodeURIComponent });
  • 注意:本方法返回的对象不继承自 JavaScript 的 Object。 这意味着典型的 Object 方法如 obj.toString()obj.hasOwnProperty()等没有被定义且无法使用

反序列化:对象 -> url

querystring.stringify(obj[, sep][, eq][, options])

  • 通过遍历对象的自有属性,从一个给定的obj产生一个URL查询字符串

  • 参数:

    • obj:要序列化为 URL 查询字符串的对象。

    • sep:用于在查询字符串中分隔键值对的子字符串。默认值: '&'

    • eq:用于在查询字符串中分隔键和值的子字符串。默认值: '='

    • options

      • decodeURIComponent 当解码查询字符串中的百分比编码字符时使用的函数。默认值: querystring.unescape()
      1
      2
      3
      4
      5
      querystring.stringify({ foo: 'bar', baz: ['qux', 'quux'], corge: '' });
      // 返回 'foo=bar&baz=qux&baz=quux&corge='

      querystring.stringify({ foo: 'bar', baz: 'qux' }, ';', ':');
      // 返回 'foo:bar;baz:qux'

编码

querystring.escape(str)

querystring.unescape(str)

  • 给定的 str 上执行 URL 百分比编码字符的解码

  • querystring.unescape() 方法由 querystring.parse() 使用,通常不会被直接地使用。 它的导出主要是为了允许应用程序代码在需要时通过将 querystring.unescape 赋值给替代函数来提供替换的解码实现。

    默认情况下, querystring.unescape() 方法将会尝试使用 JavaScript 内置的 decodeURIComponent() 方法进行解码。 如果失败,则将会使用更保险的不会因格式错误的 URL 而抛出异常的同类方法。

body-parser

socket.io

crypto

crypto.createHash(algorithm, key[, options])

node-rsa

API

密钥格式:

  • 密钥内容标准规范:PKCS(The Public-Key Cryptography Standards)

    • 由美国RSA公司及其合作伙伴制定的一组公钥密码学标准,其中包括证书申请、证书更新、证书作废表发布、扩展证书内容以及数字签名、数字信封的格式等方面的一系列相关协议。
    • 版本:现有PCKS1,PCKS3,PCKS5,PKCS6,PKCS7,PKCS8,PKCS9,PKCS10,PKCS11,PKCS12,PKCS13,PKCS14,PKCS15 共 13个版本
      常用:PCKS1 和 PCKS8,本文使用 PCKS8 标准
  • 密钥展示与传输格式

    • DER:基于二进制的编码。可以用CER或者CRT作为扩展名的的整数。比较合适的说法是“我有一个DER编码的证书”,而不是“我有一个DER证书”。

    • PEM:基于ASCII(Base64)的编码。OpenSSL 使用 PEM 文件格式存储证书和密钥。

      • 实质上是 base64 编码的二进制内容,再进行增加或裁剪特殊字符-、\n、\r、begin信息、end信息等,如:

      • -----BEGIN CERTIFICATE-----
        内容
        -----END CERTIFICATE-----
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        *







        ```javascript
        var NodeRSA = require('node-rsa');
        const key = new NodeRSA([keyData, [format]], [options]);
  • keyData { string|buffer|object }:用于生成密钥或所支持格式之一的密钥的参数。

  • format { string }:导入密钥的格式。

  • options { object }:其他设置。

导入导出密钥

  • key.importKey(keyData, [format]);
  • key.exportKey([format]);
1
2
3
4
5
6
7
var key = new NodeRSA({b: 512});  //生成512位秘钥
key.setOptions({ encryptionScheme: 'pkcs1' }); //指定加密格式
var pubkey = key.exportKey('pkcs8-public'); //导出公钥
var prikey = key.exportKey('pkcs8-private'); //导出私钥

var publicKey = new NodeRSA(pubKey,'pkcs8-public'); //导入公钥
var privateKey = new NodeRSA(priKey,'pkcs8-private'); //导入私钥
  • format:指定rsa算法标准
    • pkcs8-publicpkcs1-public-pem

加密解密

  • default:
    • 公钥加密 key.encrypt(buffer, [encoding], [source_encoding]); <==>
    • 私钥解密 key.decrypt(buffer, [encoding]);
  • extend:
    • 私钥加密 key.encryptPrivate(buffer, [encoding], [source_encoding]); <==>
    • 公钥解密 key.decryptPublic(buffer, [encoding]);
1
2
3
4
5
6
7
/* 使用公钥对 buffer 进行加密 */
pubKey = new NodeRSA(publicKey,'pkcs8-public');
var encrypted = pubKey.encrypt(buffer, 'base64');

/* 使用私钥对 buffer加密后的秘文 encrypte 进行解密 */
priKey = new NodeRSA(privateKey,'pkcs8-private');
var decrypted = priKey.decrypt(encrypted, 'utf8');
  • 一般默认使用公钥加密,用私钥来解密,所以内置了encryptdecrypt函数
1
2
3
4
5
6
7
/* 使用私钥对 buffer 进行加密 */
priKey = new NodeRSA(privateKey,'pkcs8-public');
var encrypted = priKey.encryptPrivate(buffer, 'base64');

/* 使用公钥对 buffer加密后的秘文 encrypte 进行解密 */
pubKey = new NodeRSA(publicKey,'pkcs8-private');
var decrypted = pubKey.decryptPublic(encrypted, 'utf8');
  • 但也可以像上面这样反过来,使用encryptPrivate私钥加密,和decryptPublic公钥解密

签名验证

  • key.sign(buffer, [encoding], [source_encoding]);
  • key.verify(buffer, signature, [source_encoding], [signature_encoding])
1
2
3
4
5
6
7
/* 私钥签名*/
priKey = new NodeRSA(privateKey,'pkcs8-private');
var signature = priKey.sign(buffer);

/* 公钥验证 */
pubKey = new NodeRSA(publicKey,'pkcs8-public');
var flag = pubKey.verify(buffer, signature);
  • buffer:{buffer} — 用于检查的数据,与encrypt方法相同。
  • signature:{string} — 检查的签名,sign方法的结果。
  • source_encoding:{string} — 与encryptmethod相同。
  • signature_encoding:{string} — 给定签名的编码。可能是'buffer', 'binary', 'hex''base64'。默认为 'buffer'.

其他

1
2
3
4
5
6
7
8
9
10
11
var priKey = new NodeRSA(privateKey,'pkcs8-private');

priKey.getKeySize(); // 512;
priKey.getMaxMessageSize(); // 22

priKey.isPrivate(); // true
priKey.isPublic(); // true
priKey.isPublic(true); // false

pubKey.isPublic(false); // true
pubKey.isPublic(true); // true
  • isPrivate():判断是否是私钥,是的话返回 true
  • isPublic([strict]):判断是否是公钥
    • strict 只在私钥是有效:为 false 时(默认),与判断私钥相同,为 true 时才判断是否为公钥。

时间

Date()

1
2
3
const time = new Date();
//
new Date(ms);

silly-datetime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var sd = require('silly-datetime');

/* silly-datetime 当前时间格式化 */
console.log(sd.format(new Date(), 'YYYY-MM-DD HH:mm'));
// 2021-04-18 12:18
console.log(sd.format(new Date(), 'YYYY-MM-DD HH:mm:ss'));
// 2021-04-18 12:18:04


/* 通过时间戳拼接 格式化时间 */
var date = new Date((new Date()).getTime())
Y = date.getFullYear() + '-';
M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-';
D = (date.getDate() < 10 ? '0'+date.getDate() : date.getDate()) + ' ';
h = (date.getHours() < 10 ? '0'+date.getHours() : date.getHours()) + ':';
m = (date.getMinutes() < 10 ? '0'+date.getMinutes() : date.getMinutes()) + ':';
s = (date.getSeconds() < 10 ? '0'+date.getSeconds() : date.getSeconds());
console.log(Y+M+D+h+m+s);
// 2021-04-18 12:18:04


/* 获取当前时间戳 */
console.log((new Date()).getTime());
// 1618719753004
console.log((new Date()).valueOf());
// 1618719753007
console.log(Date.parse(new Date()));
// 1618719753000

/* 将指定时间转化成时间戳 */
var newDate = new Date('2021-04-18 12:25:13');
console.log(newDate.getTime());
// 1618719913000

moment

  1. 日期格式化
1
2
3
4
5
6
7
8
9
10
11
const moment = require('moment');
console.log(moment().format('MMMM Do YYYY, h:mm:ss a'));
// April 18th 2021, 12:29:11 pm
console.log(moment().format('dddd'));
// Sunday
console.log(moment().format("MMM Do YY"));
// Apr 18th 21
console.log(moment().format('YYYY [escaped] YYYY'));
// 2021 escaped 2021
console.log(moment().format());
// 2021-04-18T12:29:11+08:00
  1. 相对时间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 当前时间 */
console.log(moment().format()); // 2021-04-18T12:32:11+08:00

/* 相对时间 */
console.log(moment("20111031", "YYYYMMDD").fromNow());
// 9 years ago
console.log(moment("20120620", "YYYYMMDD").fromNow());
// 9 years ago
console.log(moment().startOf('day').fromNow());
// 13 hours ago
console.log(moment().endOf('day').fromNow());
// in 11 hours
console.log(moment().startOf('hour').fromNow());
// 31 minutes ago
  1. 日历时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 当前时间 */
console.log(moment().format()); // 2021-04-18T12:35:03+08:00

/* 日历时间 */
console.log(moment().subtract(10, 'days').calendar());
// 04/08/2021
console.log(moment().subtract(6, 'days').calendar());
// Last Monday at 12:35 PM
console.log(moment().subtract(3, 'days').calendar());
// Last Thursday at 12:35 PM
console.log(moment().subtract(1, 'days').calendar());
// Yesterday at 12:35 PM
console.log(moment().calendar());
// Today at 12:35 PM
console.log(moment().add(1, 'days').calendar());
// Tomorrow at 12:35 PM
console.log(moment().add(3, 'days').calendar());
// Wednesday at 12:35 PM
console.log(moment().add(10, 'days').calendar());
// Wednesday at 12:35 PM

其他更多的函数参考:http://momentjs.cn/docs/

  1. 示例
    计算俩日期相差多少天
1
2
3
4
5
6
// 获取当前时间
let m1 = moment();
// 获取需要对比的时间
let m2 = moment(time);
// 计算相差多少天 day可以是second minute
day = m2.diff(m1, 'day');

当前日期往后加几天

1
moment().add(30, "days").format('YYYY-MM-DD HH:mm:ss')

指定日期往后加几天

1
moment("传入时间戳").add(30, "days").format('YYYY-MM-DD HH:mm:ss')
  1. clone

所有的 moment 都是可变的。 如果想要克隆 moment,则可以隐式或显式地操作。

在 moment 上调用 moment() 将会克隆它。

1
2
3
4
var a = moment([2012]);
var b = a.clone();
a.year(2000);
b.year(); // 2021

同步异步

async-await

Async/await的主要益处是可以避免回调地狱(callback hell),且以最接近同步代码的方式编写异步代码。

基本概念

  • promise 对象:
    • 有三种状态:成功(Fulfilled)、失败(Rejected)、等待(Pending)
    • 不配合<async, await>时,使用 .then().catch() 处理成功失败情况是目前的常规方案。
  • async 表示这是一个async函数,await只能用在这个函数里面。async 对象也是一个 promise 对象。
  • await 表示在这里等待 promise 返回结果了,再继续执行。
  • await 后面跟着的应该是一个 promise 对象(当然,其他返回值也没关系,不过那样就没有意义了…)
  • 很多库的接口返回 promise 对象,await 后赋值给一个变量后使用其 resolve 的值。例如
  • 注意三点,promise 对象的状态,promise 对象上的方法(then,catch),promise 对象返回的值。
  • promise 是当时为了解决回调地狱的解决方案,也是当前处理异步操作最流行和广泛使用的方案,async 和 await 最为当前的终极方案两只之间还有一些过渡方案。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
var showArticle = async function () {
await new Promise(function (resolve, reject) {
PostModel.incPv(postId, function (result) {
resolve(result);
});
});// pv 加 1

var post = await new Promise(function (resolve, reject) {
PostModel.getPostById(postId, function (article) {
resolve(article);
});
});// 获取文章信息,相当于 post = article

await new Promise(function (resolve, reject) {
userModel.getUserById(post.author,function (author) {
post.author=author;
resolve();
})
});//获取文章作者

var comments = await new Promise(function (resolve, reject) {
CommentModel.getComments(post._id, function (comment) {
resolve(comment);
});
});// 获取该文章所有留言

for(var i = 0; i < comments.length; i++){
await new Promise(function (resolve, reject) {
userModel.getUserById(comments[i].author,function (author) {
comments[i].author=author;
resolve();
})
});//获取文章留言作者
}

if (!post) {
req.session.error = '该文章不存在';
return res.redirect('/post');
}

res.render('post',{post: post, comments: comments});
};
 
showArticle();

Promise

实现延时

https://zhuanlan.zhihu.com/p/163292394

  1. 使用 setTimeout 函数
1
2
3
4
5
function wait(ms) {
return new Promise(resolve => setTimeout(() => resolve(), ms));
};

await wait(3000); //延时3秒
  1. 使用for和 Date
1
2
3
4
5
6
7
// 函数实现,参数 delay 单位 毫秒 ;
function sleep(delay) {
for (var t = Date.now(); Date.now() - t <= delay;);
}

// 调用方法,同步执行,阻塞后续程序的执行;
sleep(5000);
  1. 在nodejs平台调用Linux系统自带的 sleep 函数
1
2
3
4
5
6
7
8
9
// 函数实现,参数单位 秒 ;
function wait(second) {
// execSync 属于同步方法;异步方式请根据需要自行查询 node.js 的 child_process 相关方法;
let ChildProcess_ExecSync = require('child_process').execSync;
ChildProcess_ExecSync('sleep ' + second);
};

// 调用方法;休眠 60 秒,即 1 分钟;
wait( 60 );

框架

express

server.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const express = require('express');
const bodyParser = require('body-parser');

var server = express();
server.listen(8088); // server 后端监听的端口

server.use(bodyParser.urlencoded({}));

/* localhost:8088/try [get类型请求,post类型将会到 server.post()]
* try后面跟的参数,将会存储为对象 req.params.id
*/
server.get('/try/:id', function (req, res) {
console.log('try');
res.send(`1111 ${req.params.id}`);
})
// localhost:8088/try/wjy 将会输出:
// try
// 1111 wjy

/* router 路径管理 */
server.use('/login', require('./router/login.js')());
// 访问 'localhost:8088/login' 将会定位到文件夹 router 下的 login.js文件

server.use('/home', require('./router/home')());
// 访问 'localhost:8088/home' 将会定位到整个文件夹 home 下

router/login.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const express = require('express');

function loadUser(req, res, next) {
// You would fetch your user from the db
var user = users[req.params.id];
if (user) {
req.user = user;
next();
} else {
next(new Error('Failed to load user ' + req.params.id));
}
}

function andRestrictToSelf(req, res, next) {
req.authenticatedUser.id == req.user.id
? next()
: next(new Error('Unauthorized'));
}

var responseData;
module.exports = function(){
var router = express.Router();
router.use('/', function(req, res, next){
responseData = { // 返回值标准化
code: '', // 状态吗
message: '' // 一般为json类型
}
next(); // 将会接着执行下面的函数
});

/* 同时处理多个函数 */
router.get('/user/:id/edit', loadUser, andRestrictToSelf, function(req, res){
res.send('Editing user ' + req.user.name);
});

return router;
}
  • 当用户访问:/user/gainover/edit 时,首先会调用第一个处理函数loadUser,判断用户是否存在于users中,如果不存在,通过 next(new Error(msg)); 的方式抛出异常,否则,执行next(),而next此时实际就是指向 andRestrictToSelf 函数,然后判断当前登录的id和被编辑的id是否等同,如果等同,则继续 next(),从而执行 res.send( ...);

koa

Fabric

(指路fabric篇)

实例

多人实时聊天服务器

HTTP

Socket

npm install socket.io --save

  • 网络上的程序实现双向的数据链接,这个链接的一端成为socket。

    1.Socket是一个持久链接。

    2.Socket是双向通信的。

Socket VS ajax轮询

  • ajax轮询 , 是利用客户端来发送请求,每隔几秒发送一个http请求,服务器压力大。
  • Socket不会,一旦链接不会断开,可以实现实时通信。 比如微信的朋友圈更新提示。即时聊天通讯。

p2p

  • Post title:nodejs
  • Post author:Wei Jieyang
  • Create time:2020-12-18 17:56:37
  • Post link:https://jieyang-wei.github.io/2020/12/18/nodejs/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.