在本单元中,你将学习关于 API(应用程序编程接口)的知识。
本单元的目标是学习如何使用异步 JavaScript,这将使你能够最终向 API(应用程序编程接口)发出 HTTP 请求。与 API 的互动将使你能够处理存储在远程服务器上的数据,包括来自第三方网站(如 Instagram 和 Reddit)的数据。在发出 HTTP 请求时,了解一般的网页安全原则也非常重要。
完成本单元后,你将能够:
- 使用
async-await
和promises
语法编写异步 JavaScript - 解释不同类型的 HTTP 请求
- 描述 REST 协议
- 处理 JSON 数据
- 向外部 Web API 发出 HTTP 请求
- 描述 Web 安全性的重要性
异步编程
探索异步编程及其如何使应用程序能够以非顺序的方式运行操作。
什么是同步代码?
在定义异步代码之前,首先让我们了解一下同步代码。我们甚至不需要从代码开始,而是先用一个现实生活中的例子来理解。
假设我们正在建造一座房子。首先,我们需要打好房子的基础,铺设砖块。然后,我们逐层堆叠砖块,从地基开始建造房子。我们不能跳过某一层期待房子依然稳固。因此,铺设砖块的过程是同步进行的,即按顺序进行。
同样,同步代码是按顺序执行的------它从文件顶部开始,逐行执行,直到文件的结尾。这种行为被称为阻塞(或阻塞代码),因为每一行代码都必须等上一行执行完成后才能开始执行。
什么是异步代码?
让我们再次用一个现实生活中的例子来说明,就像做蛋糕。我们可以在等待烤箱预热的同时准备蛋糕的原料。烤箱预热和准备原料是可以同时进行的,因此这些操作是异步的。
类似地,异步代码可以与已经在运行的其他代码并行执行。我们不需要等其他代码执行完毕才能执行新的代码,应用程序可以节省时间并提高效率。这种行为被认为是非阻塞的。
异步代码的工作原理
对于大多数编程语言,执行异步代码的能力取决于应用程序能够访问的线程数量。我们可以把线程看作是计算机为应用程序执行任务提供的一种资源。通常,一个线程允许应用程序完成一个任务。如果我们回到建房子的例子,计算机的线程任务可能是这样的:
线程1:建造房子基础 -> 建造墙壁 -> 构建地板
单线程适用于像建房子这样的同步任务。然而,在做蛋糕的例子中,线程可能需要这样分配:
线程1:预热烤箱
线程2:准备原料 -> 烤蛋糕
我们不会深入讨论应用程序可以访问多少线程,但我们应该注意到,线程越多,就可以同时运行更多任务。此外,在大多数现代计算机中,多线程通过具有多个核心的 CPU 或其他技术实现。
异步代码在 Web 开发中的应用
类似于异步行为在做蛋糕时的有用性,它在网页编程中也很有用。如果我们在浏览器中使用同步(阻塞)代码,可能会阻止用户在代码执行完成之前与 Web 应用程序进行交互。这并不是一个良好的用户体验。如果我们的应用加载时间过长,用户可能会认为出了问题,甚至可能选择浏览其他网站!
然而,如果我们选择异步方法,我们可以减少等待时间。我们只加载用户交互所需的代码,然后在后台加载其他代码。通过异步代码,我们可以创建更好的用户体验,并使应用程序更加高效!
异步JavaScript
Web 开发中的异步代码
JavaScript 通过异步代码为我们提供了流畅的网页浏览体验。网站通常允许我们执行不同的交互操作,比如滚动内容、点击创建弹出窗口、输入文本等。当一个网站能够响应用户的不同操作时,它很可能正在使用异步 JavaScript 代码。这种代码考虑了用户如何使用网站,而不会阻止他们(即不强制要求用户等待一个操作完成后才能继续进行下一个操作)。
作为开发人员,我们的任务是思考完成任务所需的时间,并围绕等待时间进行规划。像联系后端获取信息、查询数据库获取用户信息或向外部服务器发送请求(如第三方 API)等任务需要不同的时间。由于我们不确定什么时候能收到这些信息,我们可以使用异步代码在后台运行这些任务。让我们来看看 JavaScript 如何处理异步代码。
JavaScript 和异步代码
JavaScript 是一种单线程语言。这意味着它只有一个线程,能够一次执行一个任务。然而,JavaScript 拥有一个叫做事件循环的设计,它使得即使只有一个线程,也能执行异步任务(稍后会详细讲解!)。让我们来看一些 JavaScript 中异步代码的例子!
异步回调
JavaScript 中一个常见的异步性例子是使用异步回调。**这是一种在满足特定条件后执行的回调函数,并且该函数可以并行运行于当前运行的任何其他代码。**来看一个例子:
js
easterEgg.addEventListener('click', () => {
console.log('上,上,下,下,左,右,左,右,B,A');
});
在上面的代码中,作为 .addEventListener()
第二个参数传递的函数是一个异步回调------这个函数不会执行,直到点击了 easterEgg
。
setTimeout
除了异步回调,JavaScript 还提供了一些内置函数,可以异步执行任务。一个常用的函数是 setTimeout()
。
使用 setTimeout()
,我们可以编写代码告诉 JavaScript 程序在执行回调函数之前等待最短的时间。看一下这个例子:
js
setTimeout(() => {
console.log('请延迟打印这个字符串');
}, 1000);
请注意,setTimeout()
接受两个参数,一个是回调函数,另一个是指定等待多长时间才执行该函数的数字。在上面的例子中,函数将在 1000 毫秒(即 1 秒)后打印 '请延迟打印这个字符串'
。
由于 setTimeout()
是非阻塞的,我们可以同时执行多行代码!比如,想象一下有这样一个程序:
js
setTimeout(() => {
console.log('请延迟打印这个字符串');
}, 1000);
console.log('正在做重要的事情');
console.log('仍在做重要的事情');
输出结果是:
正在做重要的事情
仍在做重要的事情
请延迟打印这个字符串
如果我们仔细看输出,我们会发现 setTimeout()
的回调函数并没有在执行其他非常重要的 console.log()
语句之前执行。
在 web 开发中,这意味着我们可以编写代码,等待事件触发的同时,用户仍然可以与我们的应用程序互动。一个这样的例子是,如果用户访问一个购物网站,并且被通知某个商品正在特卖并且只有有限时间。我们的异步代码可以允许用户与网站互动,当销售计时器到期时,代码将删除该销售商品。
setInterval()
另一个常用的内置函数是 setInterval()
,它也接受一个回调函数和一个数字,指定回调函数应执行的频率。例如:
js
setInterval(() => {
alert('Are you paying attention???');
}, 300000);
setInterval()
会每隔 300000 毫秒(即 5 分钟)调用 alert()
函数,显示 'Are you paying attention???'
的弹窗信息。注意:请不要在应用程序中实际使用这种做法,谢谢。
在等待每 5 分钟弹出一次提示时,用户仍然可以继续使用我们的应用程序!注意:再次提醒,别这么做。
使用 setInterval()
,我们可以编程地创建一个闹钟、倒计时器、设置动画的频率等更多功能!
JavaScript的并发模型和事件循环
JavaScript 如何使用事件循环模拟并发
如果你已经学习了异步编程,你可能会好奇,当代码等待异步操作完成时,如何能够不阻塞并继续进行其他任务。本文将揭开一些关于 JavaScript 如何模拟并发的抽象,看看事件循环在幕后发生了什么。那么,事件循环到底是什么?为什么我们需要它?
为什么我们需要事件循环?
JavaScript 是一种单线程语言,这意味着两个语句不能同时执行。例如,如果你有一个需要处理一段时间的 for
循环,它必须在执行完之前才能运行你代码的其他部分。这会导致代码阻塞。但正如我们已经学到的,我们可以在 JavaScript 中运行非阻塞代码,而这正是事件循环的作用。输入/输出(I/O)是通过事件和回调来处理的,这样代码执行就可以继续进行。让我们来看一个阻塞和非阻塞代码的例子。你可以自己在本地运行这段代码:
js
console.log("I'm learning about");
for (let idx=0; idx < 999999999; idx++) {}
// 第二个 console.log() 语句被 for 循环的执行延迟
console.log("the Event Loop");
上面的例子中有一个带有长 for
循环的同步代码。以下是发生的情况:
- 代码执行,并且"我在学习"被记录到控制台。
- 接下来,
for
循环执行并运行了 999999999 次,这导致了代码的阻塞。如果你在本地运行这段代码,这就是暂停发生的地方。 - 最后,"事件循环"被记录到控制台。
现在让我们看看非阻塞的例子。像 setTimeout()
这样的函数由于事件循环的作用,工作方式不同。运行以下代码:
js
console.log("我在学习");
setTimeout(() => { console.log("事件循环");}, 2000);
console.log("the");
在这种情况下,代码片段使用 setTimeout()
函数演示了 JavaScript 如何通过事件循环实现非阻塞。发生的情况是:
- 一个语句被记录。
- 执行
setTimeout()
函数。 - 执行第三行代码并记录文本:"the"。
- 最后,
setTimeout()
函数的计时器完成,并记录额外的文本:"事件循环"。
在这种情况下,JavaScript 仍然是单线程的,但事件循环使得某些东西被称为并发。
JavaScript 中的并发
通常,当我们谈论编程中的并发时,意味着两个或更多过程在相同的共享资源上同时执行。由于 JavaScript 是单线程的,正如我们在 for
循环示例中看到的,我们永远不会有那种"真正的"并发。然而,我们可以通过事件循环来模拟并发。
什么是事件循环?
从高层次来看,事件循环是一个管理代码执行的系统。在这个图示中,你可以看到组成事件循环的各个部分是如何结合在一起的。
我们有一些数据结构,称为堆(heap)和调用栈(call stack),它们是 JavaScript 引擎的一部分。堆和调用栈与 Node 和 Web API 交互,后者通过事件队列将消息传递回调用栈。事件队列与调用栈的交互是由事件循环来管理的。所有这些部分共同维护了当我们运行异步函数时代码执行的顺序。别担心暂时理解这些术语,我们稍后会深入探讨它们。

