华清远见-重庆中心-node.js阶段性总结

ejs模板

模板就是一个用于生成html文件的有独特语法的文件

<html>
<head></head>
<body>
    <!-- 此处就是模板语法 -->
    <div>${user.name}</div>
</body>
</html>

然后我们自己写个函数将他转换成html,如:

// 定义个渲染函数,用于将模板转换成html
function render(templateName, params) {
    // 转化过程写在这里
}

// 调用渲染函数,将模板和对应的数据传入函数
// 函数将返回html内容
let html = render('template.rgb', {user: {name: '法外狂徒张三'}})

上述模板最终会被翻译成html,如下:

<html>
<head></head>
<body>
    <!-- 此处就是转换出来的内容 -->
    <div>法外狂徒张三</div>
</body>
</html>

模板引擎是什么

模板引擎就是用于解释翻译模板的工具,例如上述例子中的render函数,应该由模板引擎提供

模板引擎ejs

ejs 是个被许多框架使用的模板引擎

安装与使用

# 安装
npm install ejs

使用流程

  • 创建模板文件.ejs

  • 引入ejs模块

  • 编译模板

  • 返回html内容

代码参考《ejs-project》的 ejs-demo.js

express 中集成 pug

代码参考《ejs-project》的 ejs-express.js

express

ajax异步请求

两种情况可以考虑使用 ajax

  1. 网页中为了发送请求的同时,允许用户继续和页面进行交互

  1. 不希望使用 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

常用请求头和响应头

请求头就是浏览器上的 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

请求超时

mongodb

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的第二个参数就是指定要修改的字段和值

  • 修改并查询这是个可以应对并发的方法,是查询和修改两个功能的合体。

语法:

// 查询并修改:具备数据库原子性
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

websocket

websocket 是一个服务器和客户端双向通信的协议(双工通道),主要用作服务器向客户端主动推送数据。

websocket api

express 服务器也可以实现 websocket 协议,可以使用 express-ws

websocket 更常见的使用方法是做一个独立服务器,如直播弹幕服务器,聊天服务器等

完成上述服务器的 node.js 的框架有 socket.io

认识node,fs,promise

核心模块

模块的概念,当讲到引入第三方模块的时候,再进行深入学习。

简单理解模块的概念:一个封装好的软件包,开箱即用。

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核心上运行多个副本

常见全局变量

__dirname // 执行代码所在的目录
__filename // 执行代码的文件名称
console // 控制台对象
exports // 导出方法
require // 导入方法
global === globalThis // 全局对象,类似浏览器上的 window 对象

服务器理论,建立node.js服务器

第一个服务器

术语:

  • ip: 计算机在网络中的地址 其中本机地址为 127.0.0.1

  • 一般情况可以使用 localhost 代表 127.0.0.1,若不成功则可以修改 C:\Windows\System32\drivers\etc\hosts 来定义 localhost

  • 端口号: 一个应用程序与互联网连接通信的出入口

  • 协议: 例如 http https 都是协议名,规定网络通信如何进行

  • http 协议默认端口: 80

  • https 协议默认端口: 443

服务器获取参数

术语:

  • url: 统一资源定位符, 其实就是一个网络资源在网络中的地址路径

  • uri: 统一资源定位标识, 任意资源在系统中的一个唯一标识符,并不局限于网络中。例如: 身份证号就是每个人的uri

路由器 Router

路由(route)

什么是路由?

  • 动词: 通过不同请求地址的路径,分发请求

  • 分发请求时,请求可能被重新分配到另一个服务器,或者被分配到自己服务器的另一个应用程序,又或者分配给接收请求的服务器某个处理函数等

  • 名词: 包含访问地址url,访问参数等一系列和路径相关的信息

以下代码中的 if else 代码块就是个简单的路由器

server.on('request', (req, resp) => {
    resp.setHeader('Content-Type', 'text/html;charset=utf-8')
    let url = req.url
    // 这段if else代码块,可以看作是个简单的路由器
    if (url === '/') {
        resp.end('感谢您的访问')
    } else if (url === '/text') { // 读取一段文本
        resp.setHeader('Content-Type', 'text/plain;charset=utf-8')
        fs.readFile('./a.txt', (_, data) => {
            resp.end(data)
        })
    } else if (url === '/favicon.ico') { // 读取一个图片
        resp.setHeader('Content-Type', 'image/jpeg')
        fs.readFile('./favicon.ico', (_, data) => {
            resp.end(data)
        })
    } else {
        resp.end('404 资源没找到')
    }
})

请求文件

静态资源服务器

  • 静态资源: 不会产生变化的文件

重定向

重定向就是当用户访问一个地址时,服务器返回另一个地址,然后浏览器重新访问该地址,这个过程称为重定向

例如:

  • 服务器有个地址 /img/1.jpg

  • 用户访问该地址 /img/1.jpg

  • 服务器重新返回一个地址 /image/1.jpg

  • 客户端重新访问地址 /image/1.jpg

  • 以上过程称为重定向

重定向的一个特点就是地址栏会发生变化

基于路由创建简单的静态资源服务器

静态资源服务器就是一个只提供资源下载的服务器,常见的产品有nginx,apache等。之所以叫做“静态”是因为资源是静态的,不受服务器逻辑产生变化。有静态肯定就有动态,之后讲模板引擎的时候会进行说明。

也有使用node.js搭建的静态资源服务器。

主要方法:

  • 指定静态资源文件夹

  • 将路由地址匹配到文件目录

node.js模块化

什么是一个 node.js 项目

一个目录,若使用了 npm init 命令创建了一个 package.json 文件,那么我们称该目录为一个 node.js 项目

package.json

package.json 是 node.js 用于描述项目的文件

{
  "name": "npm-demo", // 项目名称
  "version": "1.0.0", // 项目版本号
  "description": "第一个用来演示npm命令的项目", // 项目的描述
  "main": "index.js", // 项目最终导出的文件
  "scripts": { // 脚本
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node main.js" // 脚本中的内容实际就是项目目录下运行的命令
  },
  "author": "", // 作者
  "license": "ISC", // 证书
  "dependencies": { // 安装的依赖
    "axios": "^0.27.2",
    "bootstrap": "^5.1.3",
    "jquery": "^3.6.0"
  },
  "devDependencies": { // 开发依赖
    "@types/node": "^16.11.43"
  }
}

package-lock.json

package-lock.json 是 npm 安装依赖包时的版本信息,它记录了安装依赖包时,当时的版本号

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 模块化的条件有两个

  1. script 标签需要添加 type 属性, 如: <script type="module"></script>

  1. 网页需要通过服务器访问

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值