什么是node.js
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时。
Node.js和浏览器区别
node.js没有BOM和DOM
简单理解下BOM和DOM
- BOM (browser object model) 浏览器的对象模型
- node.js不能通过js控制浏览器(因为node.js不是浏览器)
- DOM (document object model) 文档的对象模型
- node.js不能直接渲染页面(因为不是浏览器,没有html文档)
- 浏览器不认识node.js内置模块的代码
常见全局变量
__dirname // 执行代码所在的目录
__filename // 执行代码的文件名称
console // 控制台对象
exports // 导出方法
require // 导入方法
global === globalThis // 全局对象,类似浏览器上的 window 对象
node.js模块
什么是模块
独立的高度封装的node.js指令集,就是一个模块。
我们常写的
const fs = require('fs')
上述代码就在引入一个叫 fs
的模块
在 node.js 看来,我们每个
.js
文件都是一个模块
模块规范CommonJs
CommonJs
规定了如何引用和导出模块(导出:可以理解成借出代码给其他模块,导入:可以理解成借入代码给自己这个模块用)
- 语法:const 自定义变量名 = require('模块名')
- 作用:
1. 执行被加载模块中的代码
2. 得到被加载模块中 exports
接口对象
- 导出模块
exports
- 语法:
module.exports = 要导出的对象
- 作用:为了让其他js文件能够引用到(那为什么要让其他js文件引用呢?为了内聚,减少代码量)
- 语法:
来实际使用一下,见《1.模块的导入导出》
exports 与 module.exports
- exports: 希望一个模块导出多个内容时使用,exports 只是 module.exports 的引用
- module.exports: 希望导出单个内容时使用
核心模块:
简单理解模块的概念:一个封装好的软件包,开箱即用。
fs(file system)文件系统模块(重点)
node.js中的文件系统,是一个工具,用于在操纵系统上对文件和目录进行操作,基本操作如:创建文件夹,删除文件,读取文件,写入文件等。
let fs = require('fs')
fs.readFile('./a.txt', (err, data)=>{
if(err) {
console.error(err)
} else {
console.log(data)
let str = data.toString()
console.log(str)
}
})
http协议通信模块(重点)
let http = require('http')
let server = http.createServer()
os(operating system)操作系统模块
let os = require('os')
// 获取当前几期的CPU信息的
console.log(os.cpus())
path 路径模块
let path = require('path')
//获取一个路径中的扩展名部分 extname 扩展名
console.log(path.extname('./data/hello.txt'))
event 事件模块
该模块可以创建收发事件的实体
assert 断言
定义一个笃定的言论就叫断言,多用于做函数的参数判断,例如:加函数function add(x, y) 需要两个参数,那么就可以使用断言判断调用函数时参数的合法性
cluster 集群模块
集群模块可以让js代码在多个cpu核心上运行多个副本
es模块化
es 模块化的语法 浏览器天然支持
node.js 需要较新的版本才能支持
导出模块
// export 导出时要同时进行声明
export let a = 1
export function fn(){}
// export default 导出一个内容 且 必须写在代码最下面
export default { a, fn }
导入模块
// 导入模块语法 path 路径 可以是一个文件路径 也可以是一个模块名称
// import ... from <path>
// 导入 export 的内容
import {age, sex} from './module.js'
// 导入 export default 的内容
import m1 from './module1.js'
// 同时导入 export default 和 export 的内容
import m1, {age, sex} from './module1.js'
// 使用 通配符 * 导入模块的所有内容
// as 就是 “当作” 的意思
// 下面的引入语句相当于: 将模块 m1.js 当作 m1 变量导入
import * as m1 from './m1.js'
// 若已有一个变量和模块中的内容同名
// 那么导入的时候需要添加别名
import {sex as se, age as ag} from './m1.js'
let age = 40
let sex = 'female'
node.js 上使用 es 模块化的方法
修改 package.json
文件,添加 "type": "module"
浏览器上使用 es 模块化的方法
浏览器上使用 es 模块化的条件有两个
- script 标签需要添加 type 属性, 如:
<script type="module"></script>
- 网页需要通过服务器访问
express服务器
静态资源服务器
什么是静态资源?
不会发生变化的资源就是静态资源,静态资源一般指不会变化的文件
静态资源服务器:用于访问静态资源的服务器
const http = require('http');
const path = require('path');
const fsp = require('fs/promises');
const app = http.createServer();
// 定义静态资源文件夹
const staticDirPath = path.join(__dirname, 'public')
app.on('request', (req, res) => {
// 拼接资源路径
let assetPath = path.join(staticDirPath, `.${req.url}`)
// 判断文件是否存在
fsp.stat(assetPath).then((stat) => {
// 判断当前路径是否是一个目录
if (stat.isDirectory()) {
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('无效文件')
return
}
// 读取文件
fsp.readFile(assetPath).then(data => {
// data: 读出来的文件数据
res.write(data)
}).finally(() => {
res.end()
})
}).catch(reason => {
console.error(reason)
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('404资源未找到')
})
})
app.listen(80, () => {
console.log('server start on: http://127.0.0.1')
})
重定向
const http = require('http');
const fsp = require('fs/promises');
const path = require('path')
const app = http.createServer();
app.on('request', async (req, res) => {
if (req.url === '/') {
// 重定向
// 添加重定向的状态码
res.statusCode = '302'
// 添加重定向的路径
res.setHeader('Location', '/1.png')
res.end()
} else if (req.url === '/1.png') {
const data = await fsp.readFile(path.join(__dirname, 'public', '1.png'))
res.end(data)
} else {
res.end('404')
}
})
app.listen(80, () => {
console.log(`server start on: http://127.0.0.1`)
})
http协议
什么是http协议
http (Hypertext transfer protocol)全称 超文本传输协议
分析理解:
- 超文本:比文本更多的信息(图片,超链接,媒体资源等等)
- 传输:运送
- 协议:服务端客户端共同认可的规则
连起来理解:运送比文本更多信息的 “服务器客户端共同认可的” 规则
协议就像游戏规则,规定了服务器和客户端怎么互相聊天,怎么一起愉快的玩耍
通过浏览器直观看看http协议
打开chrome浏览器,f12或ctrl+shift+i,在network下查看请求
常用请求头和响应头
请求头就是浏览器上的 request header
响应头就是浏览器上的 response header
Request Method
:请求方法
名称 | 特点 | 服务器取值方式 |
---|---|---|
get | 传递参数在url上可见 | 通过url.parse(req.url).query 取值 |
post | 传递参数在url上不可见 | 通过req.body 取值 |
Content-Type
:内容类型
有常见的以下几种类型
名称 | 含义 |
---|---|
text/html | html文档 |
text/plain | 纯文本 |
image/jpeg | .jpg图片 |
image/png | .png图片 |
text/xml | xml文档 |
application/json | json数据 |
application/x-www-form-urlencoded | 表单url编码数据,例如:a=1&b=2&c=3 |
multipart/form-data | 上传文件时常看到 |
charset=utf-8 | 指定编码集 |
Status Code
:状态码
状态码只指示请求或响应状态,不对业务负责
常见状态码
代码 | 含义 |
---|---|
200 | 请求成功 |
302 | 资源重定向 |
304 | 资源未改变,使用浏览器缓存 |
400 | 客户端请求的语法错误,服务器无法理解 |
403 | 权限不足 限制访问 |
404 | 资源未找到 |
500 | 服务器内部错误 |
503 | 请求超时 |
User-Agent
:访问代理
意思是什么东西访问了服务器,通常可以用作平台判断。例如:不同浏览器,该值不一样
TCP/IP协议
TCP(translate controll protocol)传输控制协议
TCP/IP协议不仅仅指的是TCP 和IP两个协议,而是指一个由FTP、SMTP、TCP、UDP、IP等协议构成的协议簇, 只是因为在TCP/IP协议中TCP协议和IP协议最具代表性,所以被称为TCP/IP协议
TCP/IP 主要规定链路层、网络层、传输层、应用层
重点:
- 网络层规定了ip地址
- 传输层规定了端口号 port
通过ip + 端口的组合,可以找到互联网上的一个应用服务。如果在使用http协议访问网络时,不加端口号,则默认访问 80 端口。例如:访问 http://127.0.0.1/ 等价于访问 http://127.0.0.1:80/
ajax 异步请求
什么是 ajax
ajax (async javascript and xml)
翻译过来就是:异步js和xml
通常我们把 ajax
理解成:异步的网络请求
ajax 的应用场景
两种情况可以考虑使用 ajax
- 网页中为了发送请求的同时,允许用户继续和页面进行交互
- 不希望使用
form
表单,导致页面跳转
如何发起 ajax 网络请求
发起 ajax
请求的工具 我们称为 Http Client(http客户端)
使用浏览器自带的 ajax
XMLHttpRequest
// 创建 xhr
const xhr = new XMLHttpRequest()
// 打开连接
// xhr.open(requestMethod, url, async)
// requestMethod 请求方法 get 或 post
// url 请求地址
// async 是否异步 默认为 true,若为 false 则,xhr.send 在收到服务器响应后才会返回
xhr.open('post', '/play', true)
// 监听收到服务器响应事件
xhr.addEventListener('load', ev=>{
ev.currentTarget // 该对象中可以获取服务器响应数据
})
// 发送请求
xhr.send()
fetch
// fetch api: https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch#%E6%94%AF%E6%8C%81%E7%9A%84%E8%AF%B7%E6%B1%82%E5%8F%82%E6%95%B0
// fetch 是一个 promise 函数
// fetch(url, options)
// url 请求路径
// options 配置对象
fetch(`/doGet?name=${fd.get('name')}&age=${fd.get('age')}`, {
method: 'GET', // 请求方法 *GET, POST, PUT, DELETE, etc.
mode: 'same-origin', // 跨域策略 no-cors(不跨域), *cors(跨域), same-origin(同源)
// referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
// body: JSON.stringify(data) // 数据体 body data type must match "Content-Type" header
}).then(res => {
// res 服务器的响应对象
console.log(res)
// res.text() 用字符串格式读取服务器返回的结果
// 该函数返回的是 promise
// return res.text()
return res.json()
// res.json() 用json对象读取返回值
// res.blob() 用blob对象(二进制数据对象)读取返回值
}).then(text => {
console.log(text)
})
使用一些 http clint 框架
经典的 http client 框架有:
- jquery
- axios
jquery 已过时,可以自行查找文档,方法非常类似 fetch
axios 比较主流的 http client
mongodb安装
下载 MongoDB Server
.msi
微软安装向导.zip
压缩绿色版
区别:
.msi
会添加注册表并自动添加windows服务,开机自动启动 mongodb.zip
程序绿色环保,但需要通过命令行启动
通过命令行启动 mongodb
启动 mongodb 前,需要先创建 mongodb 的启动配置文件
mongodb 的配置文件格式为 yaml
,名称通常叫 mongod.conf
给配置文件添加内容如下:
# 网络配置
net:
# 绑定ip
bindIp: 0.0.0.0
# 绑定所有可用ip
bindIpAll: true
# 数据库服务的端口号
port: 27017
# 存储配置
storage:
# 数据库保存路径
dbPath: D:/xxx/xxx/db
# 系统日志
systemLog:
# 目标格式
destination: file
# 日志存放路径
path: "D:/xxx/xxx/log/mongod.log"
# 追加日志
logAppend: true
storage:
journal:
enabled: true
# 安全选项
security:
# 是否开启权限校验
authorization: disabled
关于配置文件的其他配置,可以查看此详情
配置完成后,使用命令行启动数据库
mongod --config <配置文件路径>
# 或
mongod -f <配置文件路径>
添加用户并开启权限检测
添加用户
mongodb 默认没有用户,也不会校验数据库权限
为了安全起见,需要添加用户,例如: root权限用户、数据库管理员和普通用户
请使用 mongo shell 或 mongo compass 执行以下命令
// 切换到管理员数据库
use admin
// 创建用户
db.createUser({
user: 'root',
pwd: 'xxxxxx',
roles: [
'root'
]
})
db.createUser详情请见
创建用户的内置角色详情请见
可以在 mongodb shell 中使用 db.createUser.help()
查看帮助信息,createUser
方法名替换成其他方法就能查看其他方法的帮助信息了
开启权限检测
在 mongod.conf
配置文件中添加如下配置
# 安全选项
security:
# 是否开启权限校验
authorization: enabled
开启权限后,若要操作数据库,需要在对应数据库下登录用户
// 切换数据库到创建用户的数据库下
use admin
// 登录
db.auth('root', 'xxxxxx')
登录完后可以操作任意数据库的数据了,例如
db.getCollection('stu').insert({name: '张三', sex: 'male'})
Mongoose
简介
官网:https://mongoosejs.com/
mongoose 是 mongodb 的 node.js 的数据库驱动
安装
npm i --save mongoose
连接数据库
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/test2', { useNewUrlParser: true })
const db = mongoose.connection; // 获取数据库连接对象
db.on('error', console.error.bind(console, 'connection error:')); // 绑定连接错误事件
db.once('open', function () { // 绑定一次打开数据库连接事件
// we're connected!
console.log("we're connected!")
})
其中 mongodb://localhost:27017/test2
是连接字串
localhost
是数据库地址,可以换成对应的ip地址27017
是数据库端口test2
是数据库库名
也可以追加登录数据库的用户名和密码,如:mongodb://admin:111111@localhost:27017/test2
admin: 账号
111111: 密码
连接字串,可以参考 mongobooster 的是怎么写的。见图《如何查看数据库连接字串.png》
创建数据库表结构图
表结构图是用来设计表的对象,其定义了表字段的属性。
就像是一个工程图纸,根据图纸创建出来的就是具体的表。
语法:
const { Schema } = require('mongoose')
const gameSchema = new Schema({
name: { type: String, index: true, unique: true }, // 创建唯一标识
platform: { type: String, index: true }, // 创建索引
price: { type: Number, index: true },
createTime: { type: Date, default: Date.now(), index: true },
updateTime: { type: Date, default: Date.now(), index: true }
// 试试将时间的类型改成数字
// createTime: { type: Number, default: Date.now(), index: true },
// updateTime: { type: Number, default: Date.now(), index: true }
})
表结构中,通常都会有
createTime
和updateTime
用来描述记录创建事件和修改事件
如何定义字段:
- 直接指定类型,字段类型是必须的,所以这种是最基础的定义方法,例如:
const gameSchema = new Schema({
name: String // 创建一个叫name的字段,类型是 String
})
- 指定字段初始值,例如:
const gameSchema = new Schema({
createTime: { type: Date, default: Date.now() }
})
- 创建索引
index
通常在建表的时候,请将需要用于查询的字段添加上索引index
什么是索引呢?数据库有一个字典,如果创建索引,数据库会在字典上做记录,那么查询的时候,数据库会查询得更快。
const gameSchema = new Schema({
name: { type: String, index: true } // 创建一个叫name的字段,类型是 String
})
- 创建唯一索引
unique
唯一索引是指数据库的某个字段,不接受重复的值。例如有个name
字段添加了唯一索引,那么这个表里面,name
是不能重复的。
const gameSchema = new Schema({
name: { type: String, unique: true } // 创建一个叫name的字段,类型是 String
})
创建数据模型
数据模型对应的就是数据库中的表,当我们基于数据模型进行数据保存的时候,如果数据库还不存在这张表,那么就会自动创建表。
所有数据库操作,都可基于数据库模型进行操作的
const { model } = require('mongoose')
const Game = model('game', gameSchema) // 第一个参数影响表名,且大小写不敏感
数据库基础操作
保存 save
保存操作用于对整个对象进行修改并保存。
代码里一顿操作猛如虎,但是不保存,就不会进入数据库
先创建用于保存的数据对象
const tlou = new Game({ // 直接 new 一个之前创建的数据模型
name: 'The Last Of Us',
platform: 'PS',
price: 400
})
如果创建对象的时候有
_id
字段,那么保存功能会覆盖数据库中有指定id的数据
如何理解呢?平时保存文件是,文件名相同的就会被覆盖掉。这里的
_id
相当于数据对象的“文件名”,如果数据库有这个id的数据,那么保存的时候就会被覆盖掉。
保存:
tlou.save((err, tlou) => {
if (err) console.error(err)
else console.log(tlou)
})
保存方法,接受一个 callback: (err, savedModel) => {}
,其中 savedModel
是保存后数据库返回的数据对象,如果是新增数据,会包含一个数据库分配的id
删除 delete
删除符合条件的数据,通常以id为查询条件进行删除,当然也能以其他字段为查询条件进行删除。
// 单个删除
Game.deleteOne({ _id: '5f0aa810a5770b0960a33314' }, (err) => {
if(err) console.error(err)
})
// 批量删除
Game.deleteMany({ _id: { $in: ['5f0aac77c783dc380cf15eef', '5f0aac77c783dc380cf15eee'] } }, (err) => {
if (err) console.error(err)
})
修改 update
修改能找到与指定条件相匹配的数据,修改指定的字段为指定的值
与save的区别,在于,save是整个覆盖数据,而update是修改指定的字段
- 普通修改
最为普通,没有特点的update
Game.updateOne({ _id: '5f0aadc5dd9d313ee070fcee' }, {
name: 'TheLastOfUs' // 要修改的字段和值
}, (err, raw) => {
if (err) console.error(err)
else {
console.log('修改成功')
console.log(raw) // 修改结果文档
}
})
通常update的查询条件是id,updateOne的第二个参数就是指定要修改的字段和值
- 修改并查询
这是个可以应对并发的方法,是查询和修改两个功能的合体。
并发:同一段代码,同时被调用多次。
高并发:同一段代码,同时被调用N次,就是非常非常多次
并发时最容易出的错误就是,值被覆盖了
常见例子:钱包余额,抢红包等。但凡和钱有关,就要格外注意。
这种场合下如果要在修改后获取其数据,就需要一个具备数据库原子性的操作。
什么时原子性?过去科学家认为原子是最小的物质,不可分割
的物质。所以原子性的意思就是具备 不可分割
的特性。
语法:
// 查询并修改:具备数据库原子性
Game.findOneAndUpdate({ name: 'Super Mario Bros' }, {
platform: 'SFC',
price: 300,
updateTime: Date.now()
}, {
new: true, // 是否返回修改后的数据
upsert: true, // 是否不存在就增加一条新的
// 注意:upsert的时候不会去修改 updateTime 和 createTime 这种创建时自动赋值的数据,所以需要设置setDefaultsOnInsert
setDefaultsOnInsert: {
createTime: Date.now()
}
}, (err, doc, res) => {
if (err) console.error(err)
else {
console.log('查询并修改完成')
console.log(doc) // 返回查询到的数据
console.log(res)
}
})
- 第一个参数是查询条件
- 第二个参数是要修改的字段和值
- 第三个是可选附加功能,可以不填
- 第四个是callback,用来获取查询到的数据
对数字进行加减
如果说对数字进行直接覆盖,可以使用 update
或 findOneAndUpdate
。但之前例子中,如:从钱包中扣钱或存钱,抢红包,这些场景就不能直接覆盖数字,而是在其现有基础上进行加减。
以update为例,语法:
// 单独对数字的修改
Game.updateOne({ name: 'Super Mario Bros' }, {
platform: 'SFC', // 这里依然可以修改其他字段的值
$inc: { price: -20 } // 使用$inc,增加数字的值,可以是负数
}, (err, raw) => {
// err 如果数据库报错会有err,否则就不存在
// raw 修改结果
// 修改的callback方法,基本没有用
})
第二个参数是修改的字段和值,其中只要声明一个 $inc
就可以了 inc是 increase(增加) 的缩写
查询 exists & find
- exists 查询指定条件的数据是否存在
- 语法:
// 查询 name 字段为 The Last Of Us 的数据是否存在
Game.exists({ name: 'The Last Of Us' }, (err, res) => {
if (err) console.error(err)
console.log('exists: ' + res) // 这里的res是个boolean值,true代表存在,false代表不存在
})
- find 条件查询,如果只查询一个值,可以使用 findOne方法
基础用法
语法:
// 查询 name 中包含 “2” 的数据
Game.find({ name: { $regex: /^(\s|\S)*2(\s|\S)*$/ } }, (err, docs) => {
if (err) console.error(err)
else console.log(docs)
})
- 第一个参数是查询条件
- 第二个参数是
callback: (err, games)=>{}
- err:如果数据库报错,会包含错误信息
- docs:查询出来的数据库数据,是个数组;如果用的
findOne
方法,docs是一个数据库数据对象,而不是数组
使用$where做查询条件
语法:
Game.find({
name: { $regex: /^(\s|\S)*2(\s|\S)*$/ }
$where: 'this.createTime.getTime()!=this.updateTime.getTime()'
}, (err, docs) => {
if (err) console.error(err)
else console.log(docs)
})
- 参数与基础用法相同
- 在第一个参数,查询条件中,多了一个
$where
指令。该指令接收一个字符串参数,该字符串是一个js表达式。表达式中的this
代表正被查找的当前数据对象。所以可以通过this.field
来引用自身数据的字段来进行逻辑判断。 - 用处:如果需要取表里两个及其以上字段来做逻辑判断时,就需要用
$where
- 举例:以 game 表为例。如果要查找
createTime
和updateTime
相同的数据(也就是查找从来没被修改过的数据);或者查找updateTime - createTime
小于三天的数据(也就是查找自创建以来,3天内都未作修改的数据)
查询排序 sort 与分页 skip & limit (size)
排序:
Game.find({},
null, // '-updateTime' 排除updateTime字段 'name'选择 name字段
{
sort: {
price: 1, // 按价格降序和最新数据排序
updateTime: -1 // 大于零 升序;小于零 降序
}
}, (err, docs) => {
console.log(docs)
})
注意:该方法参数与基础查询方法不一样
- 第一个参数:查询条件
- 第二个参数:选择返回的字段,类似sql中的select,null的话就返回所有表数据
- 参数值是个字符串,例如
'name platform price'
,字段间用空格隔开 - 字段名前加上
-
号,代表排除某字段,如:'-updateTime -createTime'
,这样数据库就不会返回updateTime
和createTime
的值。 - 选择字段和排除字段不能同时存在,也就是不能这么写
name -updateTime
- 参数值是个字符串,例如
- 第三个参数:可选配置
- sort:按字段排序,值大于零升序,小于零降序
- 第四个参数:callback
分页 skip & limit
- skip:查询时候,跳过多少条数据
- limit:返回数据的总数
举例:如果我们按每页5条数据进行取值,第二页的数据该怎么查询呢?
语法:
Game.find({}, // 分页的时候也可以加查询条件
null,
{
sort: {
price: 1, // 按价格降序和最新数据排序
updateTime: -1 // 大于零 升序;小于零 降序
},
skip: (2-1)*5, // 公式:skip:(page-1)*size
limit: 5 // 公式:limit:size
}, (err, docs) => {
console.log(docs)
})
写法基本和排序相同,但多了两个字段 skip
和 limit
如果令当前页为 page
;每页记录数为 size
;那么参数 skip
和 limit
就应该是:
skip: (page-1)*size,
limit: size
mongoose的curd操作
const mongoose = require('mongoose');
const {model, Schema} = mongoose;
// 数据结构
const schema = new Schema({
name: {type: String, index: true},
platform: {type: String, index: true},
price: {type: Number, index: true},
// default: 字段的默认值
// createdAt: {type: Date, default: new Date(), index: true},
// updatedAt: {type: Date, default: new Date(), index: true},
}, {
// 添加时间戳
timestamps: true
});
// 构建数据模型Model
const Game = model('game', schema);
(async () => {
const db = mongoose.connection
db.on('open', () => {
console.log('打开数据库连接')
})
db.on('error', err => {
console.error('数据库异常:"', err)
})
await mongoose.connect('mongodb://admin:111111@127.0.0.1:27017/test')
console.log('连接成功')
// let r = await Game.insertMany([
// {
// name: 'Super Mario Bros',
// price: 320,
// platform: 'FC'
// },
// {
// name: 'The Last Of Us',
// price: 400,
// platform: 'PS'
// },
// {
// name: 'Mather',
// price: 250,
// platform: 'FC'
// },
// {
// name: 'Dead Space',
// price: 300,
// platform: 'XBOX'
// },
// {
// name: 'Final Fantasy 10 Remaster',
// price: 280,
// platform: 'PS'
// },
// {
// name: 'Halo',
// price: 366,
// platform: 'XBOX'
// },
// {
// name: 'Gears Of War',
// price: 333,
// platform: 'XBOX'
// },
// {
// name: 'God Of War',
// price: 420,
// platform: 'PS'
// },
// {
// name: 'Legend Of Zalda',
// price: 460,
// platform: 'FC'
// }
// ])
// 新增数据
// let doc = new Game({
// name: 'FEZ',
// platform: 'PC',
// price: 100
// })
// await doc.save()
// await Game.create({
// name: 'Start Craft',
// platform: 'PC',
// price: 230
// })
// 删除数据
// let r = await Game.deleteOne({_id: '63e4bfd89b9de0343e09b9d0'})
// let r = await Game.deleteMany({_id: {$in: ['63e4c0169b9de0343e09b9d2', '63e4c01e9b9de0343e09b9d3']}})
// 修改数据
// let r = await Game.updateOne({_id: '63e4bfba36568e3647fa9cb9'}, {
// price: 130
// }, {
// // 是否修改时间戳
// timestamps: true
// })
// 查询并修改数据
// let r = await Game.findOneAndUpdate({name: 'Diablo'}, {
// price: 500,
// platform: 'PC'
// }, {
// // 是否修改时间戳
// timestamps: true,
// // 是否没有数据就插入数据
// // 当数据查询不到时就添加新数据
// upsert: true,
// // 返回修改后的数据
// new: true,
// // 若添加数据的话就要设置一些默认数据
// setDefaultsOnInsert: true
// })
// 查询
// $where 添加一个约束条件,值是一个用于数据库查询的js脚本,脚本里写一个布尔表达式
// let r = await Game.find({/*$where: `this.updatedAt === this.createdAt`*/}, '-platform -price', {
// sort: {updatedAt: -1},
// skip: 1,
// limit: 2
// })
// 判断是否存在符合条件的数据
// let r = await Game.exists({name: 'Start Craft'})
// 查询数量
// let r = await Game.count({platform: 'PC'})
let r = await Game.aggregate([
// $match 用来过滤数据
{$match: {price: {$gt: 300}}},
// 数组中每个对象是一个聚合的条件
// 其中 $group 必填
// _id: 代表用什么字段进行分组,可以写 null 代表不分组
// $avg: 是内置的命令,用来求字段的平均值
{$group: {_id: "$platform", avgPrice: {$avg: "$price"}}},
// $count 用于统计数量
// { $count: 'total' },
])
console.log(r)
})()
ts配置文件
部分参考文献
什么是 tsconfig
tsconfig.json
文件存在的目录表明该目录是 TypeScript
项目的根目录。该 tsconfig.json
文件指定了编译项目所需的根文件和编译器选项。
当成功配置了 tsconfig.json
后,就可以在项目根目录下运行 tsc
了,此时编译器会根据配置对项目进行编译
配置文件选项
顶级选项有:
- files 编译文件的入口文件列表
- extends 扩展继承其他的 tsconfig
- include 指定要包含在项目编译中的文件名或文件模式字符串(和
files
选项作用类似) - exclude 指定
include
中的一些不需要被编译的文件或文件模式字符串 - references 引入外部项目的选项
- compilerOptions 编译选项
- typeAcquisition 设置自动引入库类型定义文件(.d.ts)相关
compilerOptions 常见配置
{
// ...
"compilerOptions": {
"incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
"tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
"diagnostics": true, // 打印诊断信息
"target": "ES5", // 目标语言的版本
"module": "CommonJS", // 生成代码的模板标准
"outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
"lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
"allowJS": true, // 允许编译器编译JS,JSX文件
"checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
"outDir": "./dist", // 指定输出目录
"rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构
"declaration": true, // 生成声明文件,开启后会自动生成声明文件
"declarationDir": "./file", // 指定生成声明文件存放目录
"emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
"sourceMap": true, // 生成目标文件的sourceMap文件
"inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
"declarationMap": true, // 为声明文件生成sourceMap
"typeRoots": [], // 声明文件目录,默认时node_modules/@types
"types": [], // 加载的声明文件包
"removeComments":true, // 删除注释
"noEmit": true, // 不输出文件,即编译后不会生成任何js文件
"noEmitOnError": true, // 发送错误时不输出任何文件
"noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
"importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
"downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
"strict": true, // 开启所有严格的类型检查
"alwaysStrict": true, // 在代码中注入'use strict'
"noImplicitAny": true, // 不允许隐式的any类型
"strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
"strictFunctionTypes": true, // 不允许函数参数双向协变
"strictPropertyInitialization": true, // 类的实例属性必须初始化
"strictBindCallApply": true, // 严格的bind/call/apply检查
"noImplicitThis": true, // 不允许this有隐式的any类型
"noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
"noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
"noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
"noImplicitReturns": true, //每个分支都会有返回值
"esModuleInterop": true, // 允许export=导出,由import from 导入
"allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
"moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
"paths": { // 路径映射,相对于baseUrl
// 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
"jquery": ["node_modules/jquery/dist/jquery.min.js"]
},
"rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
"listEmittedFiles": true, // 打印输出文件
"listFiles": true// 打印编译的文件(包括引用的声明文件)
}
}