建立你的第一个JavaScript应用程序--随机报价生成器

279 阅读20分钟

如果你正在学习JavaScript作为你的第一门编程语言,本教程提供了一个很好的机会来学习它在现实世界中的使用方法。它将帮助你了解JavaScript、HTML和CSS如何在浏览器中共同创建应用程序。

在这篇文章中,你将学习如何使用JavaScript来构建一个随机报价生成器。这是一个应用程序,每次按下按钮都会获取一个新的随机报价,并在浏览器中显示。下面是完成后的应用程序的截图。

Random quote generator screenshot

在这里可以找到一个实时演示

要完成这个教程,你只需要一个现代网络浏览器(如Firefox或Chrome)和一个正常的网络连接。如果你遇到困难,请在下面留言,我会尽快给你答复。

前提条件

我假定你对HTML和CSS有基本的熟悉,如果你知道JavaScript的基本知识,也会有帮助。不过,我已经努力使本教程尽可能容易理解,即使你以前从未写过一行JavaScript。

开始学习

你可以在JSFiddle上找到这个项目的标记和样式。点击顶部的Fork按钮来创建你自己的实例,如果你愿意,也可以把它复制并粘贴到本地的HTML和CSS文件中。

JavaScript最基本的用途之一是用于制作交互式网页。这允许页面在用户执行某个动作时发生轻微或完全的变化。如今,JavaScript几乎被用于一切,包括但不限于服务器基础设施、桌面和移动应用程序,但这仍然是将你所学知识付诸实践的最快捷方式。

如果你看一下当前状态的应用程序,你会发现它实际上只是一个静态页面,对用户的交互没有反应。我们有两个按钮:第一个用于获取一个新的随机报价并在页面上显示,第二个用于在Twitter上分享该报价。目前,点击任何一个按钮都没有效果,这是因为我们还没有编写任何JavaScript代码。

我们的任务是在点击报价按钮时获取一个新的报价并将其显示给用户。我们将利用What Does Trump Think API来完成这个任务。

选择页面上的元素

首先,让我们在我们的JavaScript代码中存储一个对报价按钮的引用。这里是HTML中的报价按钮的标记。

<button type="button" id="js-new-quote" class="new-quote button">
  Generate a new quote
</button>

一个元素上的类和ID的存在,不仅有助于对该元素应用样式,而且也有助于在JavaScript代码中引用它们。用JavaScript选择一个元素有很多方法,但我将向你展示一种方法,它与CSS中的方法有一些相似之处。

在CSS中,你会这样做。

/* when using a class */
.class-name {
  /* styles go here */
}

/* when using an id */
#id-name {
  /* styles go here */
}

在JavaScript中,你可以用DOM document.querySelector 方法选择一个元素,如下图所示。

// Using a class selector
document.querySelector('.class-name')

// Using an id selector
document.querySelector('#id-name')

document 代表当前的网页,而querySelector 是一个DOM方法,用来引用文档中第一个符合指定选择器的元素。

注意,在上面的代码中,选择器字符串是用单引号括起来的。你也可以使用双引号。

document.querySelector(".class-name")

在JavaScript中,字符串使用单引号或双引号没有语义上的区别。重要的是要保持一致。在本教程的剩余部分,我将继续使用单引号,因为它是JavaScript社区的主流风格。

继续,这里是如何在我们的JavaScript代码中选择报价按钮的。

document.querySelector('#js-new-quote')

这将创建一个对页面上的报价按钮的引用。接下来,我们需要将这个按钮的引用存储在一个变量中。JavaScript中的变量(以及其他编程语言)是存放不同类型数据结构的容器。它们给我们提供了一种简单的方法来标记我们程序中的一些数据,以便程序的其他部分可以方便地引用。

在JavaScript中,有很多方法可以创建变量。你可能已经看到了现在已经过时的var 关键字的使用。

var name = value;

然而,更现代的方法是使用ECMAScript2015(又称ES2015或ES6)中引入的constlet ,以取代var

const name = value;
let name = value;

我们将在本教程中专门使用const ,并在以后的文章中讨论它与letvar 的区别。

命名变量

JavaScript中的变量必须有唯一的名字。这些名字可以由字母、数字、下划线和美元符号组成。但是,它们不能以数字开头(只有字母、下划线和美元符号)。变量名称也是区分大小写的(意味着ziggyZIGGY 是不同的变量),而且它们不能是语言中的关键词。