理解事件循环的组成部分
事件循环由以下几个部分组成:
- 内存堆 (Memory Heap)
- 调用栈 (Call Stack)
- 事件队列 (Event Queue)
- 事件循环 (Event Loop)
- Node 或 Web API
在我们把这些部分组合在一起之前,让我们仔细看看每个部分。
内存堆 (Heap)
堆是一个存储对象的内存块,存储的顺序是无序的。JavaScript 中当前使用的变量和对象会存储在堆中。
调用栈 (Call Stack)
栈(或调用栈)用于追踪当前正在运行的函数。
当你调用一个函数时,一个帧会被添加到栈中。帧将该函数的参数和本地变量从堆中连接起来。帧以"后进先出 "(LIFO)的顺序进入栈。在下面的代码片段中,声明了一系列嵌套函数,然后调用 foo()
并输出结果。
javascript
javascript
复制编辑
function foo() {
return function bar() {
return function baz() {
return 'I love CodeCademy'
}
}
}
console.log(foo()()());
在任何给定时刻,正在执行的函数位于栈的顶部。在我们的示例代码中,由于我们有嵌套函数,它们会依次添加到栈中,直到最内层的函数被执行。当函数执行完毕(例如返回时),它的帧将从栈中移除。当我们执行 console.log(foo()()())
时,栈的构建过程如下:





