安装
Linux
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
# 查看nvm版本
nvm -v
# 配置淘宝镜像 提升下载速度
export NVM_NODEJS_ORG_MIRROR=https://npm.taobao.org/mirrors/node/
# 查看node版本列表
nvm ls-remote
# 安装node 16.20.1版本
nvm install 16.20.1
#选择node版本
node use xxx
# 查看node版本
node -v
#现在node版本
nvm uninstall xxx
# 查看安装的所以版本
nvm ls
Node设置淘宝镜像
npm config set registry=https://registry.npm.taobao.org/
命令行接收参数
process对象 公开的argv属性
argv :
第一个参数: node命令完整地址
第二个参数:正在被执行的文件地址
其他参数从第三个开始
循环迭代
process.argv.forEach((val, index) => { console.log(`${index}: ${val}`) })数组
const args = process.argv.slice(2) //屏蔽掉前两个参数 //没有索引 const args = process.argv.slice(2) args[0] //索引 node app.js name=joe 引用minimist库 const args = require('minimist')(process.argv.slice(2)) args['name'] //joe
async和await
async异步执行函数,该函数的执行不会阻塞后面代码的执行
await等待,用于等待一个promise对象,只能在异步async中使用,否则会报错
返回一个promise对象处理完成的结果
await表达式会暂停当前async function的执行,等待Promise处理完成。若Promise正常处理(fulfilled),其回调的resolve函数参数作为await表达式的值,继续执行async function,若Promise处理异常(rejected),await表达式会把Promise的异常原因抛出。如果await操作符后的表达式的值不是一个Promise,那么该值将被转换为一个已正常处理的Promise。
const axios = require('axios');
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
// Promise方式
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => step2(time2))
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
// async await方式
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
//doIt();
commonjs模块规范
1.模块引用
require('math');
引入一个模块api到当前上下文中
2.模块定义
exports 导出当前模块中的方法,变量
module对象,代表模块自身,exports是module的属性,
module.exports 会覆盖exports导出的值,对象
3.模块标识
传递给require方法的参数,符合小驼峰命名的字符串,或者以.、..开头的相对路径,或者绝对路径,可以没有文件后缀.js
Node的模块实现
在node引入模块,需要经历的3个步骤
1.路径分析
2.文件定位
3.编译执行
在Node中,模块文件分为两类,Node提供的模块,称为核心模块,用户编写的成为文件模块
- 核心模块在Node源代码的编译过程中,编译未二进制文件。在文件启动时,部分核心模块被加载进内存中,所以这部分模块引入时,文件定位和编译执行可以省略掉,并且在路径分析中优先判断,所以它的加载速度时最快的
- 文件模块则时在运行时动态执行加载,需要完整路径分析,文件定位,编译执行过程,速度比核心模块慢
1.优先从缓存加载
Node会对引入过的模块进行缓存,以减少二次引入时的开销,缓存的时编译和执行过后的对象
不论时核心模块还是文件模块,require方法对相同模块的二次加载一律采用缓存优先的方式,这是第一优先级的,不同的是核心模块的缓存检查优于文件模块的缓存检查
2.路径分析和文件定位
不同的标识符在,模块查找,和定位上有不同程度的差异
1.模块标识符分析
require方法接受一个标识符作为参数,在Node实现中,正式基于这样一个标识符进行模块查找的。模块标识符在node中主要分为以下几类:
- 核心模块,如http,fs,path等
- .或.. 开始的相对路径文件和/开头的绝对路径文件模块
- 非路径形式的文件模块,如自定义的connect模块
核心模块
- 核心模块加载速度仅次于缓存加载,它在Node的源代码编译过程中已经被编译未二进制文件,其加载速度是最快的。
- 用户自定义标识符不能跟核心模块标识符相同,加载失败,如果需要加载,必须选择一个不同的标识符或者使用不同路径的方式
路径形式的文件模块
- 相对路径和绝对路径开始的标识符,会被当作文件模块来处理,在分析模块时,require方法会将路径转换成真实路径,以真实路径为索引,将编译执行后的结果存放到缓存中,以使二次加载时更快。
- 由于文件模块给Node指定了文件位置,所以查找过程中节省了大量时间,其加载时间满于核心模块
自定义模块
非核心模块,也不是以路径形式的标识符。它是一种特殊的文件模块,可能是一个文件或者包的形式。这类模块查找是最耗时的,也是所以方式中最慢的一种
模块路径
- 是Node在定位文件模块的具体文件时制定的查找策略。具体表现为一个路径组成的数组
- 当前文件目录下的node_modules目录
- 父目录下的node_modules目录
- 父目录的父目录下的node_modules目录
在加载过程中,node会逐个尝试模块中的路径,直到找到目标文件为止。文件路径越深,加载耗时越多,这个就是自定义模块加载速度慢的原因
2.文件定位
文件扩展名分析,目录和包的处理
- 文件扩展名分析
require在分析标识符的过程中,会出现标识符不包含文件扩展名的情况,CommonJS模块规范也允许在标识符中不包含文件的扩展名,这种情况下Node会按js,json,node的次序补足扩展名,依次尝试。
事件循环,调用栈
使用call apply bind 重新定义 this
var name = '小王', age = 17;
var obj = {
name: '小张',
objAge: this.age, // this 指向windows
myFun: function () {
console.log(this.name + "年龄" + this.age) // this 指向obj
}
}
var db = {
name: "EZ",
age: "999"
}
obj.myFun.call(db)
obj.myFun.apply(db)
obj.myFun.bind(db)() // 返回一个函数
// 传参
var name = '小王', age = 17;
var obj = {
name: '小张',
objAge: this.age, // this 指向windows
myFun: function (f, t) {
this.delete()
console.log(this.name + "年龄" + this.age, "f = " + f, "t = " + t) // this 指向obj
}
}
var db = {
name: "EZ",
age: "999",
delete:function(){
this.name = 'EZ1'
}
}
obj.myFun.call(db, '北京', '武汉') // EZ年龄999 f = 北京 t = 武汉
obj.myFun.apply(db, ['上海', '武汉']) // EZ年龄999 f = 上海 t = 武汉
obj.myFun.bind(db, '北京1', '武汉1')() // 返回一个函数 EZ年龄999 f = 北京1 t = 武汉1
obj.myFun.bind(db, ['上海', '武汉'])() // 返回一个函数 EZ年龄999 f = 上海,武汉 t = undefined
// call bind 参数用逗号作为分隔符 传参
// apply 参数使用数组
// bind 除了返回函数以为,参数和call一样
KOA
接受参数
get
ctx.request.query 获取request对象传入的值(get参数)
ctx.request.params 获取动态路径地址(url参数)
post
ctx.request.body 获取POST提交的参数
JOI
对前段传来的数据做 valication(合法性校验)
用法
Joi.validate(value, schema, [options]);
例:
router.post('/create', function (req, res, next) {
const schema = Joi.object().keys({
name: Joi.string().min(2).max(20).required(),
age: Joi.number().min(0).max(100).required(),
sex: Joi.string().valid(['男', '女']),
})
const result = Joi.validate({ name: '小明', age: 12, sex: "男" }, schema);
res.send(result);
});
返回:
{
"error": null,
"value": {
"name": "小明",
"age": 12,
"sex": "男"
}
}
result.error === null 为true后,result.value拿值
验证规则
基本验证规则:类型 / 长度范围 / 取值范围 / 是否必填 / 与其它字段的关系 / 默认值
类型
//任意类型 any()
//指定类型 array() boolean() binary() date() func() number() object() string()
类型下面还有子约束
//Requires the number to be an integer (no floating point).
Joi.number().integer(), 必须是整数
//Requires the string value to only contain a-z, A-Z, and 0-9.
Joi.string().alphanum()
Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
Joi.string().email()
长度范围
Joi.number().min(2) Joi.array().max(5)
取值范围
(1) valid - 白名单
可以用来实现枚举类型
a: Joi.any().valid('a'), b: Joi.any().valid('b', 'B'), c: Joi.any().valid(['c', 'C'])
(2) invalid - 黑名单
a: Joi.any().invalid('a'), b: Joi.any().invalid('b', 'B'), c: Joi.any().invalid(['c', 'C'])
(3) allow - 白名单的补充
a: Joi.any().allow('a'), b: Joi.any().allow('b', 'B'), c: Joi.any().allow(['c', 'C'])
是否必填
只对 undefined有效,null 会认为不合法
Joi.any().required()
const result = Joi.validate(undefined, Joi.string().required());
与其它字段的关系
(1) with / without / or
如现在有 a、b 两个字段:
Copy const schema = Joi.object().keys({
a: Joi.any(),
b: Joi.any()
}).with('a', 'b');
a.with('a', 'b') //a 和 b 必须都要填写
b.without('a', 'b'); //a 和 b 只能填写其中一个
c.or('a', 'b') //b 和 b 至少填写一个
(2) when
需求:验证条件是男人必须 50-100 岁,女人必须 0-50岁
Copy const schema = Joi.object().keys({
name: Joi.string().min(2).max(20).required(),
age: Joi.number().min(0).max(100).required().when('sex', {
is: '男',
then: Joi.number().min(50).max(100),
otherwise: Joi.number().min(0).max(50),
}),
sex: Joi.string().valid(['男', '女']),
})
const result = Joi.validate({ name: '小明', age: 60, sex: "女" }, schema);
return:
Copy{
"error": {
"isJoi": true,
"name": "ValidationError",
"details": [
{
"message": "\"age\" must be less than or equal to 50",
"path": [
"age"
],
"type": "number.max",
"context": {
"limit": 50,
"value": 60,
"key": "age",
"label": "age"
}
}
],
"_object": {
"name": "小明",
"age": 60,
"sex": "女"
}
},
"value": {
"name": "小明",
"age": 60,
"sex": "女"
}
}
默认值
default()
const result = Joi.validate(undefined, Joi.string().default("空"));
return:
{
"error": null,
"value": "空"
}
某个字段添加多个约束
验证条件为即可是string值也可是number值:
const result = Joi.validate(23, [Joi.string(), Joi.number()]);
对多余传进来的变量不要理会
options参数加上
const schema = Joi.object().keys({
name: Joi.string().min(2).max(20),
age: Joi.number().min(0).max(100).required(),
sex: Joi.string().valid(['男', '女']),
})
const result = Joi.validate({ name: '小明', age: 12, sex: "男", hometown: "上海" }, schema, { allowUnknown: true });
return:
{
"error": null,
"value": {
"name": "小明",
"age": 12,
"sex": "男",
"hometown": "上海"
}
}
value 里会保留多传的 hometown 字段
基本使用案例
常用方法
let paramSchema = Joi.object().keys({
// 3 - 30 个 数字、字符
username: Joi.string().alphanum().min(3).max(30).required(),
// 3 - 30 位 字母数字组合密码
password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
// string || number 都可以通过
access_token: [Joi.string(), Joi.number()],
// 生日限制
birthyear: Joi.number().integer().min(1900).max(2018),
// email 限制
email: Joi.string().email(),
// URI限制
website: Joi.string().uri({ scheme: [ 'git', /git+https?/ ] }),
// ==== 允许为空/ 否认不允许为空 ====
search: Joi.string().allow(''),
// 验证枚举值,如果不传,默认为all
type: Joi.string().valid('disabled', 'normal', 'all').default('all'),
// 开始时间 会自动格式化
startTime: Joi.date().min('1-1-1974').max('now'),
// 结束时间 必须大于开始时间,小于2100-1-1
endTime: Joi.when(Joi.ref('startTime'), { is: Joi.date().required(), then: Joi.date().max('1-1-2100') }),
// 页码 限制最小值
page: Joi.number().integer().min(1).default(1), pageSize: Joi.number().integer().default(8),
// deleteWhenLtTen: Joi.number().integer().max(10).strip(),
// 数组中包含某个字段 && 数字
arrayString: Joi.array().items(
// 数组中必须包含 name1
Joi.string().label('name1').required(),
// 数组中必须包含 数字
Joi.number().required(),
// 除掉【以上类型的以外字段】---数组中可以包含其他类型,如bool
Joi.any().strip()
),
// 数组对象, 如需其参考以上字段
arrayObject: Joi.array().items(
Joi.object().keys({
age: Joi.number().integer().max(200),
sex: Joi.boolean()
})
)
with('isA', 'AVal') //意思是,isA 和 AVal 这两字段如果填写了isA,也必须要填写AVal
with('isB', 'BVal') //道理同上
without('isA', 'isB'); //意思是 isA 和 isB 只能填写其中一个
or('isA', 'isB') //意思是 isA 和 isB 这两字段至少填写其一
// 测试数据
const testData = { Password: "12345678"}
// 验证
let value = Joi.validate(testData, paramSchema, { allowUnknown: true, abortEarly: true });
console.log(value);
if (value.error) { throw error; }