关键词是编程语言中的保留词,具有特殊意义。它们不能被用作变量或函数的名称。我们已经在JavaScript中看到了三个关键词。var,letconst ,它们被用来声明变量。其他还有function,for,if,while, e.t.c.

当你声明一个变量时,你需要给它分配一个值。等号,= ,被称为赋值运算符,就是用来给变量赋值的。下面是你如何在JavaScript中声明一个变量并给它赋值。

const answer = 42;

这个变量声明的名称是answer ,其值是42 。赋值总是从右到左。这意味着,= 右侧的所有值都会先被评估,然后再将最终的值分配给左侧的变量。

const a = 10;
const b = a + 20; // a is resolved to 10 and added to 20 before being assigned to b

现在继续在JSFiddle的JavaScript窗格中输入这个内容。

const newQuoteButton = document.querySelector('#js-new-quote');

这就把对报价按钮的引用分配给了一个新的变量,叫做newQuoteButton 。现在,我们可以在我们的JavaScript代码中的任何地方使用newQuoteButton 这个变量,因为它总是指的是报价按钮。

命名变量时,要确保名称与变量的使用方式或它所引用的内容密切相关。在上面的步骤中,注意到变量的名字newQuoteButton ,清楚地描述了它所引用的内容。

例如,如果我们将变量命名为button ,代码就会变得含糊不清。任何正在阅读代码的人都无法立即分辨出我们所指的是哪个按钮。

顺便说一下,你是否注意到在变量声明的最后有一个分号(;)?虽然不是严格意义上的必要,但惯例是在JavaScript的语句中总是用分号来结束。

喘口气吧,看看本步骤末尾的完整代码

听取元素上的事件

DOM事件是浏览器在网页上发生特定动作时发出的信号,通常是用户互动的结果。开发人员可以设置他们的程序来监听这些事件中的任何一个,并在事件被触发时执行某些操作。

在我们的应用程序中,我们需要检测对报价按钮的点击,这样我们就可以获取一个新的随机报价并将其显示给用户。我们可以通过document.addEventListener 方法监听按钮上的click 事件来实现这一目的。

newQuoteButton 变量声明下键入此内容。

newQuoteButton.addEventListener('click', getQuote);

addEventListener 至少接受两个参数(括号内的数值)。第一个参数,click ,是我们要监听的事件,第二个参数,getQuote ,是当click 事件在newQuoteButton 上被触发时将被调用的函数名称。

一个函数是一个定义为执行特定任务的代码块。它被用来封装那些我们想多次重复使用的代码,这样我们就不必费力地重复它了。这使得代码更容易推理。

function 关键字被用来在JavaScript中声明函数。它的后面是函数的名称,以及当函数被调用时将在大括号中执行的代码(也就是函数体)。

语法如下。

function name() {
  // body of function
}

上面的函数可以用它的名字加圆括号来调用,比如:name() 。当你调用一个函数时,它将执行其主体中定义的代码。

有时,一个函数会有参数。这些参数写在函数名称后面的括号之间,每个参数都用逗号隔开。参数就像一个变量,用于定制一个函数的输出。你可以把它们看作是输入值的占位符,当一个函数被调用时,这些输入值将被传递给它。通常,一个接受输入参数的函数会对它们进行一些操作以产生一个新的值。

function name(a, b) {
  return a + b;
}

return 关键字在函数中被用来返回在函数中进行的操作的结果。如果一个函数没有明确地返回一个值,它将返回undefined

回到我们的代码。

newQuoteButton.addEventListener('click', getQuote);

在上面这行代码中,我们指定getQuote ,作为点击报价按钮时需要运行的函数。这个函数还不存在,所以我们需要它,否则程序将抛出一个ReferenceError ,这意味着我们试图访问不存在的东西。

事实上,你现在应该能在你的浏览器控制台看到这个错误。在Chrome中使用Ctrl+Shift+J或Cmd+Opt+J,在Firefox中使用Ctrl+Shift+K或Cmd+Opt+K来打开控制台,或者使用右下方的内置JSFiddle控制台。

ReferenceError in JSFiddle console

让我们通过在前两行下面创建getQuote 函数来解决这个错误,如下图所示。

function getQuote() {
  console.log("quote button was clicked");
}

在这里,我们创建了一个新的函数,给它命名为getQuote ,在它的主体中写了一些代码来打印文本到控制台。如果你打开浏览器的控制台,点击几次报价按钮,每点击一次按钮,就会打印一次 "报价按钮被点击 "的文本。

Quote button clicked

我点击了按钮四次,所以文本被记录了四次

