后端
存储数据
现代后端应用通常包括某种数据库,经常不止一种。数据库是信息的集合。
有许多不同的数据库,但是我们将它分为两位:关系型数据库和非关系型数据库。
- 关系型数据库把信息存储在有行有列的表中,非关系型数据库通常用其他方式,类似键值对或文件存储模型
- SQL是一种程序语言用来访问和修改关系型数据库的数据。
- 常用的关系型数据库有MySQL和PostgreSQL,同时流行的非关系型数据库有MongoDB和Redis
除了数据库本身,后端还需要一种方法以编程的方式访问、修改、分析数据库里的数据
api
当用户在电商网站浏览到某个特定商品时,这些商品的价格清单会存储在数据库,当他们购买它时,数据库需要更新这个商品的存货。实际上,大多数商品的阅读、更新、删除详情、都存储在数据库上
为了保持与数据交互的一致性,后端通常会包括web api
API代表:Apilication Programming Interface,它可以指代很多不同的东西,但api是一组预定义的交互方式和规则,用于和web应用的数据交互, 通常是通过http-request和http-response来实现
让我们再次回到在电商网站购买商品的例子,但这次,我们会想象web API是如何实现这些的
当用户按下提交订单的按钮时,将会触发一个request将其引导至网站的另一个页面——订单确定页面,但同时会触发,前端发送一个客户不可见的额外请求到WebAPI,这样数据库就会更新订单的信息到数据库中
认证和权限
我们希望服务器端逻辑处理的另外两个概念是身份验证(authentication)和授权(authorization)。
身份验证 是验证用户身份的过程。一种常见的身份验证方式是使用用户名和密码进行登录。这些凭证需要在后端的数据库中安全存储,并在每次访问时进行检查。Web 应用程序也可以使用外部资源进行身份验证。你可能曾使用 Facebook、Google 或 Github 的账户登录网站或应用程序;这也是一种身份验证过程。
授权 控制哪些用户可以访问哪些资源和执行哪些操作。某些应用页面,例如编辑社交媒体个人资料的页面,只允许该用户访问。其他操作,如删除帖子,通常也会受到类似的限制。
在构建一个健壮的 Web 应用后端时,我们需要将身份验证(这个用户是谁?他们真的是他们声称的那个人吗?)和授权(谁被允许做什么,看到什么?)纳入服务器端逻辑中,以确保我们创建的是安全、个性化和动态的内容。
不同的后端框架
不像前端必须使用html、CSS、js,技术栈提供了许多可以灵活创建后端应用的方法。开发者可以用不同的语言构建后端,例如php,java,js,python等
你不用为了后端的健壮性而反复造轮子。大多数开发者使用框架(用于构造后端的组织架构并提供高效的方法去实现不同的任务的工具集合)
例如:
预备知识
同步/异步概念
在Node.js和js的开发中,我们使用同步代码和异步代码混合,异步代码一个常见的示例就是promise
Promise是Js内置的对象,可以直接使用,表示异步操作的最终结果。Promise有以下三种不同的状态:
- pending:结果为undefined,expression正在等待结果
- fulfilled:promise已成功完成并返回一个值
- rejected:preomise没有成功完成,结果是一个错误对象
在下面的代码中,定义了一个新的Promise,并传入了一个函数,该函数接受两个参数,一个表示成功状态,一个表示失败状态,然后我们将Promise返回的值打印到控制台,并用catch处理错误
// Creating a new Promise and saving it to the testLuck variable. Two arguments are being passed, one for when the promise resolves, and one for if the promise gets rejected.
const testLuck = new Promise((resolve, reject) => {
if (Math.random() < 0.5) {
resolve('Lucky winner!')
} else {
reject(new Error('Unlucky!'))
}
});
testLuck.then(message => {
console.log(message) // Log the resolved value of the Promise
}).catch(error => {
console.error(error) // Log the rejected error of the Promise
});
Async/Await
async…await允许开发者更轻松地实现基于promise的代码
当关键词async和函数声明一起使用时,会创建一个返回promise的异步函数。异步函数允许我们用await关键字去阻塞事件循环直到promise的状态为settle。await关键字允许我们将promise解析后的值赋给变量
看一下下面的代码。async关键字定义了箭头函数,在函数体内,我们创建了一个新的 Promise,该 Promise 传入了一个函数,该函数会在 5 秒后执行。我们使用 await 等待 Promise 解析,并将返回的值保存到 finalResult 变量中,最后将 Promise 的输出记录到控制台中。
// Creating a new promise that runs the function in the setTimeout after 5 seconds.
const newPromise = new Promise((resolve, reject) => {
setTimeout(() => resolve("All done!"), 5000);
});
// Creating an asynchronous function using an arrow expression and saving it to a the variable asyncFunction.
const asyncFunction = async () => {
// Awaiting the promise to resolve and saving the result to the variable finalResult.
const finalResult = await newPromise;
// Logging the result of the promise to the console
console.log(finalResult); // Output: All done!
}
asyncFunction();
setInterval() and setTimeout()
除了使用async……await语法外,还可以使用setInterval() 和 setTimeout() 函数。在上一节的示例代码中,我们在 Promise 构造函数中创建了一个 setTimeout() 实例。
setInterval() 函数以指定的时间间隔(毫秒)执行一段代码。setInterval() 函数需要两个参数:函数的名称(即将要执行的代码块)和毫秒数(即执行该函数的时间间隔)。此外,我们还可以传递额外的参数,这些参数将作为 setInterval() 执行的函数的参数传递。
setInterval() 函数会一直执行,直到调用 clearInterval() 函数停止它,或者 Node 进程退出。在下面的代码块中,showAlert() 函数中的 setInterval() 将每 5000 毫秒显示一个警告框。
// Defining a function that instantiates setInterval
const showAlert = () => {
// Calling setInterval() and passing a function that shows an alert every 5 seconds.
setInterval(() => {
alert('I show every 5 seconds!')
}, 5000);
};
// Calling the newInterval() function that calls the setInterval
showAlert();
setTimeout()函数在一段特定的时间后执行块内的代码,并且只执行一次。setTImeout接收和setInterval相同的函数,使用clearTimeout可以阻止指定特定函数的执行
在下面的代码块中,声明了一个名为 showTimeout() 的箭头函数。随后,setTimeout() 函数被定义,并将在 5 秒后显示一个警告框。
// Defining a function that calls setTimeout
const showTimeout = () => {
// Calling setTimeout() that passes a function that shows an alert after 5 seconds.
setTimeout(() => {
alert('I only show once after 5 seconds!');
}, 5000);
};
// Calling the showTimeout() function
showTimeout();
什么是Json
介绍
世界充满了数据,知道如何去处理各种各样的数据正在变得越来越重要。作为一个程序员,我们需要能够将已填充的数据结构从我们选择的任何编程语言转换为其他语言和平台可识别和可读的程序
幸运的是,存在这样一种数据交换格式
什么是Json
Json,或者说JavaScript Object Notation,是一种流行的,依赖于语言的,存储和交换数据的标准格式。是ECMA——为了制定标准和交流所建立在1961年的行业协会所提出的。json已经在事实上成为标准,用于在所有编程语言之间存储和传输的工具
json的应用
json在web应用和客户端(例如浏览器,服务器)的数据转换上有着大量的应用
一个数据传输典型的例子就是填写网络表单。表单数据从HTML转换为JavaScript对象,再转换为Json对象,并发送到远程web服务器进行处理。这些交互可以是简单的搜索引擎查询,也可以是多页的求职申请。
当公司向其他应用公开数据时,例如分享Spotify上的音乐库或者分享google地图上的数据,这些信息也会以json的格式传输。这样,无论什么编程语言,任何应用都可以收集和解析这些数据
json语法
因为json是js衍生出的语言,所以它看起来和js的对象很相似
举例一个Json对象:
{
"student": {
"name": "Rumaisa Mahoney",
"age": 30,
"fullTime": true,
"languages": [ "JavaScript", "HTML", "CSS" ],
"GPA": 3.9,
"favoriteSubject": null
}
}
请注意以下Json的语法规则:
- 花括号{……}用于包含对象
- 方括号[……]用于包含数组
- 数据以名称-值对的形式存储,名称和值之间用冒号
:分隔。 - 每个名称-值对之间用逗号
,分隔。同样,数组中的每个项也用逗号分隔。但禁止使用尾随逗号。 - JSON 属性名称必须使用双引号
""包裹,即使 JavaScript 的名称不要求如此严格。
json数据类型
一个Json的数据类型必为以下几种之一:
- string (double-quoted)
- number (integer or floating point)
- object (name-value pair)
- array (comma-delimited)
- boolean (true or false)
- null
特别的是,json并没有覆盖所有数据类型,json中不支持的类型例如date将会以字符串的形式存储,并在特定时转换为数据结构,以下是符合 ISO 8601 标准的国际公认日期格式:
"2014-01-01T23:28:56.782Z"
上边的格式包含了类似日期和时间的数据,然而,作为一个字符串,编程语言很难直接去使用它。方便的是,每种编程语言都内置了Json处理功能,可以将该字符串转换成更可读,更易用的格式,例如:
Wed Jan 01 2014 13:28:56 GMT-1000 (Hawaiian Standard Time)
node.js
介绍
在很长时间以来,js只能在浏览器上执行。web开发者不得不在后端和前端使用不同的编程语言。这也意味着,即使JavaScript发展成为一种更健壮,更强大的语言,它也只是一种限于前端的语言。
尽管多次尝试创建脱离浏览器的JavaScript运行环境,但由 Ryan Dahl 于 2009 年发明的 Node.js 获得了前所未有的流行,并且现在已经被Netflix, Uber, Paypal, and eBay在内的顶级公司所使用。
Node.js是一个JavaScript的运行时,或者说是一个允许我们在浏览器外执行JavaScript代码的环境。“运行时”会将高级,可读性强的程序代码转换并编译为计算机可以执行的代码。尽管Node创建时的目标是构建js写的web服务和web应用,但它也可以用来创建命令行应用程序或桌面应用程序。
在本次课中,我们将探索Node的特性,使你更轻松地运行JavaScript在NOde环境上,更加熟悉Node的特性。对于更高级的开发,Node.js可以与各种强大的框架结合使用(例如Express.js框架)用来高效地创造web后端应用
node REPL
REPL是Real-eval-print-loop(取值,求值,输出,循环)的缩写。它是一个循环执行的程序,会重复经历三个不同的状态:
- 读取(read):程序从用户那里读取输入
- 求值(eval):程序对用户的输入进行计算或解析
- 输出(print):程序将计算或解析的结果打印到控制台
然后,它会再次循环执行这些状态。
当你安装Node.js时,它自带一个内置的REPL,你可以在终端中输入命令Node(后面不追加任何内容)并按回车来访问REPL,终端会>符号,表示REPL已启动,并等待你的输入
Node REPL会逐行解析和执行你的输入。
默认情况下,当你按下回车键时,表示输入已经准备好进行求值
如果你想输入多行代码后再一次性执行,可以在 REPL 中输入 .editor 进入“编辑器”模式。在该模式下,当你准备好执行输入内容时,按下 Control + D 即可。
每个 REPL 会话共享同一块内存,你可以访问在会话中定义的变量或函数,直到你退出 REPL
REPL 在执行计算、学习编程语言和开发代码方面非常有用。它是一个可以探索语言特性、尝试新代码并获得即时反馈的环境。在浏览器或网站之外掌握这种技能,会让你更具掌控感。
Node 运行环境除了包含 JavaScript 语言内置的功能外,还提供了许多特定于 Node.js 的全局元素。所有 Node.js 特有的全局属性都属于 global 对象,该对象包含许多在 Node 环境中的任何地方都可用的有用属性和方法。
让我们尝试使用 Node REPL,这将是一个探索 Node global 对象的好方法!
Node.js与浏览器的一个主要区别是:尝试访问windows对象会报错,因为Windows是浏览器中的JavaScript对象,它包含了DOM,而在Node.js环境中,我们没有DOM,因此也就不存在Windows对象
在node上运行程序
node是设计来做web服务端的开发,并为此提供了许多精心设计的功能。
然而从最基本的层面来看,它的作用是在我们的计算机上运行JavaScript程序,而不仅仅是在浏览器控制台或嵌入html运行
在本课程中,我们将探索一些Node.js环境中特有的功能和属性,但首先,我们看看如何运行一个程序
我们需要创建一个以.js作为拓展名的文件,我们将其命名为myProgram.js,然后,用文本编辑器打开该文件并添加代码:
// Inside myProgram.js
console.log('Hello World');
我们的代码已经完成!现在,我们需要执行它。
我们将打开终端,并导航到包含 myProgram.js 的目录。
最后,在终端中输入命令:node myProgram.js。
$ node myProgram.js
程序的结果会打印在终端上:
Hello World
核心模块
模块化(Modularity)是一种软件设计技术,在这种技术中,一个程序被划分为不同的部分,每个部分都提供整体功能的一个独立组件。
模块化的代码组织方式通过将相关的函数、类和变量分隔到不同的文件中,使得在大型项目中代码的管理和复用变得更加容易。这些独立的模块共同组成一个完整的系统。
模块化对于构建可扩展的程序至关重要,这些程序通常需要集成库和框架,并将程序的各个部分划分为可管理的单元。本质上,模块就是存放在文件中的一组代码。与将整个程序写在单个文件中不同,模块化方法根据不同的功能点,将代码组织到多个文件中。这些文件可以使用 require() 函数包含到其他文件中。
为了避免开发者每次都从零开始,Node.js 提供了一些内置模块(core modules),用于高效执行常见任务。这些核心模块是 Node.js 源代码的一部分,位于 lib/ 目录中。可以通过 require() 函数,并传入模块名称的字符串,来引入核心模块,例如:
// Require in the 'events' core module:
const events = require('events');
上面的示例展示了如何在文件中引入 events 模块,并将其存储在 events 变量中。此时无需深入理解该模块的具体细节,但 events 模块是 Node.js 的一个核心模块,提供了用于处理事件的关键功能。你将在后续课程中进一步学习它。
一些核心模块实际上会在其他核心模块内部被使用。例如,util 模块可以在 console 模块中用于格式化消息。在本课程中,我们将学习这两个模块,以及另外两个常用的核心模块:process 和 os。
Node.js 具有多个核心模块,远超本课程所能涵盖的范围。我们将学习如何获取完整的模块列表,并在接下来的练习中深入探讨上述几个核心模块。
在REPL中,我们可以通过输入以下命令获取完整的核心模块列表:
require('module').builtinModules
正如你所看到的,Node.js 已经内置了许多模块,并且可以直接使用! 在接下来的练习中,我们将更详细地探讨其中一些更实用的模块。
Console模块
Node.js中最常用的核心模块之一就是console模块。在Node.js中,终端被通过文本来发送和接收反馈,经常用于debug。听起来和我们使用console在浏览器上差不多。这是因为在node.js中,内置的console模块会导出一个全局console对象,为终端提供类似的功能
console对象提供了许多熟悉方法:
- log:在终端打印message
- assert:如果value是false的话在终端打印错误信息
- table:在控制台将对象或数组打印成一个表格
因为console是全局模块,它的方法可以在任何地方被调用,不需要加require方法
进程模块
在计算机科学中,进程是计算机程序正在被执行的实例、如果你是windows系统可以打开任务管理器看运行在电脑上的各种进程,Node具有一个全局process对象,其中包括许多有用的方法和关于当前进程的信息。
process.env 对象包含一个 PWD 属性,该属性保存了当前进程所在目录的字符串。
在程序中,根据当前环境编写一些 if/else 逻辑可能会很有用——例如,一个 Web 应用在开发阶段可能会执行与上线后不同的任务。我们可以将这些环境信息存储在 process.env 中。
一种常见的约定是,在 process.env 中添加一个 NODE_ENV 属性,并将其值设为 "production"(生产环境)或 "development"(开发环境)。
(process.env.NODE_ENV === 'development'){
console.log('Testing! Testing! Does everything work?');
}
process.memoryUsage返回一个当前进程的CPU需求信息。它返回的属性类似以下内容
{ rss: 26247168,
heapTotal: 5767168,
heapUsed: 3573032,
external: 8772 }
在不同的上下文中,堆(Heap)的意思也不同,它可以指一种特定的数据结构,也可以指一种特定的数据结构,也可以指一块计算机内存。
process.memoryUsage().heapUsed 方法会返回一个数值,表示当前进程使用了多少字节的内存。具体来说,它表示进程使用的 JavaScript 堆内存的字节数。这是 Node.js 内部用于存储 JavaScript 对象、数组、函数等数据的内存。
process.memoryUsage() 方法还可以返回其他内存相关的信息,比如:
rss(常驻集大小):进程使用的总内存,包括堆内存、栈内存等。heapTotal:V8 引擎分配的总堆内存大小。external:C++ 内部对象使用的内存(不是 V8 的内存)。
process.argv 属性保存了一个数组,该数组包含了当前进程启动时提供的命令行参数。数组的第一个元素是运行该进程的 Node 绝对路径,第二个元素是正在运行的文件的路径。从第三个元素开始,数组包含的是启动进程时提供的所有命令行参数。命令行参数之间使用空格分隔。
node myProgram.js testing several features
console.log(process.argv[3]); // Prints 'several'
os模块
在开发或调试应用程序时,了解程序运行的计算机、操作系统和网络信息会很有帮助。在 Node.js 之前,由于 JavaScript 仅限于在浏览器中运行,无法获取这些信息。
然而,Node.js 是一个 JavaScript 运行时,意味着它可以在浏览器之外执行代码,因此我们能够通过 os 核心模块访问许多相关信息。
与 process 和 console 不同,os 模块 不是全局对象,需要在文件中手动引入才能使用。
你可以通过以下方式在文件中引入 os 模块:
const os = require('os');
将 os 模块保存到 os 变量后,你可以调用以下方法:
os.type()—— 返回计算机的操作系统类型。os.arch()—— 返回操作系统的 CPU 架构。os.networkInterfaces()—— 返回计算机的网络接口信息,例如 IP 地址和 MAC 地址。os.homedir()—— 返回当前用户的主目录。os.hostname()—— 返回操作系统的主机名。os.uptime()—— 返回系统的运行时间(单位:秒)。
来看下面这个例子:
const os = require('os');
const local = {
'Home Directory': os.homedir(),
'Operating System': os.type(),
'Last Reboot': os.uptime()
}
在上例的代码中,我们首先引入os模块,并将其存储在变量os中
在其下方,我们创建了一个对象local,用于存储用户计算机的一些信息,包括:
-
主目录的名称
-
操作系统的类型
-
计算机上次重启以来的运行时间
{
'Home Directory': '/Users/luca',
'Operating System': 'Darwin',
'Time since reboot': 86997
}
当我们运行程序时,local 对象会存储所有请求的信息:
- 用户的主目录 ——
'/Users/luca' - 操作系统 ——
'Darwin'(Darwin 是 macOS 的底层操作系统) - 计算机上次重启以来的运行时间 ——
86997秒