你可能已经注意到,global()
位于调用栈的底部 ------ 当你首次启动一个程序时,全局执行上下文会被添加到调用栈中,它包含全局变量和词法环境。
每一个随后被调用的函数都会在栈中新增一个帧(frame),这个帧是函数的执行上下文,其中包括该函数的词法环境和变量环境。
所以当我们说调用栈"追踪当前正在运行的函数"时,实际上是在追踪当前的执行上下文。当一个函数运行完毕后,它就会从调用栈中被"弹出"(pop),对应的内存(也就是那个帧)就会被清除。
事件队列(Event Queue)
事件队列是一个消息列表,这些消息对应的是等待被处理的函数。在图示中,这些消息是从各种 Web API 或异步函数中传入事件队列的,这些异步函数已被调用,并返回了一些待处理的事件。
消息进入队列时遵循先进先出(FIFO)的顺序。事件队列中并不会执行代码,它只是保存等待被重新加入调用栈的函数。
事件队列在上下文中的作用
事件队列是整个事件循环(Event Loop)概念中的一个重要部分。那些等待被重新加入调用栈的消息,通过事件循环机制被添加到栈中。
事件循环的工作机制如下:
- 首先,事件循环会轮询调用栈,看是否为空;
- 如果为空,就从事件队列中取出第一个等待的消息添加到调用栈中;
- 重复上述步骤,直到事件队列清空或调用栈被占满。
事件循环实际运行示例
js
console.log("This is the first line of code in app.js.");
function usingsetTimeout() {
console.log("I'm going to be queued in the Event Loop.");
}
setTimeout(usingsetTimeout, 3000);
console.log("This is the last line of code in app.js.");
执行顺序如下:
console.log("This is the first line of code in app.js.");
被加入调用栈、执行完毕后弹出。setTimeout()
被加入调用栈。setTimeout()
的回调函数被交给 Web API 去处理(设置一个 3 秒的定时器)。当 3 秒结束后,该回调函数usingsetTimeout()
被推入事件队列。- 与此同时,事件循环机制会不断检查调用栈是否为空,以便处理事件队列中的内容。
- 然后
console.log("This is the last line of code in app.js.");
被加入调用栈,执行,弹出。 - 此时调用栈已经清空,事件循环将
usingsetTimeout
推入调用栈。 console.log("I'm going to be queued in the Event Loop.");
被加入调用栈,执行,弹出。usingsetTimeout()
执行完毕后,也从调用栈中弹出。
有事件循环(Event Loop),JavaScript 才能在单线程 的基础上实现事件驱动 ,从而以异步的方式运行非阻塞代码。
可以将事件循环的机制总结如下:
- 当代码执行时,它会由**堆(heap)和调用栈(call stack)**处理;
- 调用栈与 Node API 或 Web API 交互,这些 API 具备并发能力;
- 它们将异步消息传回给调用栈,是通过 事件队列(event queue) 完成的;
- 事件队列与调用栈的互动由事件循环(event loop)来管理。
https
互联网由许多托管在不同服务器上的资源组成。这里的"资源"可以是网页上的任何实体,比如 HTML 文件、样式表、图片、视频或脚本等。
为了访问这些内容,你的浏览器需要向这些服务器请求所需资源 ,然后再将它们展示给你 。
这一套请求与响应的协议,正是你能在浏览器中看到网页的根本原因。
本篇文章将重点介绍互联网运作的一个核心部分:HTTP。
什么是http
HTTP 是"超文本传输协议 "(Hypertext Transfer Protocol)的缩写,用于在互联网中构造请求和响应的格式。HTTP 要求数据在网络上传输时必须遵循某种结构和方式。
资源的传输是通过另一种协议------TCP(传输控制协议)来完成的。
比如当你访问这个网页时,TCP 就负责管理你的浏览器与服务器(例如 codecademy.com)之间的通信通道。
HTTP 就像是一种命令语言,连接两端的设备(浏览器和服务器)需要遵循这种语言才能完成交流。
HTTP 和 TCP 是如何协作的?
当你在浏览器中输入网址,比如 www.codecademy.com
,你实际上是在告诉浏览器去建立一个TCP 通道 ,连接到负责该网址的服务器(URL,即统一资源定位符,你可以在维基百科查看更多信息)。
URL 就像家庭住址或电话号码,它描述了如何找到你要去的地方。
此时你的电脑就是客户端(client) ,请求的 URL 指向的就是服务端的地址。
一旦 TCP 连接建立,客户端就会发送一个 HTTP GET 请求 给服务端,要求获取网页内容。
服务端发送响应后,通常会关闭这个 TCP 连接。
如果你再次打开网站,或浏览器主动请求更多内容,那么就会重新建立连接,重复上述流程。
GET 请求是 HTTP 方法中的一种,你可以进一步了解常见的几种方法(如 POST、PUT 和 DELETE)。
GET 请求的示例流程:
假设你想查看 Codecademy 上最新的课程:
- 你在浏览器中输入
http://codecademy.com
; - 浏览器解析
http
,知道要使用 HTTP 协议; - 然后从 URL 中提取域名
"codecademy.com"
; - 浏览器会向域名服务器(DNS)请求该域名对应的 IP 地址;
- 获取到 IP 地址后,客户端用 HTTP 协议向该 IP 发起连接;
- 浏览器发送如下的 GET 请求:
vbnet
GET / HTTP/1.1
Host: www.codecademy.com
-
第一行说明请求类型是 GET,请求的是根路径
/
,并使用的是 HTTP/1.1 协议; -
第二行说明请求的主机地址。
如果服务器找到了这个资源,就会返回一个响应头:
arduino
HTTP/1.1 200 OK
Content-Type: text/html
-
第一行表示请求成功,并使用的是 HTTP/1.1 协议;
-
第二行表示返回的是 HTML 格式的内容;
-
后面跟着的就是你请求的网页内容。
如果找不到这个资源,服务器会返回:
HTTP/1.1 404 NOT FOUND
表示服务器理解请求,但找不到对应的资源。这可能是因为内容被移动、URL 拼写错误,或者页面已被删除。
一个类比:
理解 HTTP 的工作原理可能有点抽象(特别是涉及这么多缩写),我们来用一个更生活化的比喻:
想象互联网是一个小镇。你是这个镇上的一个"客户",你的地址决定了别人如何联系你。Codecademy.com 是镇上的一家"公司",负责处理外来的请求。
镇上还有许多其他居民,也都像你一样发送请求并期待收到回复。
同时,这个镇还有一个飞快的邮政系统,一群快递员可以通过光速运行的"火车"进行通信。
现在你想阅读报纸:
- 你用一种叫做 HTTP 的语言写下一份请求;
- 快递员接受请求,瞬间建好一条"铁路"(连接),并乘坐标有 TCP 的火车前往你指定的那家公司;
- 到达后,公司里的员工去找你要的报纸页面;
- 如果找不到,就告诉快递员"404 Not Found";
- 快递员乘着火车飞速回到你家,告诉你找不到;
- 你发现自己拼错了报纸名称,改正后再发请求;
- 这次请求成功,你可以安静地看报纸了;
- 当你想看下一页报纸,就要再发送一个请求......
什么是https
由于 HTTP 请求在网络中可能被他人截取查看,如果你要传递诸如信用卡、密码等敏感信息,直接使用 HTTP 并不安全。
所以,很多服务器现在支持 HTTPS (即 HTTP Secure),它允许对你发送和接收的数据进行加密 。
你可以在 Wikipedia 上查看更多关于 HTTPS 的内容。
HTTPS 在你和网站之间传输敏感数据时非常重要。
不过,是否支持 HTTPS 是由网站运营者决定的。如果想支持 HTTPS,他们需要向**证书机构(Certificate Authority)**申请安全证书。
web api
什么是 API?
API 是"应用程序编程接口"(Application Programming Interface)的缩写,是一种软件工具,能让开发者更方便地与其他应用交互,从而使用其中的一些功能。就像现实世界中的工具一样,API 被设计用来解决特定、重复性高、可能很复杂的问题。
想象一下,我们需要把一颗螺丝拧进木板里。我们可以用手指直接拧,但这既困难又低效。拧一颗螺丝都很吃力,要拧很多颗简直不可能!这时我们可以借助别人发明的工具 ------ 螺丝刀!螺丝刀能轻松完成这一特定任务,并且可以重复使用,效率大大提升。
回到 API 的例子,我们当然不希望每次都为同一个问题重复写同样的代码。如果有现成的 API 能解决这个问题,我们就可以直接调用它,让开发工作更轻松。但在使用 API 之前,我们还是需要了解一些重要的前提知识。
api的类型
Web API 大致可以分为两类:浏览器 API(Browser API) 和 第三方 API(Third-party API) 。
浏览器 API 是专为浏览器环境设计的,开发者可以借助它访问浏览器中可用的信息。例如,Geolocation API(地理位置 API)就可以让你的应用知道用户的地理位置。但前提是用户要授权浏览器访问这些信息。浏览器 API 的种类很多,还包括音频、加密、虚拟现实(VR)等相关功能。你可以在 MDN 的 Web API 文档中查看完整列表及用法。
第三方 API 是由其他应用(通常是公司)提供的功能或数据接口。例如,你可以使用 OpenWeather 的 API 获取某地的天气、预报、历史气象数据等。我们自己既访问不了这些数据,也没必要专门为一个应用写这套功能。
如何从第三方api获取信息
每个 API 都有一套特定的结构和协议,想使用它,就要遵守这些规则。
规则与要求
维护第三方 API 的组织通常会设定一套规范,规定开发者怎么与其 API 交互。以 OpenWeather 为例,我们需要注册一个账号,并生成一个特殊的令牌,称为 API 密钥(API key),才可以发送请求。API 密钥是账号唯一的凭证,应妥善保管。OpenWeather 提供部分免费功能,部分则需要付费。所以在使用某个第三方 API 之前,务必先查阅它们的 API 文档。例如你可以看看 OpenWeather 的官方文档。
发起请求
API 文档中也会说明如何发起数据请求。你可能需要在请求中提供更多参数,例如 API 密钥、城市名称、时间、预报类型等,以便获得你想要的数据。这些规范同样写在 API 的文档中。
响应数据
请求成功后,API 会返回数据。大多数 API 会使用 JSON(JavaScript 对象表示法)格式返回,这种格式和 JavaScript 的对象写法很像。来看一个简单的例子:
json
{
"temperature": {
"celcius": 25
},
"city": "chicago"
}
接下来由我们决定如何使用这些数据。如果我们收到如上 JSON,我们就可以提取温度信息并在应用中显示出来。
总结:
-
Web API 是一种工具,它可以让我们访问其他应用的功能或数据。
-
API 分为两大类:浏览器 API 和 第三方 API。
-
浏览器 API 需要特定的语法和用户授权。
-
第三方 API 有各自的规则和要求,一般记录在 API 文档中。
-
发起 API 请求时,可能需要提供更多参数说明我们想要什么数据。
-
收到响应后,还需要决定如何使用返回的数据。
Rest
什么是Rest
了解 REST(表述性状态转移)范式,以及 REST 架构如何简化 Web 组件之间的通信。