使用console.log() 将数据记录到浏览器控制台是一种常见的调试技术,用于调查程序中的问题。例如,你可以像我们上面做的那样,在事情发生时将一些文本记录到控制台。

如果你点击页面上报价按钮以外的其他地方,就不会发生任何事情。这是因为事件监听器只与报价按钮相连,所以getQuote ,只有当它被点击时才被调用。

喘口气,看看本步骤末尾的完整代码

使用API获取随机报价

目前,当报价按钮被点击时,我们将一些文本记录到控制台,但我们真正需要做的是获取一个随机报价并在屏幕上显示。

做到这一点的一个方法是在你的应用程序中硬编码报价,并从那里访问它。硬编码只是意味着将一个程序所依赖的数据放在程序本身中。这使得它很容易操作,但在不修改代码的情况下很难或不可能改变,这通常是不可取的。

一个更好的方法是使用API获得数据。这可能是一个我们自己建立的API,也可能是一个由其他人制作并提供给公众使用的API。大多数网络API以一种叫做JSON的格式传输数据,JSON是JavaScript对象符号的缩写。

有几个公共API提供随机报价。如前所述,我们将使用What Does Trump Think API,它提供唐纳德-特朗普的通用语录。

为了有效地使用API,你需要知道接收你需要的特定数据的URL、HTTP方法、查询参数和认证要求。你通常可以在你想使用的API的文档中找到这些信息。

例如,What Does Trump ThinkAPI的文档规定,我们使用以下URL来获得一个随机报价。

https://api.whatdoestrumpthink.com/api/v1/quotes/random

幸运的是,这个特定的API不需要认证,所以我们可以直接使用它,而不需要注册一个API访问密钥。让我们把这个URL端点保存在getQuote 函数声明上面的一个变量中。

const endpoint = 'https://api.whatdoestrumpthink.com/api/v1/quotes/random';

接下来,我们需要向API发出请求,抓取一个随机的报价。要做到这一点,我们将利用浏览器中的一个内置机制,称为fetch。修改getQuote 函数,使其看起来像这样。

async function getQuote() {
  // The `try` block executes the statements within it as usual.
  // If an exception is thrown, the statements defined in
  // the `catch` block will be executed.
  // Learn more here: https://javascript.info/try-catch
  try {
    const response = await fetch(endpoint)
    // If the response is not 200 OK...
    if (!response.ok) {
      // ...throw an error. This causes control flow
      // to skip to the `catch` block below.
      throw Error(response.statusText)
    }

    const json = await response.json();
    console.log(json);
  } catch (err) {
    console.log(err)
    alert('Failed to fetch new quote');
  }
}

这里有很多东西需要解读。首先,我们看到在函数前出现了async 关键字。这是用来指定该函数是异步的,意味着你可以在函数中使用await 关键字来暂停该函数,同时等待一个操作的完成。如果你试图在一个非异步函数中使用await ,JavaScript引擎将抛出一个错误。

你可以在这里了解更多关于async.await的信息。

在我们的getQuote 函数中,我们有一个try 块和一个catch 块。如果在try 块内发生错误,catch 块内的代码将被执行。这是一种控制流机制,允许我们在执行操作时发生错误(预期的或其他的)时作出适当的反应。

const response = await fetch(endpoint)

fetch 的最简单的用法是接受一个参数--你想获取的资源的URL--并返回所谓的Promise。Promise代表了一个操作的最终成功或失败,await 关键字被用来暂停函数,直到一个承诺被解决。

对获取请求的响应(在它被解决之后)被存储在response 变量中。如果请求成功,将收到一个200 OK的响应。如果不是,则意味着请求因某种原因而失败。

if (!response.ok) {
  throw Error(response.statusText)
}

这部分代码检查响应是否为200 OK。如果不是,将使用throw 关键字抛出一个错误对象。如果发生这种情况,或者在try 块内的任何其他地方发生错误,控制会立即转移到catch 块,并向用户显示一个警告,同时将实际的错误记录到控制台供检查。

假设响应确实是200 OK,if 块将被跳过,它之后的一行将被执行。

const json = await response.json();

在这里,response.json 方法读取响应主体,完成后将响应解析为JSON。我们之所以在这里再次使用await ,是因为json() 方法返回一个承诺。如果承诺被成功解析,JSON对象将被存储在json 变量中并被记录到控制台。否则,如果在解析JSON时发生错误,catch块将像以前一样执行。

试着点击几次报价按钮。你应该每次都能在浏览器控制台看到从 "特朗普怎么想 "收到的JSON对象。

JSON object in console

如果你检查JSON对象,你会看到报价被存储在message 属性中,每次都包含不同的报价。该对象还包含nlp_attributes 属性,在本教程中我们不需要这个属性。我们可以使用点符号访问消息键中的值,如:json.message 。如果我们想访问nlp_attributes 键的值,我们将使用json.nlp_attributes

getQuote 函数中,改变下面一行。

console.log(json);

console.log(json.message);

现在,再一次点击引号按钮几次。你会看到,只有报价被记录到控制台,而不是整个JSON对象。

Quotes displayed in the console

你也可以尝试关闭你的互联网连接,然后再试着点击按钮。这一次,窗口中会显示一个警告,并有一个错误记录到控制台。

喘口气,看看本步骤末尾的完整代码

在页面上显示报价

现在我们能够成功地从What Does Trump Think中获取随机报价,是时候在应用界面中显示它了。

让我们为此目的创建一个新函数。在getQuote 函数的下面输入这个函数。

function displayQuote(quote) {
  const quoteText = document.querySelector('#js-quote-text');
  quoteText.textContent = quote;
}

displayQuote 函数是向用户显示报价的代码的位置。它需要一个参数,即quote ,它是将在页面上显示的报价。

在函数体中,我们在quoteText 变量中保存了对id为 "js-quote-text "的元素的引用。接下来,我们将quoteText 元素的textContent 属性分配给我们想要显示的报价。

这样做的效果是将quoteText 元素的内容替换为一个单一的文本节点,其中包含从What Does Trump ThinkAPI收到的报价。

在这一点上,如果你再次点击报价按钮,页面上不会显示任何内容。这是因为虽然我们已经定义了displayQuote 函数,但我们还没有从任何地方调用它,我们必须在函数主体的代码被执行之前这样做。

因此,回到getQuote 函数,修改下面一行。

console.log(json.message);

displayQuote(json.message);

现在,每次从API收到新的报价时,displayQuote 函数就会被召唤出来,并将报价作为输入传给它。然后,我们在displayQuote 的正文中写的代码被执行,导致报价被显示在页面上。

试着点击几次报价按钮。你会看到每次都有一个新的随机报价显示在页面上。

GIF of quotes

喘口气,看看这一步结束时的完整代码

显示一个加载指示器

让我们把我们的应用程序提高到一个新的水平,在每次请求一个新的报价时显示一个加载指标。这向用户表明,一个操作目前正在进行中。我们还将禁用报价按钮,以便一次只能提出一个请求。

我们将使用的加载指标取自这个网站。如果你点击页面顶部的链接,你会看到用于产生每个效果的标记和样式。如果你不喜欢我选择的那个特定的旋转器,你可以用其他选项来替换它。

在HTML窗格中的两个<section> 元素之间添加以下代码。

<div id="js-spinner" class="spinner hidden">
  <div class="rect1"></div>
  <div class="rect2"></div>
  <div class="rect3"></div>
  <div class="rect4"></div>
  <div class="rect5"></div>
</div>

接下来,在CSS窗格中的所有其他样式下面放置以下代码。

.spinner {
  margin: 10px auto;
  width: 50px;
  height: 40px;
  text-align: center;
  font-size: 10px;
}

.spinner.hidden {
  visibility: hidden;
}

.spinner > div {
  background-color: #333;
  height: 100%;
  width: 6px;
  display: inline-block;

  -webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out;
  animation: sk-stretchdelay 1.2s infinite ease-in-out;
}

.spinner .rect2 {
  -webkit-animation-delay: -1.1s;
  animation-delay: -1.1s;
}

.spinner .rect3 {
  -webkit-animation-delay: -1.0s;
  animation-delay: -1.0s;
}

.spinner .rect4 {
  -webkit-animation-delay: -0.9s;
  animation-delay: -0.9s;
}

.spinner .rect5 {
  -webkit-animation-delay: -0.8s;
  animation-delay: -0.8s;
}

@-webkit-keyframes sk-stretchdelay {
  0%, 40%, 100% { -webkit-transform: scaleY(0.4) }
  20% { -webkit-transform: scaleY(1.0) }
}

@keyframes sk-stretchdelay {
  0%, 40%, 100% {
    transform: scaleY(0.4);
    -webkit-transform: scaleY(0.4);
  }  20% {
    transform: scaleY(1.0);
    -webkit-transform: scaleY(1.0);
  }
}

