发布时间:2024-01-26 14:30
1、什么是Node.js
**Node.js⑧is a JavaScript runtime built on Chrome’s V8 JavaScript engine.
Node.js是一个基于Chrome V8,弓擎的JavaScript运行环境。
Node.js的官网地址: https://nodejs.org/en/
Node.js作为-个JavaScript的运行环境,仅仅提供了基础的功能和API。然而,基于Node.js提供的这些基础能,很多强大的工具和框架如雨后春笋,层出不穷,所以学会了Node.js,可以让前端程序员胜任更多的工作和岗位:
总之: Node.js 是大前端时代的”大宝剑”, 有了Node.js这个超级buff的加持,前端程序员的行业竞争力会越来越强!
浏览器中的JavaScript学习路径:
JavaScript基础语法+浏览器内置API (DOM + BOM) +第三方库(jQuery、art-template 等)
Node.js的学习路径:
JavaScript基础语法+ Node.js内置API模块(fs、 path、 http等) +第三方API模块(express、 mysql 等)
打开终端,在终端输入命令node-v后,按下回车键,即可查看已安装的Node.js的版本号。
windows系统快速打开终端的方式:
使用快捷键(windows徽标键+R)打开运行面板,输入cmd后直接回车,即可打开终端。
在Windows的powershell或cmd终端中,我们可以通过如下快捷键,来提高终端的操作效率:
fs模块是Node.js官方提供的、用来操作文件的模块。它提供了一系列的方法和属性, 用来满足用户对文件的操作需求。
例如:
如果要在JavaScript代码中,使用fs模块来操作文件,则需要使用如下的方式先导入它:
1、 fs.readFile()的语法格式
使用fs.readFile( )方法,可以读取指定文件中的内容,语法格式如下:
参数解读:
2、 fs.readFile( )的示例代码
以utf8的编码格式,读取指定文件的内容,并打印err和dataStr的值:
//1.导入 fs模块 来操作文件
const fs = require(\'fs\')
//2. 调用fs. readFile() 方法读取文件
//.参数1:读取文件的存放路径
//.参数2:读取文件时候采用的编码格式,一般默认指定UTF 8
//....参数3:回调函数,拿到读取失败和成功的结果 err. . dataStr
fs.readFile(\'./files/01.txt\', \'utf-8\', function (err, dataStr) {
//2.1 打印失败的结果
//如果读取成功,则err 的值为null
//如果读取失败,则err的值为错误对象,dataStr 的值为undefined
console.log(err);
console.log(\'---------\');
//2.2 打印成功的结果
console.log(dataStr);
})
1、 fs.writeFile()的语法格式
使用fs.writeFile( )方法,可以向指定的文件中写入内容,语法格式如下:
参数解读:
//1.导入 fs 文件系统模块
const fs = require(\'fs\');
// 2.调用fs.writeFile() 方法,写入文件的内容
// 参数1:表示文件的存放路径
// 参数2:表示要写入的内容
// 参数3:回调函数
fs.writeFile(\'./files/01.txt\', \'abcd\', function (err) {
//2.1 如果文件写入成功 则err 的值等于null
//2.2 如果文件写入失败 则err 的值等于一个错误的对象
console.log(err);//null
})
fs.writeFile(\'f:/files/02.txt\', \'abcd\', function (err) {
//2.1 如果文件写入成功 则err 的值等于null
//2.2 如果文件写入失败 则err 的值等于一个错误的对象
console.log(err); //[Error: ENOENT: no such file or directory, open \'f:\\files\\02.txt\'] {
})
可以判断err对象是否为null,从而知晓文件写入的结果:
fs.writeFile(\'./files/01.txt\', \'abcd\', function (err) {
//2.1 如果文件写入成功 则err 的值等于null
//2.2 如果文件写入失败 则err 的值等于一个错误的对象
if (err) {
return console.log(\'文件写入失败!\' + err.message); //文件写入成功!
}
console.log(\'文件写入成功!\');
})
使用fs 文件系统模块,将素材目录下成绩.txt文件中的考试数据,整理到成绩-ok.txt文件中。
整理前,成绩.txt文件中的数据格式如下:
整理完成之后,希望得到的成绩-ok.txt文件中的数据格式如下:
//1.导入 fs模块 来操作文件
const fs = require(\'fs\')
//2. 调用fs. readFile() 方法读取文件
//.参数1:读取文件的存放路径
//.参数2:读取文件时候采用的编码格式,一般默认指定UTF 8
//....参数3:回调函数,拿到读取失败和成功的结果 err. . dataStr
fs.readFile(\'./files/成绩.txt\', \'utf-8\', function (err, dataStr) {
if (err) {
return console.log(\'读取文件失败!\' + err.message);
}
// console.log(\'读取文件成功!\' + dataStr);
// 4.1 先把成绩的数据,按照空格进行分割
const arrOld = dataStr.split(\' \')
// 4.2 循环分割后的数组,对每一项数据,进行字符串的替换操作
const arrNew = []
// 4.3 把新数组中的每一项,进行合并,得到一个新的字符串
arrOld.forEach(item => {
arrNew.push(item.replace(\'=\', \':\'))
})
const arrStr = arrNew.join(\'\\r\\n\');
console.log(arrStr);
// 5 调用wrideFile 为文件写入数据
fs.writeFile(\'./files/成绩.txt\',arrStr,function(err){
if(err){
return console.log(\'写入文件失败\' + err.message);
}
console.log(\'写入文件成功\');
})
})
在使用fs模块操作文件时,如果提供的操作路径是以./或…/开头的相对路径时,很容易出现路径动态拼接错误的问题。
原因:代码在运行的时候,会以执行node命令时所处的目录,动态拼接出被操作文件的完整路径。
解决方案:在使用fs模块操作文件时,直接提供完整的路径,不要提供./或/开头的相对路径,从而防止路径动态拼接的问题。
使用__dirname完美解决路径动态拼接的问题
//1.导入 fs模块 来操作文件
const fs = require(\'fs\')
fs.readFile(__dirname+\'/files/01.txt\', \'utf-8\', function (err, dataStr) {
//2.1 打印失败的结果
//如果读取成功,则err 的值为null
//如果读取失败,则err的值为错误对象,dataStr 的值为undefined
console.log(err);
console.log(\'---------\');
//2.2 打印成功的结果
console.log(dataStr);
})
path模块是Node.js官方提供的、用来处理路径的模块。它提供了一系列的方法和属性,用来满足用户对路径的处理需求。
例如:
1、path Join()的语法格式
使用path.join( )方法,可以把多个路径片段拼接为完整的路径字符串,语法格式如下:
2、path Join()的代码示例
使用path.join( )方法,可以把多个路径片段拼接为完整的路径字符串:
const path = require(\'path\')
// 注意 ../会抵消前面的路径
const pathStr = path.join(\'/a\', \'/b/c\', \'../\', \'./d\', \'e\');
console.log(pathStr); //\\a\\b\\d\\e
const pathStr1 = path.join(__dirname, \'./files/01.txt\')
console.log(pathStr1);// 输出当前所在目录\\files\\1.txt
1、path.basename()的语法格式
使用path.basename()方法,可以获取路径中的最后一部分, 经常通过这个方法获取路径中的文件名,语法格式如下:
参数解读:
使用path.basename( )方法,可以从一个文件路径中,获取到文件的名称部分:
const path = require(\'path\')
//定义文件的存放路径
const fpath = \'/a/b/c/index.html\'
const fullName = path.basename(fpath)
console.log(fullName);//index.html
const nameWithoutExt = path.basename(fpath, \'.html\')
console.log(nameWithoutExt); //index
1、path.extname()的代码示例
使用path.extname( )方法,可以获取路径中的扩展名部分:
const path = require(\'path\')
//定义文件的存放路径
const fpath = \'/a/b/c/index.html\'
const fext = path.extname(fpath)
console.log(fext); //.html
回顾:什么是客户端、什么是服务器?
在网络节点中,负责消费资源的电脑,叫做客户端;负责对外提供网络资源的电脑,叫做服务器。
http模块是Node.js官方提供的、用来创建web服务器的模块。通过http模块提供的http.createServer()方法,就能方便的把一台普通的电脑, 变成-台Web服务器,从而对外提供Web资源服务。
如果要希望使用http模块创建Web服务器,则需要先导入它:
服务器和普通电脑的区别在于,服务器上安装了web服务器软件,例如: IIS、 Apache 等。通过安装这些服务器软件就能把一台普通的电脑变成-台web服务器。
在Node.js中,我们 不需要使用IlS、Apache 等这些 第三方web服务器软件。因为我们可以基于Node.js提供的http模块,通过几行简单的代码,就能轻松的手写一个服务器软件,从而对外提供web服务。
域名和域名服务器
尽管IP地址能够唯一地标记网络上的计算机,但IP地址是一长串数字, 不直观,而且 不便于记忆,于是人们又发明了另一套字符型的地址方案,即所谓的域名(Domain Name)地址。
IP地址和域名是一对应的关系, 这份对应关系存放在一种叫做域名服务器(DNS, Domain name server)的电脑中。使用者只需通过好记的域名访问对应的服务器即可,对应的转换工作由域名服务器实现。因此,域名服务器就是提供IP地址和域名之间的转换服务的服务器。
注意:
① 单纯使用IP地址,互联网中的电脑也能够正常工作。但是有了域名的加持,能让互联网的世界变得更加方便。
② 在开发测试期间,127.0.0.1 对应的域名是localhost,它们都代表我们自己的这台电脑,在使用效果上没有任何区别。
计算机中的端口号,就好像是现实生活中的J牌号样。 通过i ]牌号,外卖小哥可以在整栋大楼众多的房间中,准确把外卖送到你的手中。
同样的道理,在一台电脑中, 可以运行成百上千个web服务。每个web服务都对应一个唯一 的端口号。 客户端发送过来的网络请求,通过端口号,可以被准确地交给对应的web服务进行处理。
1.创建web服务器的基本步骤
① 导入http模块
② 创建web服务器实例
③ 为服务器实例绑定request事件,监听客户端的请求
④ 启动服务器
只要服务器接收到了客户端的请求,就会调用通过server.on( )为服务器绑定的request事件处理函数。如果想在事件处理函数中,访问与客户端相关的数据或属性,可以使用如下的方式:
在服务器的request事件处理函数中,如果想访问与服务器相关的数据或属性,可以使用如下的方式:
当调用res.end( )方法,向客户端发送中文内容的时候,会出现乱码问题,此时,需要手动设置内容的编码格式:
① 获取请求的url地址
② 设置默认的响应内容为404 Not found
③ 判断用户请求的是否为/或/index.html首页
④ 判断用户请求的是否为/about.html关于页面
⑤ 设置Content-Type响应头,防止中文乱码
⑥ 使用res.end( )把内容响应给客户端
编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块。
把代码进行模块化拆分的好处:
模块化规范就是对代码进行模块化的拆分与组合时,需要遵守的那些规则。
例如:
模块化规范的好处:大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用,利人利己。
Node.js中根据模块来源的不同,将模块分为了3大类,分别是:
使用强大的require( )方法,可以加载需要的内置模块、用户自定义模块、第三方模块进行使用。例如:
注意:使用require()方法加载其它模块时,会执行被加载模块中的代码。
和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域。
在每个js自定义模块中都有一个module对象,它里面存储了和当前模块有关的信息,打印如下:
在自定义模块中,可以使用module.exports对象,将模块内的成员共享出去,供外界使用。
外界用require()方法导入自定义模块时,得到的就是module.exports所指向的对象。
const username = \'zs\'
module.exports.username = username
exports.age = 20
exports.sayHello = function() {
console.log(\'大家好!\')
}
使用require()方法导入模块时,导入的结果,永远以module.exports指向的对象为准。
由于module.exports单词写起来比较复杂,为了简化向外共享成员的代码,Node 提供了exports对象。默认情况下,exports和module.exports指向同一个对象。最终共享的结果,还是module.exports指向的对象为准。
时刻谨记,require()模块时,得到的永远是module.exports指向的对象:
注意:为了防止混乱,建议大家不要在同一个模块中同时使用exports和module.exports
nods.js遵循了CommonJS模块化规范,CommonJS规定了模块的特性和各模块之间如何相互依赖。
CommonJS规定:
Node.js中的第三方模块又叫做包。
就像电脑和计算机指的是相同的东西,第三方模块和包指的是同一个概念,只不过叫法不同。
不同于Node.js中的内置模块与自定义模块,包是由第三方个人或团队开发出来的,免费供所有人使用。
注意:Node.js中的包都是免费且开源的,不需要付费即可免费下载使用。
由于Node.js的内置模块仅提供了一些底层的API,导致在基于内置模块进行项目开发的时,效率很低。
包是基于内置模块封装出来的,提供了更高级、更方便的API,极大的提高了开发效率。
包和内置模块之间的关系,类似于jQuery和浏览器内置API之间的关系。
国外有一家IT公司,叫做npm, Inc这家公司旗下有一个非常著名的网站: https://www.npmjs.com/ ,它是全球最大的包共享平台,你可以从这个网站上搜索到任何你需要的包,只要你有足够的耐心!
到目前位置,全球约1100多万的开发人员,通过这个包共享平台,开发并共享了超过120多万个包供我们使用。
npm, Inc.公司提供了一个地址为https://registr.npmjs.org/的服务器,来对外共享所有的包,我们可以从这个服务器上下载自己所需要的包。
注意:
如果想在项目中安装指定名称的包,需要运行如下的命令:
上述的装包命令,可以简写成如下格式:
初次装包完成后,在项目文件夹下多-个叫做node_ modules 的文件夹和package-lock.json的配置文件。
其中:
node_ modules文件夹用来存放所有已安装到项目中的包。require( 导入第三E方包时,就是从这个目录中查找并加载包。
package-lock.json 配置文件用来记录node_ modules 目录下的每一个包的下载信息, 例如包的名字、版本号、下载地址等。
注意:程序员不要手动修改node modules或package-lock.json文件中的任何代码,npm包管理工具会自动维护它们。
在项目根目录中,创建一个叫做 package.json的配置文件,即可用来记录项目中安装了哪些包。从而方便剔除 node_ modules 目录之后,在团队成员之间共享项目的源代码。
注意:今后在项目开发中,一定要把 node_ modules 文件夹,添加到.gitignore忽略文件中。
npm包管理工具提供了一个快捷命令,可以在执行命令时所处的目录中,快速创建package.json这个包管理
配置文件:
注意:
可以运行 npm install命令(或 npm i)一次性安装所有的依赖包:
可以运行npm uninstall命令,来卸载指定的包:
注意: npm uninstall命令执行成功后,会把卸载的包,自动从package.json的dependencies中移除掉。
如果某些包只在项目开发阶段会用到,在项目上线之后不会用到,则建议把这些包记录到devDependencies节点中。与之对应的,如果某些包在开发和项目上线之后都需要用到,则建议把这些包记录到dependencies节点中。
您可以使用如下的命令,将包记录到devDependencies节点中:
下包的镜像源,指的就是下包的服务器地址
为了更方便的切换下包的镜像源,我们可以安装nrm这个小工具,利用nrm提供的终端命令,可以快速查看和切换下包的镜像源。
那些被安装到项目的node_ modules目录中的包,都是项目包。
项目包又分为两类,分别是:
在执行npm install命令时,如果提供了-g参数,则会把包安装为全局包。
全局包会被安装到C:\\Users\\用户目录AppData\\Roaming\\npm\\node_ modules 目录下。
注意:
i5ting_ toc 是一个可以把md文档转为html页面的小工具,使用步骤如下:
在清楚了包的概念、以及如何下载和使用包之后,接下来,我们深入了解一下包的内部结构。
一个规范的包,它的组成结构,必须符合以下3点要求:
注意:以上3点要求是一个规范的包结构必须遵守的格式,关于更多的约束,可以参考如下网址:
https://yarnpkg.com/zh-Hans/docs/package-json
模块在第一次加载后会被缓存。这也意味着多次调用 require( )不会导致模块的代码被执行多次。
注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率
内置模块是由Node.js官方提供的模块,内置模块的加载优先级最高。
例如,require(‘fs’) 始终返回内置的fs模块,即使在node_ modules目录下有名字相同的包也叫做fs。
使用require()加载自定义模块时,必须指定以./或…开头的路径标识符。在加载自定义模块时,如果没有指定./或.这样的路径标识符,则node会把它当作内置模块或第三方模块进行加载。
同时,在使用require0导入自定义模块时,如果省略了文件的扩展名,则Node.js会按顺序分别尝试加载以下的文件:
如果传递给require0的模块标识符不是一个内置模块, 也没有以’/’ 或./’ 开头, 则Node.js会从当前模块的父目录开始,尝试从/node_ modules 文件夹中加载第二彷模块。
如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。
例如,假设在’C:\\Users\\itheima\\project\\foo.js’文件里调用了requre(\'tools\"),则Node.js会按以下顺序查找:
① C:\\Users\\itheima\\project\\node modules\\tools
② C:\\Users\\itheima\\node_ modules\\tools
③ C:\\Users\\node_ modules\\tools
④ C:\\node_ modules\\tools
当把目录作为模块标识符,传递给require0进行加载的时候,有三种加载方式:
官方给出的概念: Express 是基于Node.js平台,快速、开放、极简的Web开发框架。
通俗的理解: Express 的作用和Node.js内置的http模块类似,是专门用来创建Web服务器的。
Express的本质:就是一个npmy上的第三方包,提供了快速创建Web服务器的便捷方法。
Express的中文官网: https://www.expressjs.com.cn/
对于前端程序员来说,最常见的两种服务器,分别是:
Web网站服务器:专门对外提供Web网页资源的服务器。
API 接口服务器:专]对外提供API接口的服务器。
使用Express,我们可以方便、快速的创建Web网站的服务器或API接口的服务器。
在项目所处的目录中,运行如下的终端命令,即可将express安装到项目中使用:
通过app.get( )方法,可以监听客户端的GET请求,具体的语法格式如下:
通过app.post( )方法,可以监听客户端的POST请求,具体的语法格式如下:
通过res.send()方法,可以把处理好的内容,发送给客户端:
通过req.query对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数:
通过req.params对象,可以访问到URL中,通过:匹配到的动态参数:
express提供了一个非常好用的函数, 叫做express.static( ),通过它,我们可以非常方便地创建一个静态资源服务器,例如,通过如下代码就可以将public目录下的图片、CSS 文件、JavaScript 文件对外开放访问了:
如果要托管多个静态资源目录,请多次调用express.static( )函数:
访问静态资源文件时,express.static( ) 函数会根据目录的添加顺序查找所需的文件。
如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下的方式:
在编写调试Node.js项目的时候,如果修改了项目的代码,则需要频繁的手动close掉,然后再重新启动,非常繁琐。
现在,我们可以使用nodemon (https://www.npmjs.com/package/nodemon) 这个工具, 它能够监听项目文件的变动,当代码被修改后,nodemon 会自动帮我们重启项目,极大方便了开发和调试。
在终端中,运行如下命令,即可将nodemon安装为全局可用的工具:
在Express中,路由指的是客户端的请求与服务器处理函数之间的映射关系。
Express中的路由分3部分组成,分别是请求的类型、请求的URL地址、处理函数,格式如下:
每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数。
在匹配时,会按照路由的顺序进行匹配,如果请求类型和请求的URL同时匹配成功,则Express会将这次请求,转交给对应的function函数进行处理。
在Express中使用路由最简单的方式,就是把路由挂载到app上,示例代码如下:
为了方便对路由进行模块化的管理,Express 不建议将路由直接挂载到app上,而是推荐将路由抽离为单独的模块。
将路由抽离为单独模块的步骤如下:
类似于托管静态资源时,为静态资源统-挂载访问前缀一 样, 路由模块添加前缀的方式也非常简单:
当一个请求到达Express的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理。
next函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一一个中间件或路由。.
可以通过如下的方式,定义一个最简单的中间件函数:
多个中间件之间,共享同一份req和res.基于这样的特性,我们可以在上游的中间件中,统-为req或res对象添加自定义的属性或方法,供下游的中间件或路由进行使用。
不使用app.use( )定义的中间件,叫做局部生效的中间件,示例代码如下:
通过app.use( )或app.get( )或app.post(),绑定到app实例上的中间件,叫做应用级别的中间件,代码示例如下:
绑定到express.Router( )实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。只不过,应用级别中间件是绑定到app实例上,路由级别中间件绑定到router实例上,代码示例如下:
错误级别中间件的作用:专门]用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
格式:错误级别中间件的function处理函数中,必须有4个形参。,形参顺序从前到后,分别是(err, req, res, next)。
注意:错误级别的中间件,必须注册在所有路由之后!
自Express 4.16.0版本开始,Express 内置了3个常用的中间件,极大的提高了Express项目的开发效率和体验:
非Express官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件。在项目中,大家可以按需下载并配置第三方中间件,从而提高项目的开发效率。
例如:在express@4.16.0之前的版本中,经常使用body-parser这个第三方中间件,来解析请求体数据。使用步骤如下:
注意: Express 内置的express.urlencoded中间件,就是基于body-parser这个第=三方中间件进一步封装出来的。
自己手动模拟一个类似于express.urlencoded这样的中间件,来解析POST提交到服务器的表单数据。
实现步骤:
在中间件中,需要监听req对象的data事件,来获取客户端发送到服务器的数据。
如果数据量比较大,无法一次性发送完毕, 则客户端会把数据切割后,分批发送到服务器。所以data事件可能会触发多次,每一次触发data事件时,获取到数据只是完整数据的一部分, 需要手动对接收到的数据进行拼接。
当请求体数据接收完毕之后,会自动触发req的end事件。
因此,我们可以在req的end事件中,拿到并处理完整的请求体数据。示例代码如下:
Node.js内置了一个querystring模块,专门用来处理查询字符串。通过这个模块提供的parse()函数,可以轻松把查询字符串,解析成对象的格式。示例代码如下:
上游的中间件和 下游的中间件及路由之间,共享同一份req 和res。 因此,我们可以将解析出来的数据, 挂载为req的自定义属性,命名为req.body,供下游使用。示例代码如下:
为了优化代码的结构,我们可以把自定义的中间件函数,封装为独立的模块,示例代码如下:
注意:如果要获取URL-encoded格式的请求体数据,必须配置中间件app.use(express.urlencoded({ extended: false }))
刚才编写的GET和POST接口,存在一-个很严重的问题:不支持跨域请求。
解决接口跨域问题的方案主要有两种:
cors是Express的一个第三方中间件。通过安装和配置cors中间件,可以很方便地解决跨域问题。
使用步骤分为如下3步:
CORS (Cross-Origin Resource Sharing,跨域资源共享)由-系列HTTP响应头组成,这些HTTP响应头决定浏览器是否阻止前端JS代码跨域获取资源。
浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了CORS相关的HTTP响应头,就可以解除浏览器端的跨域访问限制。
响应头部中可以携带一个Access-Control-Allow-Origin字段,其语法如下:
其中,origin参数的值指定了允许访问该资源的外域URL。
例如,下面的字段值将只允许来自http://itcast.cn 的请求:
如果指定了Access-Control-Allow-Origin字段的值为通配符*,表示允许来自任何域的请求,示例代码如下:
默认情况下,CORS仅支持客户端向服务器发送如下的9个请求头:
Accept、Accept-Language、 Content-Language DPR、Downlink、 Save-Data、 Viewport-Width、 Width 、Content-Type (值仅限于text/plain、multipart/form-data、 application/x-www-form-urlencoded 三者之一)
如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过Access-Control-Allow-Headers对额外的请求头进行声明,否则这次请求会失败!
默认情况下,CORS仅支持客户端发起GET、POST、 HEAD请求。
如果客户端希望通过PUT、DELETE 等方式请求服务器的资源,则需要在服务器端,通过Access-Control-Alow-Methods来指明实际请求所允许使用的HTTP方法。
客户端在请求CORS接口时,根据请求方式和请求头的不同,可以将CORS的请求分为两大类,分别是:
同时满足以下两大条件的请求,就属于简单请求:
① 请求方式: GET、POST、HEAD三者之一
② HTTP 头部信息不超过以下几种字段“ 无自定义头部字段、Accept、 Accept-Language、
Content-Language、 DPR、Downlink、Save-Data、 Viewport-Width、 Width 、Content-Type (只有三个值application/x-www-form-urlencoded、multipart/form-data、 text/plain)
只要符合以下任何一个条件的请求,都需要进行预检请求:
在浏览器与服务器正式通信之前,浏览器会先发送OPTION请求进行预检,以获知服务器是否允许该实际请求,所以这一次的QPTION请求称为\"预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。
简单请求的特点:客户端与服务器之间只会发生一次请求。
预检请求的特点:客户端与服务器之间会发生两次请求,OPTION 预检请求成功之后,才会发起真正的请求。
概念:浏览器端通过
① JSONP不属于真正的Ajax请求,因为它没有使用XMLHttpRequest这个对象。
② JSONP 仅支持GET请求,不支持POST、PUT、 DELETE 等请求。
如果项目中已经配置了CORS跨域资源共享,为了防止冲突,必须在配置CORS中间件之前声明JSONP的接口。否则JSONP接口会被处理成开启了CORS的接口。示例代码如下:
调用$.ajax( )函数,提供JSONP的配置选项,从而发起JSONP请求,示例代码如下:
重点掌握如何使用SQL从数据表中:
查询数据(select) 、 插入数据(insert into)、 更新数据(update) 、删除数据(delete)
额外需要掌握的4种SQL语法:
where条件、and和or运算符、order by排序、、count(*) 函数
SELECT语句用于从表中查询数据。执行的结果被存储在一个结果表中(称为结果集)。语法格式如下:
注意: SQL语句中的关键字对大小写不敏感。SELECT 等效于select, FROM等效于from。
我们希望从users表中选取所有的列,可以使用符号*取代列的名称,示例如下:
如需获取名为\"username“和”password\"的列的内容(从名为\"users\"的数据库表),请使用下面的SELECT语句:
INSERT INTO语句用于向数据表中插入新的数据行,语法格式如下:
向users表中,插入一条username为tony stark, password 为098123的用户数据,示例如下:
把users表中id为7的用户密码,更新为888888。示例如下:
把users表中id为2的用户密码和用户状态,分别更新为admin123和1。示例如下:
WHERE子句用于限定选择的标准。在SELECT、UPDATE、DELETE 语句中,皆可使用WHERE子句来限定选择的标准。
下面的运算符可在WHERE子句中使用,用来限定选择的标准:
注意:在某些版本的SQL中,操作符<>可以写为!=
AND和OR可在WHERE子语句中把两个或多个条件结合起来。
AND表示必须同时满足多个条件,相当于JavaScript中的&&运算符,例如if(a!== 10 &&a!== 20)
OR表示只要满足任意一个条件即可,相当于JavaScript中的|I 运算符,例如if(a!== 10||a!== 20)
使用AND来显示所有status为0,并且id小于3的用户:
使用OR来显示所有status 为1,或者username为zs的用户:
对users表中的数据,按照status字段进行升序排序,示例如下:
对users表中的数据,按照id字段进行降序排序,示例如下:
对users表中的数据,先按照status字段进行降序排序,再按照username的字母顺序,进行升序排序,示例如下:
COUNT(*)函数用于返回查询结果的总数据条数,语法格式如下:
如果希望给查询出来的列名称设置别名,可以使用AS关键字示例如下:
mysql模块是托管于npm上的第三方模块。它提供了在Node.js项目中连接和操作MySQL数据库的能力。
想要在项目中使用它,需要先运行如下命令,将mysql安装为项目的依赖包:
在使用mysql模块操作MySQL数据库之前,必须先对mysql模块进行必要的配置,主要的配置步骤如下:
调用db.query()函数,指定要执行的SQL语句,通过回调函数拿到执行的结果:
向users表中新增数据,其中username为Spider-Man, password 为pcc321。示例代码如下:
**
**
向表中新增数据时,如果数据对象的每个属性和数据表的字段一 一对应, 则可以通过如下方式快速插入数据:
更新表数据时,如果数据对象的每个属性和数据表的字段一 一对应, 则可以通过如下方式快速更新表数据:
在删除数据时,推荐根据id这样的唯一标识, 来删除对应的数据。示例如下:
使用DELETE语句,会把真正的把数据从表中删除掉。为了保险起见,推荐使用标记删除的形式,来模拟删除的动作。
所谓的标记删除,就是在表中设置类似于status这样的状态字段,来标记当前这条数据是否被删除。
当用户执行了删除的动作时,我们并没有执行DELETE语句把数据删除掉,而是执行了UPDATE语句,将这条数据对应的status字段标记为删除即可。
目前主流的Web开发模式有两种,分别是:
服务端渲染的概念:服务器 发送给客户端的HTML页面,是在服务器通过字符串的拼接,动态生成的。因此,客户端不需要使用Ajax这样的技术额外请求页面的数据。代码示例如下:
前后端分离的概念:前后端分离的开发模式,依赖于Ajax技术的广泛应用。简而言之,前后端分离的Web开发模式,就是后端只负责提供API接口,前端使用Ajax调用接口的开发模式。
不谈业务场景而盲目选择使用何种开发模式都是耍流氓。
另外,具体使用何种开发模式并不是绝对的,为了同时兼顾了首页的渲染速度和前后端分离的开发效率,一些网站采用了首屏服务器端渲染+其他页面前后端分离的开发模式。
身份认证(Authentication) 又称\"身份验证”、“鉴权” ,是指通过一定的手段,完成对用户身份的确认。
身份认证的目的,是为了确认当前所声称为某种身份的用户,确实是所声称的用户。例如,你去找快递员取快递,你要怎么证明这份快递是你的。
在互联网项目开发中,如何对用户的身份进行认证,是一个值得深入探讨的问题。例如,如何才能保证网站不会错误的将“马云的存款数额”显示到” 马化腾的账户”上。
对于服务端渲染和前后端分离这两种开发模式来说,分别有着不同的身份认证方案:
了解HTTP协议的无状态性是进一步学习Session认证机制的必要前提。
HTTP协议的无状态性,指的是客户端的每次HTTP请求都是独立的,连续多个请求之间没有直接的关系,服务器不会主动保留每次HTTP请求的状态。
对于超市来说,为了方便收银员在进行结算时给VIP用户打折,超市可以为每个VIP用户发放会员卡。
注意:现实生活中的会员卡身份认证方式,在Web开发中的专业术语叫做Cookie.
Cookie是存储在用户浏览器中的一段不超过4 KB的字符串。它由一个名称(Name) 、-个值(Value) 和其它几个用于控制Cookie有效期、安全性、使用范围的可选属性组成。
不同域名下的Cookie各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的Cookie一同发送到服务器。
Cookie的几大特性:
客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身 份认证的Cookie,客户端会自动将Cookie保存在浏览器中。
随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的Cookie,通过请求头的形式发送给服务器,服务器即可验明客户端的身份。
由于Cookie是存储在浏览器中的,而且浏览器也提供了读写Cookie的API,因此Cookie很容易被伪造,不具有安全性。因此不建议服务器将重要的隐私数据,通过Cookie的形式发送给浏览器。
注意:千万不要使用Cookie存储重要且隐私的数据! 比如用户的身份信息、密码等。
为了防止客户伪造会员卡,收银员在拿到客户出示的会员卡之后,可以在收银机上进行刷卡认证。只有收银机确认存在的会员卡,才能被正常使用。
这种 ” 会员卡 + 刷卡认证” 的设计理念,就是Session认证机制的精髓。
在Express项目中,只需要安装express-session中间件,即可在项目中使用Session认证:
express-session中间件安装成功后,需要通过app.use(来注册session中间件,示例代码如下:
当express-session中间件配置成功后,即可通过req.session来访问和使用session对象,从而存储用户的关键信息:
可以直接从req.session对象上获取之前存储的数据,示例代码如下:
调用req.session.destroy()函数,即可清空服务器保存的session信息。
Session认证机制需要配合Cookie才能实现。由于Cookie默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域Session认证。
注意:
JWT (英文全称: JSON Web Token)是目前最流行的跨域认证解决方案。
总结:用户的信息通过Token字符串的形式,保存在客户端浏览器中。服务器通过还原Token字符串的形式来认证用户的身份。
JWT通常由三部分组成,分别是Header (头部)、Payload (有效荷载)、Signature (签名)。
三者之间使用英文的\".\" 分隔,格式如下:
下面是JWT字符串的示例:
JWT的三个组成部分,从前到后分别是Header、Payload、 Signature.
其中:
客户端收到服务器返回的JWT之后,通常会将它储存在localStorage或sessionStorage中。
此后,客户端每次与服务器通信,都要带上这个JWT的字符串,从而进行身份认证。推荐的做法是把JWT放在HTTP请求头的Authorization字段中,格式如下:
jsonwebtoken用于生成JWT字符串
express-jwt 用于将JWT字符串解析还原成JSON对象
为了保证JWT字符串的安全性,防止JWT字符串在网络传输过程中被别人破解,我们需要专i门]定义一一个用于加密和解密的secret密钥:
调用jsonwebtoken包提供的sign()方法,将用户的信息加密成JWT字符串,响应给客户端:
客户端每次在访问那些有权限接口的时候,都需要主动通过请求头中的Authorization字段,将Token字符串发送到服务器进行身份认证。
此时,服务器可以通过express-jwt这个中间件,自动将客户端发送过来的Token解析还原成JSON对象:
当express-jwt这个中间件配置成功之后,即可在那些有权限的接口中,使用req.user对象,来访问从JWT字符串中解析出来的用户信息了,示例代码如下:
当使用express-jwt解析Token字符串时,如果客户端发送过来的Token字符串过期或不合法,会产生一个解析失败的错误,影响项目的正常运行。我们可以通过Express的错误中间件,捕获这个错误并进行相关的处理,示例代码如下: