Node.js阶段总结
服务器理论基础、建立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搭建的静态资源服务器。
主要方法:
指定静态资源文件夹
将路由地址匹配到文件目录
服务器C/S模型
C代表Client,是客户端的意思
S代表Server 服务端的意思
该模型规范定义了一次完整请求的全过程
具体模型请参考`cs模型.png`图片
什么是服务器和客户端?
服务器:提供服务(后端)
客户端:面向用户的程序(前端)
前端看得见,后端看不见
前后端分离和服务端渲染
前后端分离:前端和后端分别是两个项目开发,前端所有动态数据需要通过请求服务器才能获取。
服务端渲染:由服务器动态生成html,直接返回前端进行显示。
如何选择?
服务端渲染利于SEO(Search Engine Optimization),也就是搜索引擎优化。大型商业网站为了提高搜索排名,利于搜索,都会使用服务端渲染。而前后端分离则多用于混合开发,内嵌页面,后台管理页面等。
还有一种选择方法:通常服务端渲染的页面多用于展示内容,而不是和用户交互;前后端分离的页面多用于和用户交互。
本次课程选择使用服务端渲染,服务器开发是重点。
认识node.js、fs文件系统、promise
什么是node.js
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时。
太学术了听不懂?
什么是浏览器引擎
浏览器引擎是用于解析翻译和渲染页面的工具,主要用于解释翻译html css js
浏览器引擎分为两个:
渲染引擎:用于解释翻译和运行 html + css (通常指的浏览器引擎就是指的渲染引擎)
js 引擎:浏览器用于编译运行 js 的工具
类比
软件 | 平台
---|---
电脑游戏 | 电脑
手机游戏 | 手机
主机游戏 | 游戏主机
js代码 | node.js
js代码 | 浏览器
node.js特点
Node.js 应用程序运行于单个主线程中,具有线程安全性。Node.js 在其标准库中提供了一组异步的 I/O 原生功能(用以防止 JavaScript 代码被阻塞),并且 Node.js 中的库通常是使用非阻塞的范式编写的(从而使阻塞行为成为例外而不是规范)。
英文原文地址:https://nodejs.org/en/about/
单线程(JavaScript execution in Node.js is single threaded)
非阻塞(NI/O)的范式编写,同步行为是不规范的,官方解释:https://nodejs.org/zh-cn/docs/guides/blocking-vs-non-blocking/
对于 js 单线程问题,请参考 《js的单线程.md》
进程和线程的理解
从 cpu 的单核和多核理解进程和线程
理解进程和线程,要理解以下几个问题
单核cpu同时能运行几个应用程序?
单核cpu如何做到“同时”运行两个及其以上的应用程序的?
假设运行了一个《网易云音乐》,同一个软件如何“同时”播放音乐和下载音乐
多核cpu同时能运行几个应用程序?
多核cpu如何做到同时运行两个及其以上的应用程序的?
node.js中的CommonJs和EcmaScript
CommonJs和EcmaScript都是语法和规范
CommonJs 规定node.js模块化的语法规范,讲模块化时,再具体解释
EcmaScript (简称ES)规定了js浏览器中的语法,也是node.js的语法
开发者常说的ES5,ES6就是js的语法版本,通常新版本会支持新语法特性。如:ES6中支持 let 关键字,还支持 lamda 表达式(俗称:箭头函数,箭头方法等)
好消息:node.js 8 已经原生支持ES6标准了
为什么要学?
主要有以下作用:
做服务器
- 服务器应用,要在node.js上运行,很基础很重要,所以要好好听课
做脚本工具
- 例如:一件迁移文件,一键发布代码等
安装node.js
node.js中文网:http://nodejs.cn/
验证是否安装成功:
# 查看node.js版本
node -v
# 结果
v13.7.0
Hello World!
console.log('Hello World!')
控制台输入命令:
node <js文件名>
Node.js和浏览器区别
除了浏览器,node.js也能运行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内置模块的代码
学会查询文档(重点)
学语文时,你查字典;学程序时,你查文档。文档就是辅助我们学习和开发的“字典”
中文文档地址:http://nodejs.cn/api/
任何一门互联网技术,都有自己的技术文档供开发者查阅,阅读文档是项重要能力。曾有面试官问过我:“如何快速学会一个从未接触过的船新技术?”
最快方式:寻找demo(现阶段,老师课堂上的代码,对于同学们就是很好的demo)
稳定方式:寻找官方文档,有两种学习方法:
- 用什么,查什么(快速上手,完成工作,推荐,老师会介绍这种方法)
- 系统学习,通读文档(当开发人员有充足经验和时间的时候,可以这么做)
核心模块
模块的概念,当讲到引入第三方模块的时候,再进行深入学习。
简单理解模块的概念:一个封装好的软件包,开箱即用。
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的话,需要手动配置环境变量
环境变量是什么?
操作系统能够直接全局访问的变量叫做环境变量
环境变量中有个特殊变量,叫做 path
环境变量 path 的作用,是用来定义寻找程序命令的路径
例如:环境变量path的值为:c:/node; d:/node; e:/node
那么当我们运行cmd,输入命令node并运行时,操作系统会从环境变量path所指定的路径中依序寻找对应的可执行程序
设置环境变量
通过系统设置能修改环境变量,对着 计算机 图标右键->属性->高级系统设置->高级选项卡->环境变量
或者 win+r 打开运行,输入 control 回车,打开控制面板,然后选择系统->高级选项卡->环境变量
node.js 中可以访问环境变量
process.env
配置webstorm的nodejs代码提示
打开 file -> setting
找到 Languages -> javascript -> libaray
然后点击 download 等待列表刷新
然后在列表中选择 node 进行安装
安装好后,确认 @tpyes/node 已勾选,然后点击确定
至此node.js的代码提示功能就安装完成。
使用npm安装代码提示
打开终端,执行以下命令安装 @types/node
# 安装最新版本的代码提示
npm install -g @types/node
# 使用npm安装指定版本号的代码提示
# v代表的是版本号
npm install -g @types/node@v
# 卸载代码提示
npm uninstall -g @types/node
执行完命令后,查找 @types/node 安装路径
npm ls -g
# 输出结果如下
# C:\Program Files\nodejs -> .\
# ├── @vue/cli@5.0.4
# ├── create-react-app@5.0.1
# ├── npm@8.1.2
# ├── sass@1.53.0
# └── yarn@1.22.19
# 输出结果的第一行路径中存在一个 node_modules 文件夹,里面就有 @types/node 文件夹
然后 手动配置 webstorm,打开 file -> setting -> language and framework -> javascript -> libraries
然后在右侧点击 add ,然后点击 右侧加号,选中 attach Directories,然后 填入 刚才下载的 node 路径,点击确定即可
node.js模块化、npm包管理工具
node.js模块
什么是模块
独立的高度封装的node.js指令集,就是一个模块。
想想以下组装电脑,每一个零件是个完整可用的,提供独立服务的零件,如:cpu,显卡,内存等等,这每个零件就是一个模块;同理,组成我们整个服务器应用的,提供独立功能的代码集,就是node模块。
我们常写的
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: 希望导出单个内容时使用
npm (node package manager) node包管理工具
什么是npm?
npm 是通过互联网进行包下载,上传,更新等操作的工具。
简单理解,就是个代码仓库,或代码超市,我想用什么代码就从上面下载什么,前提是——你要知道包的名字
作用
最主要的作用是下载我们想要的代码包
常用命令及示范
命令名 | 作用 | 用法 | 备注
---|---|---|---
npm init | 初始化一个node项目,创建package.json依赖文件 | npm init
npm install | 安装软件包到当前目录下的node_modules文件夹中 | npm install <包名> | npm install可以缩写成 npm i; 可以使用该命令配合 package.json 来安装项目依赖
npm install -g | 全局安装,安装到npm应用程序所在node_modules文件夹中| npm install -g <包名> | 全局安装的目的通常是做命令行工具
npm install --dev | 安装项目的开发依赖,开发以来在项目最终导出时,并不会被导出 | npm install --dev <包名> | 别名 npm i -D
npm uninstall | 卸载安装的包,但package.json中还会保留包名 | npm uninstall <包名> |
npm uninstall -g | 卸载全局安装的包 | npm uninstall -g <包名> |
npm info | 查找相关包的信息 | npm info <包名> |
npm list | 查找当前项目下安装的模块 | npm list | 可以添加参数 --depth=<层级> 来显示不同层级的内容;别名为 npm ls
npm list -g | 查找全局安装的模块 | npm list -g | 也可以用来查找全局安装的文件目录
npm help | 打开帮助文档 | npm help <命令名> |
npm --help | 查询命令帮助 | npm <命令名> --help |
全局安装路径在:C:\Users<用户名>\AppData\Roaming\npm\node_modules
清除npm缓存
npm 安装时会首先查询安装缓存内容,有时使用缓存可能会出现模块版本的问题,所以这里可以利用命令清除缓存
npm cache clean -f
去掉https验证
有些电脑在npm安装模块时无法通过https验证,如:
request to https://registry.npmjs.org/lodash failed, reason: unable to verify the first certificate
此时可以设置全局变量,解除严格验证模式
npm config set --global strict-ssl false
# 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 模块化的条件有两个
script 标签需要添加 type 属性, 如: <script type="module"></script>
网页需要通过服务器访问
express基础篇
简介
官方简介:Fast, unopinionated, minimalist web framework for Node.js
Express 是一个快速,简单,极简的Node.js Web框架
特点:
简单
快
web框架 (web app:网页应用程序;web框架就是用来搭建网页应用程序的框架)
安装
创建一个空文件夹,执行了npm init后,执行以下命令安装:npm i --save express
参数的编码与转义
为什么要对参数进行编码和转义
当字符串数据中出现一些特殊字符时,服务器也好,浏览器也好,出于安全考虑都会将字符串进行转义
转义后的字符串可以避免注入漏洞
url编码
url编码 是浏览器为 url 地址进行的一种编码方式,在浏览器地址栏出现特殊符号就会看到 url编码 的结果
例如将以下 url 放到地址栏回车试试
再将 url 放到百度的搜索框里回车试试
然后观察浏览器的地址栏
https://www.baidu.com/s?wd=[{}]}!%hello world_^&$=>
https%3A%2F%2Fwww.baidu.com%2Fs%3Fwd%3D%5B%7B%7D%5D%7D!%25hello%20%20%20%20world_%5E%26%24%3D>
字符串进行转义
let str = '<=!hello world!%=>'
// escape 将特殊字符转义为url编码
str = escape(str)
console.log(str);
// unescape 解码
console.log(unescape(str))
字符串进行url编码
let str = '<=!//hello & world!\\%=>'
// encodeURI 进行url编码
str = encodeURI(str)
console.log(str);
// decodeURI 进行url解码
console.log(decodeURI(str));
let str = '<=!//hello & world!\\%=>'
// encodeURIComponent 进行url编码
str = encodeURIComponent(str)
console.log(str);
// decodeURIComponent 进行url解码
console.log(decodeURIComponent(str));
escape encodeURI encodeURIComponent 区别
escape: 将字符串中所有的编程中的特殊字符进行转义,可以避免注入攻击
encodeURI: 将字符串进行 url 编码,会保留浏览器地址栏能够识别的特殊符号,例如 = 等号
encodeURIComponent: 介于 escape 和 encodeURI 之间,部分浏览器能识别的特殊符号会被编码,部分特殊符号会被保留,将生成一个无效的 url
总结: 希望浏览器能够识别url时,使用 encodeURI;否则使用 escape 或 encodeURIComponent
关于传递数组参数
get 请求参数或 Content-Type 为 application/x-www-form-urlencoded 的 post 请求,也可以传递数组参数。方法如下:
// 拼接参数字符串如下
let params = `a=1&b=2`
// 假设 c 是一个字符串数组,则可以这样写
params += '&c=1&c=2&c=3' // 参数名相同的一组数据将被当作数组传递
// 除此以外,还可以加上数组索引来赋值
params += '&d[0]=1&d[1]=2&d[2]=3'
// 传递对象数组
params += '&e[0][name]=张三&e[0][age]=16&e[1][name]=老王&e[1][age]=30'
ajax 异步请求
知识点
什么是 ajax
ajax 的应用场景
如何发起 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
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/
express高级篇
跨域处理
当客户端请求的url域和服务器所在的域不同时,会触发浏览器的安全机制,限制跨域访问,此时服务器需要打开跨域访问的许可,允许跨域。在express中,需要在服务器启动前,安装如下代码:
// 设置允许跨域访问该服务.
app.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
// Access-Control-Allow-Headers ,可根据浏览器的F12查看,把对应的粘贴在这里就行
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Methods', '*');
// res.header('Content-Type', 'application/json;charset=utf-8');
next();
})
跨域例子:例如:
服务器地址是:shampoo6.com,有一个接口 /game/info
另一个页面,地址是:cat.com/index.html
当 cat.com/index.html 页面访问 shampoo6.com/game/info 接口时,由于域名不同,这就产生了跨域
如果 shampoo6.com 服务器不做跨域处理,就会报如下错误:
Access to XMLHttpRequest at 'http://shampoo6.com/game/info' from origin 'cat.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
使用 cors 模块进行跨域
安装 cors 包
npm i cors
使用如下:
const cors = require('cors')
const express = require('express')
const app = express()
// 跨域处理可以放在所有中间件的前面
app.use(cors())
中间件
中间件middleware,是一个采用责任链模式夹杂在我们的服务器方法中间的方法。
作用:在访问指定接口之前或之后的一个自定义方法,就是中间件
特点:
既然叫中间件,它是可以夹在方法中间的
责任链:这是个设计模式,调用到自己方法时,如符合处理条件,就自行处理并终止操作,否则就传递给下一个处理程序。
方法(function):中间件依然是个方法
有序:中间件代码是有顺序的,放的位置不一样,调用时机就不同
语法:
app.use((req, res, next)=>{
// 中间件逻辑
next()
})
// 或
router.use((req, res, next)=>{
// 中间件逻辑
next(someParams)
})
方法签名最后一个参数一定是 next,且调用next就执行下一个方法,否则就需要在此处res.end否则程序会挂起
next方法调用时,可以接受一个Error对象作为参数,用作抛出异常,任何异常将在下一个中间件的第一个参数中获得,接下来的异常处理就会用到
中间件的作用
充当请求的拦截器
异常处理
异常处理
异常处理在实际项目中非常常见,因为通常程序除了异常,服务器都需要一个统一的返回数据让页面显示,为了更友好,更好看,且数据格式需要在异常处理的时候格式化成需要的数据格式。
// 自定义返回结构
let result = {
code: 0000 // 业务码
msg: '业务成功',
result: {}
}
异常处理的方式就是使用中间件,中间件的位置需要在所有请求和中间件之后书写。如:
app.use((req, res, next)=>{
console.log('this is middleware')
next()
})
app.get('/', (req, res, next) => {
// 接口内也能接收一个 next
// next 函数用于接口抛出异常给后续中间件
// console.log('this is /')
// res.end('hello home!')
next(new Error('my error'))
})
// 定义异常处理器
app.use((err, req, res, next)=>{
console.log(err)
res.status(500)
res.end(err.stack)
})
异常处理器的方法签名中,第一个参数,始终是error,如果程序没有抛错的话,那么错误处理程序将不会被调用
异常分类与处理
异常通常分为两类:系统异常,业务异常
系统异常:通常由于开发人员的bug导致,这种异常不应该让用户看见,所以处理的时候通常会返回:"服务器忙,请稍后再试"等友好的提示语句。
业务异常:设计某一个具体功能,由于用户操作导致的异常。如:登录的时候密码不对,邮箱格式不正确,用户的非法输入和不和逻辑的操作都是业务异常。此类异常通常需要告知用户,用户哪里做错了来作为提示。
这两种异常怎么处理呢?通常我们不会去修改http的status code,也就是http协议的状态码。而是服务器规定一组错误码,前端解析或服务器直接返回错误提示,如下:
先判断异常是系统异常还是业务异常,这里我们需要先定义一个我们自己的异常类,如下:
class MyError extends Error {
}
module.exports = {MyError}
然后在业务逻辑出错时,我们应该主动抛出自定义异常,如下
const {MyError} = require('./myError')
app.post('/user/login', (req, res)=>{
// 这里如果说,我们判断到用户密码不对,那么需要抛出一个业务异常
throw new MyError('登录密码不正确')
})
在异常抛出后,我们需要一个异常处理逻辑来处理异常:
app.use((err, req, res, next)=>{
if (err instanceof MyError) {
// 在这里做业务异常处理的事情
res.end(err.message)
} else {
// 在这里做系统异常处理的事情
res.end('this is not myError')
}
})
总结
异常处理中间件可以捕获以下异常:
在接口回调函数中抛出的异常,如:throw new Error('my error')
在接口回调函数中使用 next 函数抛出的异常,如: (req, res, next)=>{ next(new Error('my error')) }
异常处理中间件无法捕获的内容如下:
promise 中 reject 的异常
若接口回调是一个异步函数,也无法捕获
处理上述无法捕获异常的方法如下:
安装 express-async-handler
给接口回调函数包装一层 asyncHandler
```js
const asyncHandler = require('express-async-handler');
app.get('/asyncHandler', asyncHandler(async (req, res) => {
await Promise.reject('my error');
res.json({code: 200, msg: 'promise 调用成功', result: null});
}));
```
cookie
什么是cookie?
浏览器用于存储指定url请求下的参数的工具,就是cookie,是浏览器的状态管理工具
cookie有哪些作用
存储指定url路径下的参数,以 { key: value } 键值对的形式存储
设置数据失效时间,时间到后会自动失效
每次浏览器发起请求时,如果请求地址保存有cookie,那么浏览器会自动将cookie的值传给服务器
总的来说,cookie就是浏览器用来储存本地数据的
安装cookie工具
npm install cookie-parser
配置
const express = require('express')
const cookieParser = require('cookie-parser')
const app = express()
app.use(cookieParser())
cookie的读写
app.get('/', (req, res) => {
// 读取cookie
console.log(req.cookies)
// 写入cookie
// httpOnly: 只能在http协议中使用
// maxAge: 有效时间
res.cookie('a', '123', {httpOnly: true, maxAge: 3000})
res.end('hello world')
})
cookie的路径
我们可以通过cookie.setPath来修改路径,路径存在以下注意点:
/web05/xx/add --- 添加了cookie
/web05/xx --- 找不到在“add”路径上,添加的cookie
/web05/xx/add/abc --- 可以找到“add”路径上,添加的cookie
cookie.setPath("url") , 修改cookie的添加路径
response.addCookie(cookie) -- 添加cookie, 如果没有使用cookie.setPath("url")方式,修改cookie的添加路径,那么cookie的路径就是当前的url地址。
cookie的限制
用户可以禁止cookie
用户可以删除cookie
cookie不安全
cookie能存储的数据小于4k
cookie个数不能超过300个
cookie的数据只能是字符串
cookie不能存中文
要让cookie存储中文,需要对内容进行url编码
路由器
基本路由
语法:
const express = require('express')
const app = express() // 创建服务器
app.methods(url, handler)
// 例如:
app.get('/', (req, resp) => {
resp.end('this is get root route')
})
app.post('/', (req, resp) => {
resp.end('this is post root route')
})
由于在http协议中的RequestMethods还有delete, option, put, all这些都不常用,所以就不做例子了
路由参数
这个路由参数非常常用,用于从路由获取参数。常见例子:商品详情页,直播房间等。
语法:
// 用冒号加参数名来命名路由参数
app.get('/users/:userId/books/:bookId', function (req, res) {
res.send(req.params) // 用req.params获取参数,参数是个json如:{userId:xxx, bookId: xxx}
})
使用express.Router该类创建模块化的,可安装的路由处理程序。
当项目变大时,路由都写在一个地方的话那么就太拥挤了。我们需要把路由分离出来。
我们来创建一个例子:
创建routers文件夹
在文件夹中创建一个路由器模块,名叫birds.js
const express = require('express')
const router = express.Router()
// 定义路由
router.get('/', (req, res) => {
res.json({ msg: '你好' })
})
router.get('/about', (req, res) => {
res.end('this is bird about')
})
module.exports = router
定义好的路由在 app.js 中安装
app.use('/birds', birds)
第一个参数,代表一个路由路径,这样的话就可以通过 http://localhost:3000/birds来访问birds.js中的接口
当然路由还能嵌套,如在routers文件夹下创建一个叫blue.js,如下:
const express = require('express')
const router = express.Router()
// 定义路由
router.get('/', (req, res) => {
res.json({ msg: '你好blue' })
})
router.get('/about', (req, res) => {
res.end('this is blue bird about')
})
module.exports = router
然后在birds.js中安装blue.js
const blue = require('./blue')
router.use('/blue', blue)
这样就可以使用http://localhost:3000/birds/blue来访问blue.js下的接口了
jsonp 跨域
jsonp跨域原理:
利用 <script> 标签的 src 属性不受跨域影响的特性,使用 <script> 标签访问接口,这种方式就是 jsonp
jsonp 的限制在于,由于使用的是标签访问接口,所以该接口只能是 get 请求
jsonp 跨域请求的script标签src中 必须包含 callback 参数,该参数指定一个回调函数的名称
session理论
什么是session
session是客户端链接服务器的一个会话,当创建与服务器的链接时,session由服务器创建,用于描述服务器状态的对象
有什么用?
用于多次请求之间能够共享数据
session的原理
浏览器访问服务器到时候,服务器会创建一个session对象(每一个session对象,都会有一个唯一标志-sessionId)
默认情况下sessionId会保存在浏览器中的cookie中,再次访问这个服务器的时候,浏览器会将cookie中的sessionId发送到服务器,服务器可以根据sessionId找到session对象
安装session工具
npm i express-session
配置
const express = require('express')
const session = require('express-session')
const app = express()
const port = 1024
// 配置session
app.use(session({
secret: 'this is a secret key', // 必要参数 这是一个签名密钥
cookie: ('name', 'value', {})
// cookie: ('name', 'value', {maxAge: 5*60*1000, secure: false})
}))
session操作
app.get('/', (req, res) => {
// 读取session
console.log(req.session)
// 写入session
if (req.session.count)
req.session.count = req.session.count + 1
else
req.session.count = 1
res.end('hello world')
})
session特点
session对象, 浏览器关闭之后,session对象消失。
session,默认情况下,30分钟,都不再和服务器端有交互,则session无效。
session可以实现服务器端数据的共享(临时数据)
session保存的数据在服务器内存中
更换客户端,session就找不到了
由于session是存在内存中,多台服务器的项目就不能有效访问session
mongodb
一个NoSql高性能分布式文档型数据库。
let student = {
id: 2
class: '221102h5'
}
let 学生班级关系表 = {
'1': {
studentId: 2,
classId: 1
}
}
leanCloud
// collection
let noSqlStudent = [
{ name, sex, class: {}, p: [] },
{}
]
// collection
let class = {
id: 1,
name: '221102h5',
total: 20,
}
拆解分析:
NoSql:非关系型查询语句(相对应的,Sql:结构化查询语句)
高性能:查询速度飞快
分布式:集群,并且有多种存储模式
- 切片:集群里每台数据库,负责存储部分数据,具体怎么存的只有mongo知道,但它能负责查询出来。
- 主从:集群里每台数据库有相同的数据,根据mongo定制的一套负载均衡逻辑,智能选择用哪个数据库。
那么为什么现在越来越多的开发者选择mongodb呢?
速度快
不要钱
mongodb特性
这里主要指4.0以上的特性:
4.0以上的mongo,支持事务
4.2以上的mongo,支持分布式事务
除了事务功能,还完善了连表查询等功能,使mongodb除了应对大堆的无关系数据外,也能处理简单有关联关系的数据了。
安装mongodb
通过官网:https://www.mongodb.com/ 可以免费下载,但最近不知怎么抽风了,国内需要科学上网才能下载。这里由老师提供4.0版本的mongodb。
直接安装到c盘即可,不用安装MongoDB Compass,安装中左下角出现“Install MongoDB Compass”的勾选框时,把它去掉(安装了也没关系,我们不会用它)
mongodb安装好后,会自动启动数据库服务,默认端口号是:27017
安装 mongobooster
mongobooster是mongodb的一个可视化工具(GUI Graphics User Interface)
请下载安装4.0以上版本,否则不支持mongodb4.0,这里由老师提供安装包
直接安装即可
验证安装是否成功
参考图片 验证mongodb连接.png
方法有二:
打开任务管理器,查看是否有“MongoDB Database Server”进程,安装好后会自动启动,有该进程就说明安装成功了。
打开 mongobooster
- 点击左上角 Connect
- 再点击弹出对话框里的 create
- 再点击对话框中,左下角的 test connection
- 如果返回了一个结果,是很大一段json数据,就说明安装成功了
Mongoose
简介
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
websoket
websocket 简介
websocket 是一个服务器和客户端双向通信的协议(双工通道),主要用作服务器向客户端主动推送数据。
express 服务器也可以实现 websocket 协议,可以使用 express-ws 包
websocket 更常见的使用方法是做一个独立服务器,如直播弹幕服务器,聊天服务器等
完成上述服务器的 node.js 的框架有 socket.io
typescript
安装
要运行 ts 需要安装其编译器
全局安装如下
npm i -g typescript
局部安装如下
npm i typescript
全局安装和局部安装的区别在于:调用命令的方式不同,且局部安装的只能在当前项目下使用
安装好后,我们可以输入一段代码来验证运行 ts
创建一个 test.ts 文件
// test.ts
let a: number = 1
let b: number = 2
function add(x: number, y: number): number {
return x + y
}
console.log(add(1, 2))
然后命令行上运行 tsc 命令
tsc test.ts
命令执行后会在同级目录下输出一个 test.js 文件,该文件就是编译后的输出文件了
至此我们就可以使用 ts 进行编程和编译输出了
ts+webpack路径别名问题
在 js 项目中,若要给路径取别名需要在 webpack.config.js 中设置,如下:
// webpack.config.js
module.exports = {
...
resolve: {
alias: {
'@': path.join(__dirname, './src')
}
},
...
}
在 ts 项目中除了设置 webpack.config.js 外,还要设置 tsconfig.json,如:
{
"compilerOptions": {
...
"paths": {
"@/*": [
"src/*"
]
}
...
}
}
请保证 tsconfig.json 中的 paths 属性下的 key,和 webpack.config.js 中的别名相对应,paths 的 value 则为项目下的路径
ts+webpack引入文件问题
js + webpack 项目中引入文件仅需使用 file-loader 即可,但在 ts + webpack 项目中,带有后缀名的文件将被当作 js 模块解析,所以常常会得到一个无法处理模块的错误
遇到这种问题,请在项目 src 文件夹下,添加一个自定义定义文件,如:
// 此处以引入图片文件为例,该文件是名为 image.d.ts 的定义文件
declare module '*.jpg';
declare module '*.png';
declare module '*.jpeg';
declare module '*.gif';
这样一来就可以在其他模块中,像 js 中一样引入图片文件了,例如:
import bg from '@/assets/bg.jpg'
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// 打印编译的文件(包括引用的声明文件)
}
}
TypeScript简介
官网:https://www.typescriptlang.org/
TypeScript 是微软推出的用于补全 javascript 的语言,是一款需要编译器的前端编程语言。
简称 ts
特点
类型检测:在编译前,tsc 编译器会先做类型检测,例如:声明变量时可以指定变量类型,声明函数时可以指定参数和返回值类型,那么在编译时编译器就会验证调用函数时是否传入了正确类型的参数,返回值是否是正确类型的返回值。
那么类型检测带来了哪些好处呢?
防止因为类型不对而导致的异常
能更加明确的定义变量和函数,提高代码可读性
基于类型检测的特点,ts 还拥有了类似 java 的一些特性
泛型
接口
枚举
等等
ts 还可以给函数和变量添加定义文件,文件后缀名为,.d.ts
非常多的 node.js 模块都采用这种方式来为项目添加代码提示功能
可以通过该网站搜索可以添加代码提示的 npm 模块有哪些:https://www.typescriptlang.org/dt/search