上述样式是用来产生动画效果的。了解它的工作原理对本教程并不关键,但如果你想学习CSS动画,可以从这里开始

此刻,由于hidden 类的存在,加载指示器被隐藏起来,其样式为visibility: hidden; 。当点击报价按钮并调用getQuote 函数时,我们需要移除hidden 类,并在请求完成后再次添加,不管是失败还是成功。同时,我们将禁用该按钮并再次启用它。

首先,在JavaScript窗格的最上面选择spinner 元素。

const spinner = document.querySelector('#js-spinner');

接下来,修改getQuote 函数,如下图所示。

async function getQuote() {
  // remove the "hidden" class on the spinner
  spinner.classList.remove('hidden');
  // disable the quote button
  newQuoteButton.disabled = true;

  try {
    const response = await fetch(endpoint)
    if (!response.ok) {
      throw Error(response.statusText);
    }
    const json = await response.json();
    displayQuote(json.message);
  } catch {
    alert('Failed to fetch new quote');
  } finally {
    // enable the quote button
    newQuoteButton.disabled = false;
    // add the "hidden" class back again
    spinner.classList.add('hidden');
  }
}

在该函数的顶部,隐藏类被从旋转器上取下,报价按钮被禁用。一旦操作完成,需要将它们恢复到原来的状态。这是在finally 块中完成的。这是为那些无论操作成功与否都需要执行的代码准备的。

按钮在禁用状态下的样式(在CSS窗格的第84-88行之间)如下所示。当disabled 属性被添加到按钮上时,它就会呈现出灰色的外观。

84.new-quote:disabled {
85  background-color: #cccccc;
86  color: #666666;
87  cursor: not-allowed;
88}

下面是结果。

GIF of loading indicator

喘口气,看看这一步结束时的完整代码

在推特上发布一个报价

为了完成这个应用,我们需要能够将页面上显示的每一句话推送出去。我们在页面上已经有了推特按钮(实际上是一个看起来像按钮的链接),但是点击它并没有任何效果。

<a class="twitter button" id="js-tweet" target="_blank" rel="noopener noreferrer">Tweet it!</a>

这是因为锚标签没有一个href 属性,所以它没有链接到任何地方。我们的任务是动态地设置这个属性,以便它能链接到Twitter的分享页面,并在文本输入中预先填入我们想在Twitter上发表的名言。

首先,选择你的JavaScript代码顶部的tweet按钮。

const twitterButton = document.querySelector('#js-tweet');

接下来,在displayQuote 之后创建如下所示的setTweetButton 函数。

function setTweetButton(quote) {
  twitterButton.setAttribute('href', `https://twitter.com/share?text=${quote} - Donald Trump`);
}

使用setAttribute 方法在推特按钮上设置href 属性,该方法需要两个参数。我们要设置的属性(href )和该属性的值(https://twitter.com/share?text=${quote} - Donald Trump )。

你可能想知道为什么我对第二个参数使用反斜线而不是单引号。这是因为我们想把quote 参数的值插入到Twitter的分享推特URL中。为了达到这个目的,我们将使用模板字面,允许嵌入表达式。

因此,如果quote的值是 "今天,伊拉克是恐怖主义的哈佛。"例如,${quote} 将被替换为该值,字符串变成

https://twitter.com/share?text=Today, Iraq is Harvard for terrorism. - Donald
Trump

这就是我们如何能够动态地将推特按钮的href 属性设置为一个URL,该URL预先填充了从What Does Trump ThinkAPI收到的任何报价。

让我们在getQuote() 函数中调用setTweetButton 函数,就在调用的下面。displayQuote()

displayQuote(json.message);
setTweetButton(json.message);

现在点击推特按钮应该打开Twitter的分享推特页面,你会看到文本框的内容已经预先填充了报价。

Tweet a Donald Trump quote

喘口气,看看本步骤末尾的完整代码

还有一件事...

如果你刷新页面,就不会显示引语,而且推特按钮也不起作用。我们可以通过在程序的末尾添加以下一行来轻松解决这个问题。

getQuote();

这在页面加载时调用了一次getQuote ,这样就可以获取一个报价并立即显示给用户。随后,新的报价将被请求并按需显示。

喘口气,看看这一步结束时的完整代码

总结

在本教程中,我们发现了如何使用JavaScript来创建一个交互式Web应用程序。我希望你从中学到了很多东西。你可以通过使用不同的API从头开始开发你自己的报价生成器来获得额外的练习,比如说这个

谢谢你的阅读,并祝你编码愉快