Rest API模型
REST 是一种架构风格,它为 Web 上的计算机系统提供标准,使系统之间更容易进行通信。遵循 REST 的系统通常被称为 RESTful 系统,它们的特点是**无状态(stateless)**并且将客户端和服务器的职责分离。
客户端与服务器的分离
在 REST 架构中,客户端和服务器的实现可以独立完成,互不依赖。这意味着客户端代码可以随时更改而不影响服务器的运行,反之亦然。
只要客户端和服务器都知道如何相互发送和接收信息,它们就可以保持模块化和独立。通过将用户界面(UI)的逻辑与数据存储的逻辑分离,我们可以:
- 提高跨平台的接口灵活性
- 通过简化服务器组件来增强系统的可扩展性
- 允许每个部分独立发展,不互相影响
使用 REST 接口时,不同客户端访问相同的 REST 接口,执行相同操作,接收相同响应。
无状态性(Statelessness)
遵循 REST 的系统是无状态的,也就是说,服务器不需要知道客户端的状态,反之亦然。
这意味着每个请求都应包含完成操作所需的所有信息。即使没有之前的通信背景,客户端和服务器也可以理解收到的消息。
这种无状态的特性通过"资源"来实现,而不是"命令"。资源是 Web 的名词,比如文档、图片、用户等。
REST 系统通过标准的操作与资源交互,不依赖接口的具体实现。
这些约束使 RESTful 应用能够实现:
- 高可靠性
- 快速响应
- 良好扩展性
因为组件可以独立管理、更新和复用,即使在系统运行期间也是如此。
客户端与服务器的通信方式
在 REST 架构中,客户端发送请求以获取或修改资源,服务器对这些请求进行响应。
发出请求
REST 要求客户端向服务器发出请求以获取或修改数据。请求通常包括:
- 一个 HTTP 动词,定义要执行的操作
- 一个 请求头(header) ,用于传递关于请求的信息
- 一个指向资源的 路径(path)
- 一个 可选的消息体(body) ,用于携带数据
HTTP 动词(Verbs)
REST 系统中使用的 4 个基本 HTTP 动词如下:
动词 | 功能 |
---|---|
GET | 获取特定资源或资源集合 |
POST | 创建新资源 |
PUT | 更新指定资源 |
DELETE | 删除指定资源 |
请求头和 Accept 参数
请求头中的 Accept
字段用于告诉服务器客户端能接受哪些类型的响应内容。
这些内容类型叫做 MIME 类型 ,格式是 类型/子类型
,例如:
- HTML 页面:
text/html
- CSS 文件:
text/css
- 普通文本:
text/plain
- 图片:
image/png
,image/jpeg
- 音频:
audio/mpeg
- 视频:
video/mp4
- JSON 数据:
application/json
bash
GET /articles/23
Accept: text/html, application/xhtml
这表示客户端能接受 HTML 或 XHTML 格式的响应。
路径(Paths)
请求路径指明了要操作的资源。
路径设计规范:
- 使用资源的 复数形式
- 层级结构清晰易读,例如:
bash
fashionboutique.com/customers/223/orders/12
这表示要访问 ID 为 223 的客户的 ID 为 12 的订单。
- 对于新建资源(如 POST 操作),不需要加 ID。
- 获取或删除特定资源时,应在路径中加上 ID。
服务器响应
内容类型(Content-Type)
服务器返回数据时必须在响应头中指定 Content-Type
,以便客户端知道如何解析内容。
例如:
arduino
HTTP/1.1 200 OK
Content-Type: text/html
响应码(Response Codes)
常见的 HTTP 响应状态码如下:
状态码 | 含义 |
---|---|
200 OK | 请求成功 |
201 CREATED | 成功创建资源 |
204 NO CONTENT | 成功但无返回内容 |
400 BAD REQUEST | 请求错误 |
403 FORBIDDEN | 无访问权限 |
404 NOT FOUND | 资源未找到 |
500 INTERNAL SERVER ERROR | 服务器内部错误 |
每种请求方式的标准响应码:
- GET → 200
- POST → 201
- PUT → 200
- DELETE → 204
请求与响应示例
假设有个服装店管理系统,网址是 fashionboutique.com
,以下是一些常见操作:
查看所有客户:
bash
GET http://fashionboutique.com/customers
Accept: application/json
→ 响应:200 OK,返回 JSON 格式数据
新增客户:
bash
POST http://fashionboutique.com/customers
Body:
{
"customer": {
"name": "Scylla Buss",
"email": "[email protected]"
}
}
→ 响应:201 CREATED,Content-Type: application/json
更新客户:
perl
PUT http://fashionboutique.com/customers/123
Body:
{
"customer": {
"name": "Scylla Buss",
"email": "[email protected]"
}
}
→ 响应:200 OK
删除客户:
objectivec
DELETE http://fashionboutique.com/customers/123
→ 响应:204 NO CONTENT
REST 实战练习:照片收藏网站
目标:构建一个管理用户、地点和照片的 API。
每个用户有用户名和密码,每张照片关联地点和拍摄者,每个地点有名字和地址。
数据模型(Models)
json
{
"user": {
"id": Integer,
"username": String,
"password": String
}
}
{
"photo": {
"id": Integer,
"venue_id": Integer,
"author_id": Integer
}
}
{
"venue": {
"id": Integer,
"name": String,
"address": String
}
}
请求与响应(Requests/Responses)
GET 请求
请求路径 | 说明 | 响应码 | 内容类型 |
---|---|---|---|
GET /index.html | 获取 HTML 页面 | 200 | text/html |
GET /style.css | 获取 CSS 文件 | 200 | text/css |
GET /venues | 获取所有地点 | 200 | application/json |
GET /venues/:id | 获取某个地点 | 200 | application/json |
GET /venues/:id/photos/:id | 获取某个地点的某张照片 | 200 | image/png |
POST 请求
请求路径 | 说明 | 响应码 | 内容类型 |
---|---|---|---|
POST /users | 新建用户 | 201 | application/json |
POST /venues | 新建地点 | 201 | application/json |
POST /venues/:id/photos | 新增照片 | 201 | application/json |
PUT 请求
请求路径 | 说明 | 响应码 |
---|---|---|
PUT /users/:id | 更新用户 | 200 |
PUT /venues/:id | 更新地点 | 200 |
PUT /venues/:id/photos/:id | 更新照片 | 200 |
DELETE 请求
请求路径 | 说明 | 响应码 |
---|---|---|
DELETE /venues/:id | 删除地点 | 204 |
DELETE /venues/:id/photos/:id | 删除照片 | 204 |
Json
什么是 JSON?
在这个数据泛滥的时代,能够处理各种数据格式变得越来越重要。作为程序员,我们需要将用任意编程语言构建的数据结构,转换成一种其他语言和平台都能识别和读取的格式。幸运的是,确实存在这样一种数据交换格式。
JSON 广泛用于 Web 应用中客户端(例如网页浏览器)与服务器之间的数据传输。一个典型的例子是你填写网页表单时,表单数据会从 HTML 转换为 JavaScript 对象,再转换为 JSON 对象,然后发送给远程服务器处理。这些交互可能是简单的搜索引擎查询,也可能是复杂的多页求职申请。
当一些公司对外开放其数据供其他应用使用时,比如 Spotify 共享其音乐库数据、Google 共享地图数据,这些信息通常也会采用 JSON 格式。这样,不论使用什么语言编写的应用程序,都可以收集并解析这些数据。
以下是一些常用 JSON 进行数据交换的流行 Web API:
- Google Maps
- Google OAuth 2.0 认证
- Facebook 社交图谱 API
- Spotify 音乐 Web API
- LinkedIn 个人资料 API
Json语法
由于 JSON 源自 JavaScript 编程语言,其外观非常类似 JavaScript 对象。
下面是一个 JSON 对象的示例:
json
{
"student": {
"name": "Rumaisa Mahoney",
"age": 30,
"fullTime": true,
"languages": [ "JavaScript", "HTML", "CSS" ],
"GPA": 3.9,
"favoriteSubject": null
}
}
请注意以下 JSON 的语法规则:
- 使用大括号
{..}
表示对象。 - 使用方括号
[..]
表示数组。 - 数据以名称-值对的形式表示,中间用冒号
:
分隔。 - 每个名称-值对之间使用逗号
,
分隔。数组中的每个元素之间也用逗号隔开。不能有尾随逗号。 - JSON 中的属性名必须使用双引号
"
括起来,而 JavaScript 并不强制这一点。
JSON 数据类型
JSON 的数据类型必须是以下之一:
- 字符串(用双引号括起来)
- 数字(整数或浮点数)
- 对象(名称-值对)
- 数组(用逗号分隔的值集合)
- 布尔值(
true
或false
) null
(空值)
试着在上面的 JSON 示例中找出所有的数据类型吧!
值得注意的是,JSON 并不涵盖所有数据类型。例如日期类型并不是 JSON 原生支持的。但你可以将其作为字符串来存储,然后在需要时转换为语言特定的数据结构。下面是一个符合国际标准 ISO 8601 的日期字符串示例:
arduino
"2014-01-01T23:28:56.782Z"
这个格式包含日期和时间部分。但作为字符串,它对编程语言而言不是特别"友好"。好在所有主流编程语言都内置了处理 JSON 的工具,可以将这种字符串转换为更易读、更实用的格式,例如:
yaml
Wed Jan 01 2014 13:28:56 GMT-1000(夏威夷标准时间)
在 JavaScript 中使用 JSON
JSON,全称 JavaScript Object Notation,是一种与编程语言无关的数据格式,已被广泛接受为业界标准。由于它基于 JavaScript 编程语言,JSON 的语法看起来和 JavaScript 对象非常相似,但还是存在一些细微差别。本文将会介绍这些差别,并讲解如何解析 JSON 并将其转换为 JavaScript 对象。最后,我们还将学习如何使用 JavaScript 编写 JSON 对象。让我们开始吧!
Json对象 vs JavaScript对象
下面是一个 JSON 对象的示例,描述了一个叫 Kate 的人,她 30 岁,爱好包括阅读、写作、烹饪和打网球:
json
{
"person": {
"name": "Kate",
"age": 30,
"hobbies": [ "reading", "writing", "cooking", "tennis" ]
}
}
同样的信息,换成 JavaScript 对象字面量的写法是:
jvascript
{
person: {
name: 'Kate',
age: 30,
hobbies: [ 'reading', 'writing', 'cooking', 'tennis' ]
}
}
注意两者之间的细微差别:
- 在 JSON 中,每个名称-值对中的"名称"部分 以及所有字符串值 都必须使用双引号括起来;
- 而在 JavaScript 中,这些是可选的。
- JavaScript 接受单引号或双引号包裹的字符串,不过一些 JavaScript 代码风格指南会更偏好其中一种格式。
读取 JSON 字符串
在典型的 Web 应用中,从网络请求中接收到的 JSON 数据通常是字符串形式。有时,JSON 数据也存储在文件中,用于身份验证、配置或数据库存储。这些文件通常以 .json
作为扩展名,读取文件时需要将其中的 JSON 字符串提取出来。无论哪种方式,我们都需要将 JSON 字符串转换为可操作的格式,以便访问其中的数据。每种编程语言都有自己的转换机制。
在 JavaScript 中,我们可以使用内置的 JSON
类,其中的 .parse()
方法可以接收一个 JSON 字符串作为参数,并返回一个 JavaScript 对象。
下面这段代码将 JSON 字符串对象 jsonData
转换为 JavaScript 对象 jsObject
,然后将其输出到控制台:
js
const jsonData = '{ "book": { "name": "JSON Primer", "price": 29.99, "inStock": true, "rating": null } }';
const jsObject = JSON.parse(jsonData);
console.log(jsObject);
输出结果如下:
javascript
{
book: { name: 'JSON Primer', price: 29.99, inStock: true, rating: null }
}
访问 JavaScript 对象的属性
将 JSON 字符串解析为 JavaScript 对象之后,我们就可以访问其中的各个属性了。在 JavaScript 中,可以使用两种方式访问对象属性:点号(.
)语法 或 中括号([]
)语法。
例如,要访问 jsObject
中的 book
属性,我们可以这样写:
js
// 使用点号语法
const book = jsObject.book;
console.log(book);
console.log(book.name, book.price, book.inStock);
// 使用中括号语法
const book2 = jsObject['book'];
console.log(book2);
console.log(book2["name"], book2["price"], book2["inStock"]);
这两种方式返回的结果完全相同:
yaml
{ name: 'JSON Primer', price: 29.99, inStock: true, rating: null }
JSON Primer 29.99 true
如你所见,一旦将 jsonData
解析成了 JavaScript 对象(存储在 book
变量中),你就可以像操作普通对象一样操作它!这意味着你可以访问属性值(如上所示)、修改已有值、遍历键和值,等等。
编写Json字符串
在我们将数据通过网络发送出去之前,需要先将它们转换为 JSON 字符串。在 JavaScript 中,我们可以使用内置的 JSON
类中的方法 .stringify()
来将 JavaScript 对象转换为 JSON 字符串。
以下代码将一个 JavaScript 对象 jsObject
转换为一个 JSON 字符串 jsonData
:
javascript
const jsObject = { book: 'JSON Primer', price: 29.99, inStock: true, rating: null };
const jsonData = JSON.stringify(jsObject);
console.log(jsonData);
这段代码会输出以下内容:
json
{ "book": "JSON Primer", "price": 29.99, "inStock": true, "rating": null }
也就是说,.stringify()
方法可以帮助我们把 JS 对象变成一个适合传输的 JSON 格式的字符串。这样就能被服务器或其他平台理解和接收啦。