JavaScript-入门指南-一-

210 阅读1小时+

JavaScript 入门指南(一)

原文:Beginning JavaScript

协议:CC BY-NC-SA 4.0

一、JavaScript 简介

这些年来,avaScript 发生了很大变化。我们目前正处于一个 JavaScript 库的时代,你可以构建任何你想构建的东西。JavaScript 存在于客户机和服务器上,存在于桌面和移动设备上。

这本书的目的是帮助你理解语言是如何工作的,可以用它做什么,可用的资源,以及围绕语言和工具的一些生态系统。有时我会指出一些在技术面试中可能会被问到的问题,所有这些都是为了帮助你了解这个不断发展的社区。我将涉及的一些主题是

  • 理解 JavaScript 语法和结构

  • 创建易于理解和维护的脚本

  • 使用工具调试 JavaScript

  • 处理事件

  • JavaScript 如何在服务器上工作

  • 使 JavaScript 成为强类型语言的框架

  • JavaScript 应用框架及其工作原理

  • 从服务器检索数据

JavaScript 在现代 web 开发中是必不可少的;单页应用(spa)构成了大部分被创建的网站。理解 JavaScript 可以让你的网站增加交互性,并降低框架之类的东西的学习曲线。这并不是说你需要所有的框架,但是为了给你的网站增加任何层次的交互性,你需要 JavaScript。

足够的介绍——你已经通过这本书了解了 JavaScript,所以让我们在深入研究之前,先从较高的层面快速讨论一下 JavaScript。

在本章中,您将学习

  • 为什么 JavaScript 对开发人员来说很重要

  • 如何向 web 文档添加 JavaScript

  • 与 JavaScript 相关的面向对象的编程(OOP)

您可能已经接触过 JavaScript,并且已经知道它是什么以及它能做什么,所以我将首先快速地介绍一下该语言的一些基础知识及其功能。如果你已经很了解 JavaScript,而你只是想了解更多更新、更容易理解的特性和概念,你可以跳过一个头。然而,可能有些信息你已经忘记了,稍微回顾一下也无妨。

JavaScript 的原因

如前所述,JavaScript 无处不在。It 加上 HTML 和 CSS 都是你开发一个网站需要的工具。

您可以使用 JavaScript 在客户端和服务器端工作。这就使得对 JavaScript 开发者的要求非常高。对开发者来说,高需求意味着各种工作机会和有竞争力的价格。截至本文撰写之时,根据 Indeed,一家招聘网站( www.indeed.com/salaries/Javascript-Developer-Salaries )的数据,一名 JavaScript 开发人员在美国的平均年薪为 110841 美元。因此,JavaScript 不仅是一种值得研究的语言,它还是开发人员工具集的一个很好的补充。让我们快速讨论一下什么是 JavaScript,然后继续写一些代码。

JavaScript 是什么?

JavaScript 是一种解释型脚本语言。宿主环境将提供对执行代码所需的所有对象的访问。

JavaScript 的主要例子是向网站添加交互性的能力。这是因为解释器被嵌入到网络浏览器中,所以你不需要添加任何软件。

这使得 JavaScript 成为一种易于使用的语言,因为你所需要的只是一个文本编辑器和一个浏览器。在客户端,您可以添加交互级别,如响应按钮点击和验证表单内容。它还允许您利用浏览器内置的 API(应用编程接口)。给你的网站增加地理定位功能就是一个例子。

另一个用例是使用 Node.js 这样的环境在服务器上执行 JavaScript。服务器端 JavaScript 的一个例子是能够从数据库发出请求、响应 HTTP 请求和创建文件。

本书的大部分内容将集中在客户端;然而,从代码的角度来看,差别很小。

网页中的 JavaScript 和基本语法

将 JavaScript 应用于 web 文档非常容易;你所需要做的就是使用script标签:

<script>
  // Your code here
</script>

虽然这是向页面添加 JavaScript 的最简单方式,但不推荐这样做。这是内联 JavaScript 的一个例子。不采用这种方式建立网站的原因之一是,随着时间的推移,维护起来会变得很困难。想象一下,随着你的网站越来越大,保持一切井然有序是多么困难。

将 JavaScript 添加到 HTML 页面的首选方式是引用外部.js文件:

<script src="js/myfile.js"></script>

注意

HTML 5 要求script标签有自己的结束标签。这将确保向后兼容旧的浏览器。此外,在结束标签body之前添加标签script被认为是最佳实践。

JavaScript 语法

学习任何编程语言都与学习外语非常相似。有规则可循,这可能需要不同的思维方式。

编程中有很多问题需要解决。你花时间观察一种情况,并试图找出如何使用代码来解决问题。记住这一点,让我们从这个角度来讨论 JavaScript。

如果你想保留一条信息以备后用,这叫做变量。如果你还记得任何高中代数,总有一些例子,其中一个单词或字母代表一个值:

5 + x = 10

在这个例子中,x 是一个代表数字的变量。使用 JavaScript,您可以声明一个变量并赋予它一个值,然后在以后使用它:

var x = 5;
5 + x = 10;

上面的代码不是完美的 JavaScript,但是它说明了变量是如何工作的。第一行使用关键字var;这是语言内置的,只能在声明变量时使用。每行末尾都是分号,可以认为是句末的句号。JavaScript 解释器并不要求您拥有它。如果您没有添加分号,解释器将为您添加分号。为了更好地控制和阅读,建议您自己添加它们。

关键字var并不是声明变量的唯一方式。也可以使用其他关键字,如letconst。我将在第三章中讲述它们的不同之处以及何时应该使用其中一个。

另一种情况是当你有一些代码,你只想在需要的时候运行它。JavaScript 称之为函数。您可以编写一个函数,向其中添加您希望它执行的所有命令,然后让它等待,直到您需要它,就像这样:

function doMath(num1, num2){
      let sum = num1 + num2;
      return sum;
}

这个示例函数的名称是doMath。这允许从代码的其他部分引用或调用该函数。这个函数也接受两个变量或参数,sum1sum2。把它们看作你的函数的变量;它们只是表示函数正确执行所需的数据。在这种情况下,它是一组数字。

接下来是花括号({ })。它们包含您的代码块。你需要让这个函数工作的所有东西都在这里。您可以根据需要添加任意多行代码。一般来说,一个函数应该只执行一件事。以doMath为例,它只是把数字加在一起。如果你想让别的事情发生,应该写另一个函数。这将有助于调试过程。同样重要的是要注意,JavaScript 语言本身包含执行字符串操作和数学等任务的函数。

假设你想给自己留笔记。在这种情况下,您需要在代码中包含一些不会作为代码执行的内容。向代码中添加注释有两种方式:

 // single line comment

*/
multi line
comment
/*

在调试代码时,添加多行注释也很有用。例如,如果您不想删除您拥有的内容,但又不想让代码执行,那么您可以将它设置为注释。

到目前为止,您已经知道了如何解决一些问题:如何保存数据、执行函数以及在代码中留下提醒。

现在让我们花点时间来讨论一下代码是如何在浏览器中执行的。

代码执行

浏览器从上到下读取页面,因此代码执行的顺序取决于脚本块的顺序。一个脚本块<script></script>标签之间的代码;如果你有一个外部的.js文件,它也会从上到下读取。(还要注意,不只是浏览器能读懂你的代码;网站的用户也可以查看你的代码,所以你不应该在里面放任何秘密或敏感的东西。)下一个示例中有三个脚本块:

<!DOCTYPE html>
<html>
    <head>
         <script type="text/javascript">
                alert("First script Block");
                alert("First script Block - Second Line");
          </script>
     </head>
     <body>
           <h1>Test Page</h1>
           <script type="text/javascript">
                 alert("Second script Block");
           </script>
    <p>Some more HTML</p>
          <script type="text/JavaScript">
                alert("Third script Block");
                function doSomething() {
                      alert("Function in Third script Block");
                }
          </script>
        </body>
</html>

如果您尝试一下,您会看到第一个脚本块中的alert()对话框出现并显示消息

First script Block

接下来是第二行显示消息的下一个alert()对话框

First script Block - Second Line.

解释器继续向下,来到第二个脚本块,这里显示了alert()函数

Second script Block

第三个脚本块跟在它后面,显示一个alert()语句

Third script Block

尽管函数内部还有另一个alert语句,但它并不执行和显示消息。这是因为它在函数定义(function doSomething())中,函数中的代码只有在函数被调用时才会执行。

到目前为止,在本章中,您已经了解了 JavaScript 语言,看到了一些语法规则,了解了该语言的一些主要组成部分(尽管很简单),并运行了一些 JavaScript 脚本。你已经走了相当长的距离。在接下来的两节中更详细地研究 JavaScript 语言之前,让我们先来探索 JavaScript 语言的重要部分:函数对象。

功能

在上一节中,您看到了一些在您明确请求之前不会执行的代码。JavaScript 中的函数非常灵活。它们可以赋给变量,并作为参数作为属性传递给其他函数。函数也可以返回另一个函数。让我们来看几个例子:

var doMath = function(num1, num2) {
        var result = num1 + num2;
         return result;
};

var myResult = doMath(2,3);

这个例子说明了如何将一个函数直接赋给一个变量,然后用这个变量调用这个函数。这个函数接受两个数字作为参数。当数字被传递给函数时,它们被加在一起,结果被返回给调用该函数的代码:

function message() {
      return 'It's. the information age ';
}

function displayMessage(msgFunction, person){
   consoe.log(msgFunction() + person) //It’s the information age brother!
}

displayMessage(message, "brother!");

此示例将一个函数传递给另一个函数。第二个函数接收一个函数作为参数,然后在一个log命令中执行该函数。这个最初被称为message的函数返回一个字符串,该字符串将显示在同样在console.log方法中传递的字符串旁边。

函数是 JavaScript 语言中非常重要的一部分,我将在第五章中详细介绍它们。

我将在这里介绍的另一个概念是对象的概念。我还将在第四章中更详细地讲述对象。

目标

对象是 JavaScript 使用方式的核心。在许多方面,JavaScript 中的对象就像编程之外的世界中的对象。(确实存在;我只是看了一下。)在现实世界中,一个对象只是一个“东西”(很多关于面向对象编程的书把对象比作名词):一辆车,一张桌子,一把椅子,还有我正在敲的键盘。对象有

  • 属性 s (类比形容词):车是红色

  • 方法 s (类似句子中的动词):发动汽车的方法可能是转动点火钥匙

  • 事件 : 转动点火钥匙导致汽车启动事件。

面向对象编程试图通过模拟现实世界的对象来简化编程。假设您正在创建一个汽车模拟器。首先,创建一个汽车对象,赋予它类似于颜色当前速度的属性。然后你需要创建方法:也许一个 start 方法来启动汽车,一个 brake 方法来减速汽车,你需要向其中传递关于刹车应该压得多紧的信息,以便你可以确定减速效果。最后,你需要知道你的车什么时候出了问题。在 OOP 中,这被称为事件。例如,当油箱油量不足时,汽车会发出通知(仪表盘上的灯)让你知道该加油了。在这段代码中,您将监听这样一个事件,以便您可以对此采取一些措施。

面向对象编程使用这些概念。这种设计软件的方式现在非常普遍,并且影响了编程的许多领域——但是对你来说最重要的是,它是 JavaScript 编程的核心。

您将使用的一些对象是语言规范的一部分:例如,String对象、Date对象和Math对象。这些对象提供了许多有用的功能,可以节省您大量的编程时间。例如,您可以使用Date对象从客户端(比如用户的设备)获取当前日期和时间。它存储日期并提供许多有用的与日期相关的功能,例如将日期/时间从一个时区转换到另一个时区。这些对象通常被称为核心对象,因为它们独立于实现。浏览器还可以通过对象进行编程,您可以使用这些对象来获取有关浏览器的信息并更改应用的外观。例如,浏览器提供了Document对象,它代表 JavaScript 可用的网页。您可以在 JavaScript 中使用它来向 web 浏览器的用户正在查看的 web 页面添加新的 HTML。如果您在不同的主机上使用 JavaScript,例如 Node.js 服务器,您会发现托管 JavaScript 的服务器公开了一组非常不同的主机对象,因为它们的功能与您想在 web 服务器上做的事情有关。

注意

尽管本节从面向对象的角度介绍了 JavaScript,但该语言本身是一种多范式语言,您可以将它用作函数式、命令式或事件驱动语言。

随着本书的深入,您将更深入地了解对象:JavaScript 语言的核心对象、浏览器使用 JavaScript 访问和操作的对象,以及您自己的自定义对象。不过现在,你只需要知道 JavaScript 中的对象是可以用来给网页添加功能的实体,并且它们可以有属性和方法。例如,Math对象在其属性中有一个表示 pi 值的属性,在其方法中有一个生成随机数的属性。

摘要

在这一章中,你学习了高级的 JavaScript。您了解了为什么 JavaScript 是开发人员工具箱中的一个好工具。您还学习了一些基本语法以及如何向代码中添加注释。您看到了如何使用变量保存数据以备后用,以及如何使用函数按需准备代码。另一个主题是代码执行(浏览器如何从上到下读取代码)。最大的部分是对对象的讨论。如果您打算在客户机或服务器上使用 JavaScript,理解对象是 JavaScript 的核心是很重要的。

接下来,作为一名 JavaScript 开发人员,您将看到一些可用的工具。

二、JavaScript 和开发工具

如果您是开发 JavaScript 应用的新手,有很多东西需要考虑。一个经常被问到的问题是“我从哪里开始?”这就是本章的目的,帮助你找到可以帮助你用 JavaScript 开发应用的资源和工具。我还将介绍这些工具的一些基本用法。这一章的目的不是作为一个明确的指南或产品定位,但它应该给你指出正确的方向。

这一章我将介绍几个主题:

  • 教程和资源

  • 集成开发环境(ide)

  • Node.js 和 npm 快速介绍

  • Git 和 GitHub

教程和资源

有很多网站可以用来收集关于 JavaScript、HTML 和 CSS 的信息。以下是一些更有用的网站:

选择这些网站是因为它们提供好的信息并且是免费的。还有其他网站提供了极好的信息。Egghead.io ( https://egghead.io/ )提供一些免费教程和订阅服务。我也推荐网站前端大师( https://frontendmasters.com/ )和复数视线( www.pluralsight.com )。

集成开发环境

IDE 是用来编辑代码的软件。一些编辑器通过使用扩展来处理任何语言。其他的是为了优化一种语言的体验。和浏览器一样,有很多编辑器可以选择。

一个流行且免费的选择是 Visual Studio 代码( https://code.visualstudio.com/ )。它提供了编辑 HTML、JavaScript 和 CSS 以及其他语言所需的一切。

其他编辑器包括

这些工具将帮助您开发任何类型的软件应用。它们通常具有的特征是

  • 自动完成(Autocomplete): IDE 指出如何完成你正在编写的代码行。

  • 调试工具:你可以在 ide 中观察变量的值。

  • 内联命令行接口:IDE 让你在命令行上执行操作。这对于处理节点模块和版本控制系统(如 Git)非常有用。

  • 可扩展的:他们能够添加一些特性,让你可以用除了基本的 HTML、CSS 或 JavaScript 之外的语言进行编辑。

如果您已经安装了 Visual Studio 代码(图 2-1 ,您可以制作一个简单的 HTML 页面,用于本练习的其余部分。

img/313453_3_En_2_Fig1_HTML.jpg

图 2-1

Visual Studio 代码是一个运行在 Windows、MacOS 和 Linux 上的源代码编辑器

在计算机上的任意位置创建一个文件夹,然后在编辑器中打开该文件夹。你将制作你的第一个 HTML 页面。在学习本章中的示例时,您将把该文件夹设置为本地 web 服务器。

我不会深入 HTML 如何工作的所有细节;对于这个练习,我会给你一个快速模板。这应该足够让你开始了。

Control-N(如果您使用的是 MacOS,则为 Command-N)将打开一个新文件。将以下代码添加到您的文档中:

<!DOCKTYPE html>
    <html lan="en">
        <head>
               <meta charset="utf-8">
               <title>Chapter 2</title>
                <body>
       <p>Hello World</p>
               </body>
        </head>
    </html>

另存为index.html。这是你的第一页。此时,您可以将它拖放到您的浏览器中。这和你从网上得到的服务是不一样的。没关系,因为您将在下一节中解决这个问题。

Node.js

这将是对 Node.js 的一个非常高层次的介绍,在以后的章节中,你将会看到一些展示这个工具强大功能的用例。

Node 是一个开源的 JavaScript 环境,允许您在服务器端执行 JavaScript。这使您能够使用相同的语言在客户端和服务器上运行代码。

节点使用模块集合。模块构成了 Node 的核心功能,允许你使用文件系统、网络协议(HTTP、DNS 等)等。)、二进制数据以及与数据库对话的能力。

获得节点的最快方法是直接从网站( https://nodejs.org/ )。在网站上,下载被描述为“推荐给大多数用户”的版本(在撰写本文时,它是 8.11.3)。安装完成后,您距离将上一个示例中的文件夹设置为 web 服务器又近了一步。

节点没有图形用户界面。要使用它,您需要习惯使用命令行。这个例子中没有很多命令。以下是你要做的事情:

  • 下载一个名为 http-server 的模块,它可以让你像从一个 web 服务器上下载文件一样为你的文件夹提供服务。

  • 导航到包含源代码的文件夹,使用该模块启动您的站点并在浏览器中查看它。

大多数 ide 都有内置的终端模拟器。如果您使用的是 Visual Studio 代码,请转到“视图”,然后选择“集成终端”。这将是您项目的命令行界面。

您要做的第一件事是确保该节点已经安装。在提示符下,键入

node -v

这应该会返回节点的版本号,如图 2-2 所示。如果这不起作用,请确保安装了该节点。

img/313453_3_En_2_Fig2_HTML.jpg

图 2-2

Visual Studio 代码有一个内置的终端,允许您执行命令

如果一切正常,安装模块,如图 2-3 所示。这是使用节点包管理器。包管理器是一种组织代码库的方式。它可能包含像这样的实用程序或像 Angular 这样的其他库来帮助构建您的项目。

图 2-3 显示加载节点模块的结果。让我们来看一下第一行,因为这是你需要做的事情。节点包管理器命令以npm开始。要安装一个模块,告诉npm你想安装的模块的名字。如果你想让那个模块在你电脑的任何地方都能工作,就加上-g

img/313453_3_En_2_Fig3_HTML.jpg

图 2-3

安装 http-server 模块。它允许你使用硬盘上的任何文件夹作为本地服务器。

将命令行界面内置到 IDE 中的一个好处是,它确切地知道您正在哪个文件夹中工作。所以现在您可以让您的模块将文件夹转换成本地 web 服务器。键入以下命令:

http-server

您应该会看到类似图 2-4 的内容。

img/313453_3_En_2_Fig4_HTML.jpg

图 2-4

运行 http-server 模块,使硬盘上的任何文件夹成为本地 web 服务器

只需几个简单的步骤,您现在就可以让硬盘上的文件夹显示 HTML 页面了。你看到的两个数字是你文件夹的 IP 地址。第一个永远是你的机器。第二个数字是您网络上的地址。如果你网络上的任何人想看你在做什么,这是很有用的。

冒号后面是一组数字。在图 2-4 中,它是 8080,这是网络上的端口,通过它提供 HTML 页面。数据可以通过不同的端口发送或接收到服务器。这个号码,8080,经常被使用。未来的例子将显示从不同的端口提供 HTML 数据。

您可以按住 control 键并单击任一地址,您的浏览器将会打开并显示如图 2-5 所示的页面。

img/313453_3_En_2_Fig5_HTML.jpg

图 2-5

在浏览器中查看运行 http-server 模块的结果。你可以提供 HTML 页面。

有了新的设置,您现在可以创建和编辑 HTML、JavaScript 和 CSS 了。使用 Node,您可以将硬盘上的任何项目文件夹转换成本地服务器。这为现代前端开发人员工作流提供了一些重要的工具。

现在就出现了跟踪代码的问题。当您对网站进行更新并添加更多文件时,记录您所做的更改是一个很好的习惯。如果你有问题,这不仅仅是做一个“撤销”。想象一下,您向站点添加了一些功能,然后意识到您需要将它备份到以前的状态。版本控制对于项目来说非常重要,尤其是当不止一个人参与项目时。

在下一节中,我将讨论 Git 以及 GitHub 和 Bitbucket 之类的网站。

版本控制系统

使用任何类型的版本控制系统都是一个好主意。项目会随着时间的推移而增长和变化,获得项目的“快照”是很好的。Git 已经成为实现这一点的流行方式。在本节中,您将

  • 安装 Git。

  • 使用 Git 来跟踪您的文件。

  • 创建一个 GitHub 帐户。

  • 将您的项目上传到 GitHub。

Git 很容易安装。前往 https://git-scm.com/ 。您可以为您正在使用的任何操作系统安装它。该网站提供了学习 Git 如何工作的资源。免费电子书可以在 https://git-scm.com/book/en/v2 找到,其他教程可以在 https://try.github.io/ 找到。

这是另一个没有 UI 的工具。与 Node 类似,您可以使用命令行来移动。你可以使用一些工具来解决这个问题。Sourcetree 是一个免费的工具,可以帮助你使用 Git ( www.sourcetreeapp.com/ )。本章中的示例将继续使用命令行。

注意

如果你运行的是 Windows,安装 Git 之后你可能会额外安装一个叫 Git Bash 的东西。该工具模拟 Unix 或 MacOS 上的命令类型。和集成编辑器真的是一回事。唯一的区别是它是一个独立的应用。现在,只需使用您的集成终端。

如果您打开了 IDE,请返回终端。首先要做的是确保已经安装了 Git。一个快速的方法是查看安装的版本。在终端内,键入

git –version

类似于检查 Node 的版本,这将返回当前安装的 Git 版本。

一旦您确认 Git 已经安装并运行,您就可以初始化该文件夹来使用 Git。你这样做:

git init

这将设置您的文件夹使用 Git。通过创建一个名为.git的不可见文件夹(你可以改变你的文件夹设置来看到它),Git 将不仅能够跟踪文件,而且能够随着时间的推移跟踪文件的内容。

现在您可以检查文件的状态,看看它们是否被跟踪。在命令行中,键入

git status

这应该会输出项目的状态(图 2-6 )。

img/313453_3_En_2_Fig6_HTML.jpg

图 2-6

Git 使用分支模型。当前分支是 master。在这个例子中,index.html 没有被 Git 跟踪。有关 Git 的完整教程,请参考“教程和资源”一节。

此时,Git 没有跟踪您的文件。如果您想更改它,请键入

git add index.html

参见图 2-7 。

img/313453_3_En_2_Fig7_HTML.jpg

图 2-7

Git 现在知道它需要跟踪 index.html。

这告诉 Git 跟踪这个文件,所以如果文件的内容有任何变化,Git 都会知道。确保 Git 知道跟踪什么非常重要。同样重要的是提交。你可以把它们看作是你的文件在任何时候的快照。

在命令行中,键入

git commit -m "my first commit"

看一下图 2-8 。您要求 Git 执行提交并添加了-m标志。这允许您添加一条关于您将要进行的提交的消息。

img/313453_3_En_2_Fig8_HTML.jpg

图 2-8

使用 git commit 命令创建文件的快照

所有这些都是本地的。Git 知道文件正在被修改。如果您对当前文档进行了任何更改,例如,如果您更改了消息,Git 将会知道。进行更改,保存文件,然后再次检查状态。参见图 2-9 。

img/313453_3_En_2_Fig9_HTML.jpg

图 2-9

Git 识别出文件的内容已经更新

现在文件不同了,您可以再次添加文件并进行新的提交。所有这些提交都保存在一个日志中。如果您想查看提交的历史,请键入

git log

图 2-10 显示了该分支的历史。每次提交都带有一个散列值、作者和提交的日期/时间。

img/313453_3_En_2_Fig10_HTML.jpg

图 2-10

Git 日志将显示所有以前的提交

在图 2-10 中,您可以看到,在向您的提交添加消息时,您确实应该比这里看到的更具描述性。该消息可以让您很好地了解正在发生的事情,而无需查看代码。

此时,您可以在 Git 中创建一个存储库。添加要跟踪的文件,并制作该文件的多个快照。如前所述,所有这些都是本地的,如果您自己工作的话,这是没问题的。如果你和其他人一起工作呢?然后,您需要一个服务器,不仅可以托管您的代码,还可以跟踪更改。这就是 GitHub 和 Bitbucket 等网站的用武之地。

对于你的目的,这些网站做同样的事情。它们是存放代码的地方。您创建的存储库可以是公共的,也可以是私有的。

顾名思义,公共存储库可供任何人查看、下载或克隆。人们也可以通过发送变更和错误修复来为项目做出贡献。

GitHub(在撰写本文时最近被微软收购)允许免费使用公共、私有和开源的存储库。Bitbucket 为小团队(最多五个用户)免费提供无限的私有存储库。

你可以注册这两个网站。本节的截图将使用 GitHub。登录后,您可以创建新的存储库。这将是一次公开回购。你可以看到它附在你的账户上(图 2-11 )。

img/313453_3_En_2_Fig11_HTML.jpg

图 2-11

在 GitHub 上创建新的存储库

一旦创建了 repo,您将获得关于如何将本地机器连接到 repo 的说明。将这个例子命名为 MyFirstProject(图 2-12 )。

img/313453_3_En_2_Fig12_HTML.jpg

图 2-12

关于如何将本地 repo 连接到 github 的说明

当你想让来回购的人知道这个项目是关于什么的时候,你可以更新这个文件。当您第一次开始一个项目时,关于它是如何制作的、如何安装以及使用了什么技术的任何注释通常都在自述文件中。

如果您阅读了关于 Git 的部分,这些命令应该很熟悉。项目的初始化和添加 README 文件应该在那一节中已经熟悉了。这里的新功能是增加了远程功能。

Git 使用了一个叫做 remotes 的概念;这些遥控器是服务器上文件的副本。这将允许您将文件推(或上传)到远程源。

您可能会注意到示例中的-u。它设置远程分支的默认位置。将来,当您尝试使用git pull时,这将从远程服务器获取(或下载)任何更新版本的代码。

一旦告诉本地 Git 实例远程位置,下一个命令就是将文件推送到服务器。

如果安装了 IDE 或 GitBash,所有这些命令都可以在终端窗口中执行。

推送到远程回购的代码并没有给你一个功能性的网站。请记住,这些服务是用于跟踪代码随时间的变化,并不显示您的网站所做的结果。

一旦你把你的代码推送到回购,你就可以检查站点了(图 2-13 )。

img/313453_3_En_2_Fig13_HTML.jpg

图 2-13

使用命令行将当前本地项目推送到 GitHub

注意

当尝试将您的代码推送到服务器时,可能会要求您输入密码。这是您刚刚创建的帐户的密码。一旦进入,一切都会起作用。

如果你厌倦了每次添加密码,你可以设置 Git 记住你的密码。进入 https://help.github.com/articles/caching-your-github-password-in-git/ 为你正在使用的操作系统设置 Git。

如图 2-14 所示,GitHub 会显示你的项目的所有已知信息。如果您决定拥有一个开源项目,这将为您提供一个可视化的参考和控制更新的方法。如果您创建了一个私有存储库,您也可以获得所有相同的信息。

img/313453_3_En_2_Fig14_HTML.jpg

图 2-14

github 上一个有效的公共存储库

摘要

在这一章中你做了很多工作。到目前为止,您已经选择了一个 IDE,设置了版本控制,甚至创建了一个远程存储库。唯一缺少的是一个你可以向人们展示的现场。第十三章是你将构建一个可以部署到实时服务器上的工作站点的地方。

现在,安装并设置好所有的开发工具后,您就可以深入研究这种语言并让浏览器为您服务了。下一章将介绍 JavaScript 如何处理数据类型。

三、JavaScript 变量

在本章中,你将学习一些基本的 JavaScript 数据类型。当你想让计算机保存一些信息时,这些信息就是某种“类型”例如,电子邮件地址是一种称为字符串的类型。电脑识别出你的电子邮件地址是一系列字母、数字和符号。

如果您要执行计算,计算机会将您用来执行计算的数据视为数字。它可以计算数字,但不能计算字符串。

在阅读本章的过程中,您将探索其中的一些数据类型。此外,您还将看到变量是如何工作的。

在这一章中,我使用术语“环境”它只是表示 JavaScript 在哪里执行,可能是在浏览器中,也可能是使用 Node.js。

在 JavaScript 中声明变量

JavaScript 是一种松散类型或动态语言。这只能说明 JavaScript 非常适合你。取决于你对此的感受,这可能是好的也可能是坏的。当您希望环境保存一个值时,您必须声明一个变量。

如果你创建了一个变量并赋予它一个值,那么你可以把这个值改变成完全不同的值。让我们看看实际情况(图 3-1 )。

img/313453_3_En_3_Fig1_HTML.jpg

图 3-1

使用 Chrome 中的控制台测试 JavaScript 变量

打开 web 浏览器,找到开发人员工具,然后转到控制台。当你在控制台时,输入图 3-1 中的代码。将userName的值设置为"Violator";你可以看到它在引号中,这使它成为一个字符串。然后它被重新分配给数字42,它的字符周围没有引号,所以它被认为是一个数字。

JavaScript 允许不同的变量声明方式。你需要遵守一些规则。

在创建变量的时候,首先需要让环境知道这是你的意图。这里我将介绍关键字的概念。

本例中使用的关键字是var(变量的缩写)。这个关键字将告诉环境你想要保留一些信息。

JavaScript 中还内置了其他关键字,可以让环境知道如何处理日期、数字,甚至是浏览器中当前的 HTML 文档。您将在未来的示例中探索它们。

在这里,您创建了一个名为userName的变量。这种大写叫做骆驼大小写,其中每隔一个单词的第一个字母都有一个大写字母。这不是创建变量的唯一方法,但你会经常看到。

在 JavaScript 中命名变量时,不能以数字或符号开头(考虑像@ a 符号这样的字符)。所有变量都可以以字母开头;无论是大写还是小写都没有关系。其后的任何字符都可以使用数字或符号。变量名称中也不能有空格。看看清单 3-1 中的例子。

var  user name = "Violator"; // not a valid variable
var !username = "Violator"; // not a valid variable
var 1user_name = "Violator"; // not a valid variable
var user_name = "Violator"; // valid variable
var userName = "Violator"; // valid variable
var username = "Violator"; // valid variable

Listing 3-1Valid and Invalid Ways to Create a Variable

这并不是一个关于变量命名的详尽列表,但是它应该给你一个在 JavaScript 中如何命名变量的概念。

注意

要获得更详尽的列表,看一看同样由 Apress 出版的 JavaScript 食谱

一旦您告诉环境您需要保存一些信息,接下来的事情就是将数据赋给该变量。该数据属于某种类型。在本例中,您将字符串"Violator"赋给了新创建的变量。

字符串是可以以文本形式使用的数据表示。电子邮件的内容、用户名和密码都是字符串。在 JavaScript 中创建字符串变量时,您可以在想要使用的字符周围使用单引号或双引号。重要的是要始终如一。你不能以单引号开始,以双引号结束,或者反过来。

在字符周围使用引号是很重要的,因为将"1"(引号中的数字)或1(只是数字)赋给一个变量是有区别的。第一个是字符串;第二个是数字。数字在 JavaScript 中是一种不同的数据类型。符号也是如此;"@"被认为是一串。

每条语句的末尾都有一个分号。这就像是句尾的句号。如果你不添加一个,环境会很好地试图理解你的意思,并会在它认为必要的地方插入一个。

您可能会遇到的一个问题是,环境将分号插入了错误的位置。这将在运行时产生错误。

然而,如果你添加它,它确实使你的代码更容易阅读。因此,作为最佳实践,在每个语句的末尾添加分号。

总之,在 JavaScript 中声明变量时,使用关键字(在本例中是var),命名变量,然后赋值。

在 JavaScript 中重新分配变量

JavaScript 中的一些变量可以被重新分配。这意味着,一旦你给了一个变量一个值,很容易,在某些情况下,有必要回到那个变量,给它一个全新的值。

在 JavaScript 中更新现有变量的一个例子是,如果您正在跟踪一个用户是否有权访问某些东西。您可以使用变量作为标志来检查某人的状态。这里有一个例子:

if ( hasAccess === true ){
     hasAccess = false;
}

这个例子说明了一个变量可以有多个值。变量hasAccess的值可以是真或假,这里您使用一个if语句来检查当前的值。

使用 true 或 false 作为值使您的变量成为布尔数据类型,这意味着它只能有一个值。布尔值只能为真或假。

有时候你可能需要一个变量有一个常量值。这种情况下的关键词是const。任何时候你都不希望这个变量的值改变。让我们看一个例子。

无法重新分配的变量

如果您有不应该更改的数据怎么办?在这种情况下,您希望变量包含一个无论发生什么都保持不变的值。JavaScript 提供了这样一种变量类型,它被称为常量

常量是一种变量,在第一次赋值后就不能更改。图 3-2 显示了一个试图改变常量值的例子。

img/313453_3_En_3_Fig2_HTML.jpg

图 3-2

标记为常量的变量在第一次给定值后不能改变

使用关键字const将允许您做与使用var相同的事情,不同之处在于您不能在以后重新赋值。在图 3-2 的例子中,你会看到浏览器抛出一个错误。

虽然不能将单个值重新分配给常量,但如果要分配一个对象,则该对象的属性可以更新。清单 3-2 包含了一个例子。

const myObj = {}
myObj.firstName = "Vince"
console.log(myObj)
{firstName: "Vince"}
  myObj.lastName = "Clarke"

console.log(myObj)
{firstName: "Vince", lastName: "Clarke"}
myObj = {}
Uncaught TypeError: Assignment to constant variable.

Listing 3-2Assigning an Object to a Constant

因为常数是一个对象,所以您可以访问该对象的属性并更新它们。我将在下一章全面阐述对象的概念。现在,把一个物体想象成名词。

这个“东西”有属性,(高度,宽度,颜色等。).对象的属性(这个“东西”)可以更新。但是变量myObj不能被重新分配。在最后一行中,您可以看到当试图给对象重新分配一个新值时,浏览器将抛出一个错误。

*到目前为止,我已经讨论了创建变量、为变量赋值以及更新变量的能力。我展示了不能给变量赋值的例子。

下一节将介绍环境如何控制变量的范围、上下文或“作用域”。

只能在单个代码块中使用的变量

我们先定义一个代码块。当您使用 JavaScript 函数时,经常会看到一组花括号({})。这是你的代码块。这些括号中的任何内容都是将要执行的代码。当使用关键字let来声明你的变量时,浏览器知道你赋给变量的值只能在那个块中看到。清单 3-3 包含了一个例子。

for (let i = 0; i < 10; i++) {
  console.log(i); //output = numbers between 0 and 9
}

console.log(i) //error i is undefined

Listing 3-3A let Statement Only Has a Value While Inside the Code Block

这里有一个循环。它创建一个名为i的变量,从值0开始,只要i没有值10,它就给i加 1。

当这个循环发生时,您在控制台中打印出i的当前值。这将显示值09(只要该值小于10)。

所有这些都发生在花括号内。一旦这个循环完成,变量i消失。环境不再考虑它了。所以,当你试图在下一行引用这个变量时,它会抛出一个错误。

所以,概括一下,当代码在花括号中执行时,使用的变量有一个值。当块执行完所有代码后,代码的其他部分就不能再访问该变量了。它已经不存在了。

这是letvar的主要区别之一。清单 3-4 显示了一个使用var的类似例子。

for (var i = 0; i < 10; i++) {
  console.log(i); //output = numbers between 0 and 9
}

console.log(i) //returns 10

Listing 3-4A var Statement Will Retain Its Value After the Code Block Has Been Executed

如果var的行为类似于let,您将得到相同的结果,其中i的值将返回undefined。相反,循环的行为完全相同,但是在这种情况下,您的变量是可以在循环外部访问的,并打印出10的值。

var关键字创建的变量是基于它当前的执行上下文来声明的。在第一种情况下,循环不在函数内部;因此,该变量在范围上成为全局变量。

在它的正下方,有一个相同的循环,但是这次是在一个函数块中。然后,该变量位于不同的执行上下文中。如果你试图在函数之外打印变量的值,浏览器将抛出一个错误。参见列表 3-5 和 3-6 。

for (var i = 0; i < 10; i++) {
        console.log(i); //output = numbers between 0 and 9
}

console.log(i) //returns 10

Listing 3-5When Creating a Variable with the var Keyword, It Will Exist Inside the Current Execution Context. The Code Here Is Outside a Function, Making the Context Global.

function goLoop(){
   for (var i = 0; i < 10; i++) {
        console.log(i); //output = numbers between 0 and 9
   }
}
goLoop();
console.log(i) //returns error

Listing 3-6When Creating a Variable Using the var Keyword Inside a Function, the Execution Context is Local to the Function

当处理变量时,在var上使用let将确保变量只存在于你创建的代码块中。变量表现不同的原因是因为*变量提升。*下一节将更详细地解释吊装。

面试问题

varconstlet有什么区别?说出一些 JavaScript 数据类型。

可变提升

在面试中,你可能会被问及variable hoisting。听起来比实际困难得多。这一节将澄清关于它如何工作的任何困惑。

当浏览器经历其编译阶段时,它会将函数和变量声明放入内存。

下面是一个如何声明变量的示例:

userName = "Stephanie";

这与使用关键字let初始化变量是不同的。这里,你给一个变量赋值,没有使用任何关键字。因为没有给这个变量分配关键字,所以它被认为是一个全局变量。这和使用var关键字是一样的。

下一个示例显示了工作代码,尽管它看起来不应该工作:

userName = "Stephanie";
console.log(userName); //returns Stephanie
var userName;

虽然看起来结果应该是undefined,但它返回了正确的值。与其他示例类似,在范围或执行上下文没有设置为代码块的情况下,使用关键字var

被提升的变量移动到当前作用域的顶部。因此,如果没有函数,变量将被移到全局范围。如果一个变量(使用var关键字)在一个函数中被声明,它将移动到该作用域的顶部。

变量提升在变量被声明时生效,而不是被赋值时生效。如果您声明了一个没有值的变量,即使它没有值,它仍然会被提升。清单 3-7 包含了一个例子。

function checkVars(){
   console.log(username); //returns undefined
   var username = "Hunter";
  console.log(username); //returns Hunter;
}
checkVars() //executes function;

function checkVars(){
   var username;
   console.log(username); //returns undefined
   username = "Hunter";
  console.log(username); //returns Hunter;
}

checkVars() //executes function;

Listing 3-7Variables Are Hoisted When They Are Declared, Not When They Are Assigned a Value. These Two Examples Produce the Same Result.

面试问题

什么是吊装,它是如何工作的?

严格模式

严格模式告诉浏览器运行更受限制的 JavaScript 版本。这是你可以选择的。在严格模式下运行您的代码将防止浏览器犯错误,从而难以优化代码。

当某些语法可能会被添加到 JavaScript 的未来版本中时,它还会阻止该语法的执行。它还消除了无声错误(没有浏览器反馈的错误)。

您可以对整个 JavaScript 文件或单个函数调用严格模式。要将其添加到整个脚本的顶部,请将use strict;添加到您的代码中。

在清单 3-8 中,如果你声明了一个变量而没有初始化它,浏览器会抛出一个错误。

x = "think tank" //Reference Error: x is not defined

Listing 3-8Variables Declared While in Strict Mode Must Be Initalized

如果您试图将一个对象赋给一个尚未初始化的变量,就会出现这种情况:

X = {user:"Player One", score:1000} //Reference Error: x is not defined.

单个函数可以在严格模式下运行,只需在函数体中添加相同的代码行。参见清单 3-9 。

function myFunction(){
   "use strict";
 // add commands here
}

Listing 3-9Using “use strict” Inside a Function Declaration Will Tell the Browser to Run Just That Function in Strict Mode

使用严格模式可以确保浏览器以最有效的方式执行您的代码,并在出现错误时提供最多的反馈。

摘要

本章讲述了如何创建变量以及何时应该使用某种类型的变量。一些变量存在于代码块中,而另一些则无法更新。

您探索了可变提升的概念。当使用关键字var时,变量可以被提升或移动到执行上下文的顶部。这可能会导致这样的情况,即使一个变量已经被声明,它可能还没有一个值,所以将返回undefined作为一个值。

最后一节介绍了在严格模式下运行代码的一些好处。它将执行更高效的 JavaScript 版本,并在浏览器忽略错误时通过返回错误给出更好的反馈。

下一章将更详细地介绍对象是如何工作的,并介绍一种叫做数组的对象。*

四、JavaScript 对象和数组

最后一章介绍了变量。使用变量,您可以保存信息以备后用。随着应用变得越来越复杂,记住用户设置、URL 或表单内容的能力变得越来越重要。

使用变量会遇到的一个问题是,不管是什么类型,一次只能保存一条信息。如果你给一个变量赋一个新值,原来的值就没了。这可以防止您持有复杂类型的数据。

为了模拟复杂的事物而创造大量的变量也是不合理的。例如,如果有人正在填写表单,那么很难管理表单中每个项目的所有变量。

对象是分组数据的好方法。JavaScript 中也使用对象来帮助您在应用中执行操作。

document对象就是一个很好的例子。它可以让你浏览浏览器中的 HTML 页面。

Math对象可以帮助执行数学运算,而Date对象不仅可以让您检索当前日期,还可以帮助计算过去或未来的日期。它还可以帮助您以正确格式输入日期。

本章将讨论什么是 JavaScript 对象,对象如何工作,并解释什么是属性和方法以及它们如何工作。

首先,我们来谈谈主机对象和本机对象的区别。然后,您可以开始学习如何用 JavaScript 创建自己的对象。

宿主对象或本机对象

这不是一个很长的部分,但是它将阐明宿主对象和本机对象之间的区别。由于 JavaScript 是一种可以在多种环境下工作的语言,因此代码本身在不同的环境下可能会有不同的表现。举例来说,浏览器中可能有些东西,如历史或位置,在服务器上是不可用的。还可能有其他环境,如具有独特功能的移动设备。在很大程度上,我将讨论 JavaScript 如何在浏览器中工作。

您可能会遇到术语“同构 JavaScript”在这种情况下,JavaScript 应用能够同时在客户端(浏览器)和服务器上运行。

本机(有时称为内置)对象是 ECMAScript 标准的一部分。ECMAScript 本身不是一种语言,而是一种脚本语言的规范。JavaScript 是该规范最流行的实现。其他实现包括 ActionScript。基于这种理解,ECMAScript 规范将定义独立于环境的对象。这些对象包括

  • 目标

  • 排列

  • 承诺

  • 数据

宿主对象是代码运行环境的一部分。例如,浏览器中的宿主对象包括

  • 窗户

  • 文件

  • 历史

  • 航海家

注意

如果您对浏览器中有哪些本地对象感兴趣,请查看位于 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects .的 MDN web 文档

您构建的许多应用将利用这两种类型的对象。您通常需要一种类型的对象来帮助处理另一种对象。

最常用的主机对象之一是document对象。您可以使用它来检查当前文档中发生了什么。这方面的一个例子是更新旅行日期。使用documentDate对象,您可以按需更新浏览器中的信息。

既然您已经对本机对象和内置对象有了很好的理解,那么让我们深入了解如何使用泛型对象的一些细节。

面试问题

宿主对象和本机对象有什么区别?举几个例子。

解释对象

我提到了能够将数据分组在一起。在这里我将开始解释这是什么意思。

在 JavaScript 中,几乎所有东西都是对象。一个对象代表一个“东西”使用事物(或名词)的概念类似于你如何描述现实生活中的物体。现实生活中的物体有时可以被描述为具有高度、重量或颜色。

记住这一点,当想要获得一个对象的细节时,这些细节被称为属性。JavaScript 有一长串对象的内置属性和方法。让我们从浏览器的document对象开始进入列表。

文档对象简介

如果您想知道浏览器中当前加载的文档的标题,您可以这样做:

let currentTitle = document.title;

这里,您正在创建一个变量,就像上一章一样,并为它赋予当前文档中标题的值。

为了让这个有意义,让我们从右边开始,然后向后看。在这里,您将带至document对象。该对象由浏览器(您的宿主环境)提供。

document对象有一个名为title的属性。你可以用点符号(在单词之间用一个.,就像在document.title中一样)来问“你的头衔是什么?”问题。使用点,你分离出你感兴趣的主要事物,然后要求更多的细节。在这种情况下,您需要文档的标题。如果你想保留这些信息以备后用,你需要一个地方来存放它们。

如果您想从浏览器中检索信息,这很好。但是,您可能会发现自己需要更新浏览器中当前的信息。

其语法非常相似:

document.title = "My New Title";

您可以看到代码与您之前的示例非常相似。在这种情况下,您正在设置属性的值,而不是获取值(编程中经常使用术语“getter”和“setter”)。

本地和宿主对象都有能力使用属性和一种叫做方法的东西来处理信息。方法是允许您对特定数据执行某种处理的函数。一个例子是,如果你想在你的 HTML 文档中找到基于 CSS 类的东西。document对象可以帮你做到这一点:

let myClassElements = document.getElementsByClassName('myCssClass');

与标题示例类似,document对象将执行一个函数(记住这个词,以后再说),它将查看整个 HTML 文档,并将使用这个类名的每个 HTML 元素发送回来。

这个方法唯一需要的是一个参数(引号中的字符串)。这样,函数就知道要寻找什么了。

JavaScript 中的其他对象更加具体。在下一个例子中,我将讨论什么是数组以及如何使用它。

面试问题

方法和属性有什么不同?使用 getters 和 setters 是什么意思?

数组和堆栈

数组是一个更加专门化的对象。它具有一般对象所没有的功能。数组不仅仅擅长保存数据组;它还擅长将数据保持有序。

形象化阵列工作方式的一种方法是想象一堆盘子。堆栈中的每个板代表数据。当讨论数组中每个元素的位置时,使用的术语是元素。如果你想象从下到上数盘子,通常你会从一开始数,这是有意义的。有些编程语言从 1 开始计数;但是,JavaScript 从零开始计数:

let myArray = new Array();
    myArray.push('some data'); //count starts at zero
    myArray.push('some other data') //count is now one

console.log(myArray[0]) // some data

这是一个向数组添加数据的例子。每向堆叠中添加一个板,堆叠中的板数量就增加一个。JavaScript arrays 称之为length

我把数组描述成一堆盘子。栈也是一种数据结构。关于数据结构的问题也可能成为你在面试中遇到的问题。

下一个示例显示了如何创建数组并开始添加值:

//create an array using the new operator
let myArray = new Array();

//create an array using square braces
let myOtherArray = [];

这里你有不同的方法来得到相同的结果。每一个都将属于一个数组的相同的属性和方法赋给你的变量。这将把你的变量变成一个数组对象。

既然已经有了 array 对象,就可以使用它附带的属性和方法了。此示例显示了如何添加数据(将样品板添加到堆叠中):

let nameArray = new Array();
    nameArray[0] = 'Hunter';
    nameArray[1] = 'Hayes';
    nameArray[2] = 'Grant';

之前,我说过要数你的盘子。在这个例子中,您为每个新创建的元素分配一个索引值;最终结果是你的数组或堆栈。

这是确保为特定元素赋值的好方法。您可能遇到的一个问题是无序添加元素的能力。这里有一个例子:

let nameArray = new Array();
    nameArray[0] = 'Hunter';
    nameArray[2] = 'Grant';

虽然这段代码是正确的,而且浏览器不会告诉你它有什么问题,但是如果你试图获取一个还没有赋值的元素,浏览器会返回undefined

获取数组的长度

其他语言要求在创建数组时在数组中设置许多元素。JavaScript 不会这样做。您可以添加任意多的元素。

在将元素添加到数组中之后,如果您想要获得当前正在处理的元素的总数,您需要使用length:

var numberOfElements = myArray.length;

在前面的示例中,在向数组中添加数据之前,您通过编号显式地调出了每个元素。如果你只是想给下一个打开的槽添加一些东西,数组对象有一个名为push的方法。这也是确保元素的编号顺序没有间隔的好方法。

let nameArray = [];
    nameArray.push("Norah");
    nameArray.push("Emily");

let numOfNames = nameArray.length; //return 2

在这个例子中,使用push方法向数组中添加两个名字,然后询问数组的长度。如果您想单独访问每个元素,您可以通过元素编号来查询每个元素:

console.log(nameArray[0]); //returns Norah
console.log(nameArray[1]); //returns Emily

你询问关于你的两个元素的信息。记住 JavaScript 对条目排序的方式,你需要索引 0 和 1,而不是索引 1 和 2。

您可以为数组的值分配字符串,但是需要注意的是,数组可以保存您喜欢的任何类型的数据。数组的目的是保存数据组。

既然可以向数组中添加信息,那么数组对象中内置了许多有用的工具供您使用。让我们先过一遍循环。

使用循环和过滤器

现在您的数组中已经有了信息,您会经常发现需要在数组中查找特定值的情况。您可能会寻找一个电子邮件地址,甚至是一个空值。遍历所有这些信息的一种方法是使用循环。由于这是经常发生的事情,JavaScript 在 array 对象中内置了一些特性来允许您这样做。

let myArray = ['one', 'two', 'three', 'four'];
myArray.forEach((value, index, array) => {
      console.log(value); //current value
      console.log(index); //current index
      console.log(array); //entire array
});

您了解了对象既有属性又有方法。属性帮助描述关于对象的事情,方法是对象用来处理数据的函数。

数组对象有一个名为forEach的方法。它让您迭代或遍历数组中的每个元素。

查看每个项目的方式是使用函数。我将在下一章详细讨论函数。目前,函数是让环境为您工作的一种方式。当你想要某种动作发生时,你创建一个函数并告诉环境你需要它做什么。

在这个例子中,您有一个forEach方法。在这个方法中,您添加了一个函数。这个函数由一组括号、一个箭头和一些花括号组成。

箭头函数实际上是一个等号,旁边有一个大于号:=>

括号包含定义函数可用数据的值。这里的值是占位符,您可以随意称呼它们。为了说明这一点,我将这些参数称为valueindexarray ( value是正在循环的元素的当前值,index是当前索引号,array是整个数组)。

这个例子显示了一个名为console.log的命令。这是开发人员经常使用的东西。

使用浏览器开发工具时,您会看到一个称为控制台的部分。在这里你可以输出你正在处理的东西的当前值。

也可以直接在控制台中编写 JavaScript。当你点击回车键时,它将被执行并更新当前页面。

使用forEach方法,您可以遍历整个数组。如果你想过滤掉数组中的一些信息呢?您可以使用filter方法来这样做。filter方法将根据你创建的测试结果返回一个新的数组。

为了完成这项工作,我将引入条件句的概念。条件句问“这是真的吗?”

当您遍历数组并对每个值使用您的条件时,结果将是一个新数组:

let numberArray = [1,2,3,4,5,6,7,8,9];
let oddNumbers = numberArray.filter((value, index, array) => {
      if(value % 2){
          console.log(value);
          return value;
      }
});

为了更好地理解它,我们来分解一下。该数组只是一组从 1 到 9 的数字。下一行是保存filter方法结果的变量。这和你之前做的差不多。方法filter内部有一个函数。该函数与forEach方法具有相同的属性。

不同的是,你添加了一个条件:如果当前值不能被 2 整除。如果这是真的,那么取当前值并从中创建一个全新的数组,并将所有其他属于相同参数的元素添加到这个新数组中。

构建到数组中的另外两个方法是mapreduce方法。

map方法让您遍历数组的所有元素,并对每个元素执行某种类型的操作。然后,通过返回函数的结果形成一个新数组,类似于筛选器示例:

let mappedValue = [1,2,3].map( (value, currentValue, currentIndex, array)=> {
   return value * 10;
});
console.log(mappedValue) //returns [10,20,30]

使用reduce方法允许您通过将数组的所有值相加来创建一个值,使用第一个值作为起点,并将所有其他元素的值加到第一个值上:

let reducedValue = [10,1,2,3,4,5,6,7,8,9].reduce( (value, index, array)=> {
   return value + currentValue;
});
console.log(reducedValue) //returns 55

面试问题

给定一个数字数组,如何删除所有重复项并只返回唯一值?给定一个数组的长度,哪个数字代表该数组中的最高索引?

摘要

本章讲述了对象的概念。此外,我还谈到了如何存在语言固有的对象(ECMAScript)和环境向您公开的对象(主机对象)。我还说明了对象如何同时具有属性和方法。属性一般被认为是描述数据的方式,而方法是对数据进行操作的方式。我用数组进一步说明了这一点。数组有类似于length的属性,它描述了数组中有多少元素。

数组也有类似于push的方法,可以向数组中的下一个可用元素添加新数据。这个对象也有可以处理数组内部数据的方法或函数,使用内置方法为您提供新的数组。

我引入了条件,您可以让您的程序评估您的数据,并根据是否满足某个条件做出选择。我还介绍了箭头函数。

下一章将更详细地介绍常规函数是如何工作的,并将它们与箭头函数进行比较。我还将讨论关键字this以及上下文的概念。

五、JavaScript 函数和上下文

上一章介绍了对象的概念,并用数组对象来说明如何用对象来帮助你开发应用。

您可以使用主机对象和环境对象。环境变量可能会因环境而异,因此浏览器中可用的对象可能并不总是与服务器上可用的对象相匹配。

本章将介绍功能。函数被用作对数据执行选项的一种方式。函数也很有用,因为它们可以根据需要多次重用。

创建和使用函数有不同的方式。你还会学到一些重要的概念,比如上下文范围。这些概念在尝试处理其他对象和变量时非常重要。关于范围和背景的问题在面试中经常出现,所以很好的了解它们是如何工作的。首先,让我们创建一个简单的函数。

进行函数声明

这可能有几个名字,比如函数 声明 ,函数定义,或者函数语句。在任何情况下,创建一个基本函数都需要几样东西:

  • 函数的名称

  • 以逗号分隔的可选参数列表

  • 将对数据执行操作的代码,用花括号括起来

这听起来可能比实际情况复杂得多。下面是一些代码:

function myFunction(optional, data, here ){
      //some code goes here
}

这个伪代码示例显示了一个基本的函数声明。它以单词 function 开始,然后是你给这个函数起的名字。括号内是可选参数。根据您正在执行的操作,该函数可能需要处理数据。接下来是花括号。在大括号中,您放置了函数完成其工作所需的所有 JavaScript 代码。让我们创建一个更现实的例子:

function  addNumbers(num1, num2){
      return num1 + num2;
}

let result = addNumbers(2,3);
console.log(result);

这个例子很简单,但是更真实。您有一个名为addNumbers的函数和两个由逗号分隔的参数。这类似于拥有一个变量。它们表示函数执行时将使用的数字。记住这一点,您执行一个将两个值相加并返回结果的操作。

return命令是新的,所以让我们花点时间来研究一下。函数总是会返回一个值,并且大多数时候这个值是undefined。然而,如果你知道你想要一个值返回,那么return语句是必要的。return语句将向调用函数的地方返回一个值。

这个例子结合了前几章的一些东西。您使用let语句创建一个名为result *的变量。*这个变量的值将是函数addNumbers的结果。

这经常被描述为调用执行一个功能。你通过名字调用一个函数,根据函数的工作方式,你传递参数给它。

本例中的最后一行将输出浏览器控制台中变量的值。

在前一章,我解释了 JavaScript 中几乎所有的东西都是对象。既然知道可以将对象赋给变量,那么也可以将对象赋给函数:

const addNumbers = function(num1, num2){ return num1+ num2 };
addNumbers(2,3);

在这里,您创建一个常量变量,并将您的函数赋给它。然后你可以像调用函数一样使用这个变量。在这种情况下,该函数没有名称,因此是一个匿名函数。这被称为函数表达式

命名函数也可以做同样的事情。使用命名函数有助于出错时的调试过程。这样更容易找到错误发生的地方。

在审查代码时,你经常会发现大量的函数表达式和声明。然而,有其他方法可以用更短的语法创建函数。这个更短的语法改变了函数与代码其余部分的关系,并很好地介绍了关键字this和作用域的概念。

使用箭头功能

箭头函数表达式是创建函数表达式的一种较短方式。在这一节中,我将解释其中的一些区别,并介绍作用域的概念和关键字this 的用法。

您将从一个简单的箭头函数开始:

const arrowFun = (num1, num2)  => { num1 + num2 };

这里,您像以前一样创建一个常量变量。你丢掉了function关键字,使用了有时被称为的粗箭头 : = >。花括号仍然存在。

您可能还注意到您删除了关键字return。当使用箭头函数时,如果函数体只有一行,JavaScript 将使用所谓的隐式返回

这只是箭头函数与您以前使用的函数类型不同的方式之一。以下是使用箭头函数时要记住的一些其他示例:

const arrowFun = num1 => num1 * 2;

如果只有一个参数,则不需要将它放在括号中。

const arrowFun =  _ => 2
const arrowFun = () => 2

这两种说法是一样的。如果没有要传递的参数,可以使用空括号或下划线。使用下划线只是减少了一个需要键入的字符,但对于不熟悉 arrow 函数所有变体的其他开发人员来说,这可能会使他们感到困惑。

const arrowFun = (num1, num2) => 2

就像介绍性的例子一样,如果你的函数需要不止一个参数,那么它需要在括号中:

const arrowFun = (num1, num2)_ => { if (num1 > num2){
      return 'first is larger than second';
}};

如果函数的代码块(花括号之间的部分)超过一行,那么它需要一个 return 语句:

const arrowFun = () => ({firstName:Paul});

通常,当使用函数时,代码块位于花括号之间。当试图使用 arrow 函数返回一个对象文本时,这会导致一个问题。这是因为解释器不知道你试图返回一个对象。解决这个问题的方法是在外部使用括号,在内部创建对象文字。

如果你只是想让一个函数为你做一些工作,任何一种方法都可以。使这一个不同的是关键字this。现在是引入上下文和函数范围概念的好时机。

关键字 this 是如何工作的?

我先解释一下关键词this是什么,什么时候用。它不是你像变量或函数那样声明的东西。这更像是一个起点。

举例来说,您将创建一个 JavaScript 文件,并从一个空的 JavaScript 文件中查看this的值。创建一个 HTML 页面,并使用它来加载 JavaScript 文件。你的代码应该如清单 5-1 所示。

console.log(this)

Listing 5-1Load a Simple JavaScript File into the Browser

在 browser developer tools 中打开控制台,您应该会得到如下所示的输出:

Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, ...}

我将关键字this描述为一个起点。所以,在这个当前实例中,this指的是window对象。

在浏览器中,window对象被认为是一个全局对象。如果使用 Node.js,情况就不一样了。根据 MDN web 文档,“全局对象是始终存在于全局范围内的对象。”此作用域对所有其他作用域都可见。

在清单 5-2 中,您需要函数中this的值。根据函数调用执行的位置,可能会产生相同的结果。

function getThis()(
    console.log(this)
}

getThis(); //returns Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, ...}

Listing 5-2Get the Current Context of a Function in the Global Scope

代码中调用函数的地方称为*执行上下文。*执行上下文决定了this 的值。

注意

如果在严格模式下运行这段代码,结果将是undefined

当试图从对象内部获取this的值时,执行上下文是不同的。见清单 5-3 。

var myObj = {
     getThis : function ()(
      console.log(this)
          }
     }

myObj.getThis() //returns {getThis: ƒ}

Listing 5-3Get the Value from Inside an Object

在这个例子中,起点在对象myObj *内部。*方法被调用时,this的值就是对象本身。

清单 5-4 引入了一些新的东西来帮助解释关键字this与执行上下文相关联。它将介绍什么叫做生命(立即调用函数表达式)。顾名思义,它是一个会立即执行的函数。

(function () {
  var person = "Friend";
  console.log(this); // returns Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, ...}
   console.log("Hello " + this,.person); //returns undefined

 console.log("Hello " + person); //returns Hello Friend
})()

console.log("Hello = " person)_; //returns reference error variable not defined.

Listing 5-4Referencing a Variable from Inside and Outside the Function Scope

无需调用该函数,该函数将在加载到浏览器后立即执行。

在前面的例子中,在全局范围内声明的变量可以在函数内部访问。在当前示例中,立即调用的函数有自己的函数范围。

您可以从输出中看到,该函数位于全局范围内。函数内部声明的所有变量都存在于该函数范围内。

这样做的好处之一是,原本会出现在全局范围内的变量,现在会出现在生命范围内。这是许多开发人员使用的技术,以避免“污染”全局范围。函数和变量永远不会是全局范围的一部分。这也防止了覆盖可能具有相同名称的其他函数或变量。

面试问题

生命是什么,它如何防止函数和变量污染全局范围?

执行上下文是全局的,但变量本身是函数的局部变量,不能被示例中的最后一行访问。

请注意,关键字this引用的是window对象。试图使用关键字this访问变量person将导致返回一个值undefined

您可以看到,如果不小心的话,使用关键字this引用属性和方法(变量和函数)会产生意想不到的结果。JavaScript 确实提供了一些更可预测的方法来控制执行上下文。

使用调用、应用和绑定方法

当调用一个函数时,调用发生的位置将决定该函数的执行上下文。这也是关键字this值变化的原因之一。使用bindapplycall方法可以让您控制上下文,并为您提供一种更可预测的使用this的方式。

让我们从bind关键词开始。您可以设置关键字this将用作起点的对象。清单 5-5 给出了一个例子。

var myObj = {
   person: "Friend"
}

function sayHello() {
      console.log("Hello " + this.person);
}

sayHello() //returns Hello undefined

var tryAgain = sayHello.bind(myObj) //assigning the value of this to the object myObj

tryAgain() // returns Hello Friend

Listing 5-5Using the Keyword bind to Set the Value of this

使用bind *,*你设置你想要使用的对象作为起点。在本例中,您将值设置为名为myObj的对象。

myObj为起点,您可以访问对象的属性(在本例中是person属性),而不会遇到浏览器声明值未定义的错误。

callapply方法类似。主要区别在于,不仅可以定义执行上下文,还可以将信息传递给想要执行的方法。清单 5-6 展示了一个调用call方法的例子。

sayHello(message){
  console.log(message, this.mainCharacter);
}

const  characters = {
       mainCharacter: "Elliot"
}

sayHello.call(characters, "Hello "); //returns Hello Elliot

Listing 5-6Using the call Method

JavaScript 中的函数是对象。这些函数对象有自己的一组属性和与之相关联的方法。

在清单 5-7 中,您使用call方法来指定您想要使用的函数的执行上下文。您告诉函数sayHello使用call方法,将执行上下文的起始点分配给 characters 对象,并将一个参数列表传递给sayHello函数,在那里它可以正确执行。在本例中,您只传递了一个。

apply方法非常类似。最大的区别是apply方法需要一个数组。

sayHello(firstName, secondName, thirdName){
  console.log(this.message, firstName); //returns Hello Elliot
  console.log(this.message, secondName); //returns Hello Tyrell
  console.log(this.message, thirdName); //returns Hello Dominique
}

const show = {
      message: "Hello"
}

sayHello.apply(show, "Elliot ", " Tyrell ", "Dominique ");

Listing 5-7Using the call Method

面试问题

一个函数的执行上下文是什么,你能改变它吗?在浏览器的全局范围内可以找到什么对象?

这里你的函数类似于你用call方法做的。传递一个数组可以让你使用该数组的元素作为函数的参数。

从这些例子中你可以看到,根据使用方式的不同,使用关键字this可以有多种不同的形式。

记住函数也是一个对象(本地或宿主对象)也很重要,这些对象有它们自己的属性和方法。你学到的方法有call apply bind *。*这些方法帮助控制执行上下文,执行上下文控制关键字this的值。

当你和其他开发人员一起工作时,你会发现闭包。一个简单的解释是闭包是函数内部的函数。在下一节中,您将探索闭包是如何工作的。

理解闭包

在上一节中,我解释了闭包作为另一个函数中的一个函数的概念。由于 JavaScript 在使用函数时处理范围的方式,您可以做一些有趣的事情。

在函数中创建的函数和变量存在于所谓的词法范围中。内部函数不仅可以访问自己范围内的变量和函数,还可以访问外部函数范围和全局范围内的变量和函数。同样重要的是要注意,这在另一个方向不起作用。

清单 [5-8 展示了内部函数如何访问自身外部的变量。

sayHello(firstName, lastName){
  let msg = "Greetings ";
   function intro(){
     return  msg + firstName = " " + lastName;
    }

 return into();

}

sayHello("Professor" , "Falken");  //returns "Greetings Professor Falken";

Listing 5-8Using a Closure to Illustrate an Inner Function’s Access to Other Scopes

这个例子说明了内部函数如何访问外部函数。外部函数执行内部函数(intro),并将结果返回到调用它的地方。内部函数返回由外部函数和内部函数的变量组成的字符串。然后使用console.log方法在浏览器控制台中显示结果。

如果您来自其他语言,如 Java,您应该熟悉声明公共和私有方法和属性的概念。当调用一个公共方法时,这些语言允许你隐藏一个方法如何在类中处理信息的一些内部工作。

JavaScript 没有提供这种能力,但是通过使用闭包,您可以编写具有相同效果的代码。清单 5-9 展示了一个不使用函数就不能立即访问变量的例子。

const sayHello = function(){
      let greetingMsg = "Greetings ";

      function msgTo(firstName, lastName){
         greetingMsg = greetingMsg + firstName + " " + lastName;
      }

   return {
            sendGreeting: function(firstName, lastName){
                   msgTo(firstName, lastName);
            }

            getMsg: function(){
                   return greetingMsg;
            }
      }
  }

const createMsg = sayHello();
createMsg.sendGreeting(("Professor" , "Falken");
console.log(creatMsg.getMsg()); //returns "Greetings Professor Falken";

Listing 5-9Using a Closure to Create Private Variables

在本例中,您声明了一个闭包,并将其赋给变量sayHello。调用闭包时,它返回一个对象,该对象的方法可以访问其函数范围之外的变量。

调用函数sayHello用两个方法返回一个对象,sendGreetinggetMsg

调用sendGreeting方法更新变量greetingMsg。除了使用sendGreeting函数,该变量不可访问。

要查看更新后的变量,可以调用另一个方法getMsg。它查看内部变量并返回其更新后的值。在这种情况下,您会收到消息“您好,Falken 教授。”

因为变量greetingMsg在外部函数中,所以不能从全局范围访问它。

同样需要注意的是,如果将sayHello的结果赋给第二个变量,它将独立于第一个实例。函数的执行方式将完全相同。但是,这些值可能不同。这里有一个例子:

const createMgs2 = sayHello();
createMsg2.sendGreeting("David", "Lightman");
console.log (createMsg2.getMg()); //returns Greetings David Lightman

在这里,你使用完全相同的功能,最终得到不同的结果。

面试问题

如何在 JavaScript 中创建私有变量?

摘要

本章开始概述函数对于应用开发的重要性。您探索了如何使用call applybind等方法来控制函数的执行上下文。您还了解了关键字this在不同的执行上下文中有不同的含义。

本章还讨论了函数表达式和箭头函数之间的区别,以及为什么理解它们之间的区别很重要。

还讨论了闭包和寿命。立即调用的函数是将变量和方法排除在全局范围之外的一种方式,而闭包是创建私有变量和方法的一种方式,类似于来自像 Java 这样的强类型语言的人所理解的。

函数是 JavaScript 的重要组成部分。同样重要的是调用函数的能力。通常函数是作为事件的结果而被调用的。下一章将介绍事件在 JavaScript 中是如何工作的。它还将涵盖来自环境的事件和用户创建的事件。

六、JavaScript 和事件

在上一章中,你学习了函数是如何工作的,以及箭头函数和函数表达式之间的区别。您还了解了执行上下文是如何控制关键字this的,以及用于创建一种更可预测的处理方式的内置方法。

函数经常因为一个事件而被调用。事件可以是页面加载或按钮点击。JavaScript 允许您“监听”事件,然后执行一个函数来响应事件。

在这一章中,你将学习如何使用前几章的知识,并开始使用事件开发一些与浏览器的交互。您还将探索事件冒泡的概念。

有时,内置事件可能不包含您想要做的事情。然后,您可以为您的应用定制事件。

document 对象是许多包含名为addEventListener *的方法的对象之一。*这个方法有两个参数,一个是您正在监听的事件,另一个是当事件发生时将执行某项操作的函数。

对于您正在使用的环境( https://developer.mozilla.org/en-US/docs/Web/Events )有一个很长的事件列表,但是对于这个例子,让我们只计算页面何时被加载。

在本例中,您将监听事件DOMContentLoaded。事件DOMContentLoaded让您知道页面的内容已经加载到浏览器中并且已经被解析。清单 6-1 展示了一个这样的例子。

document.addEventListener("DOMContentLoaded", function(){
    console.log(this); //returns #document
});

Listing 6-1Adding an Event Listener to Know When the Page Loaded

当您在浏览器开发工具中打开控制台时,您将看到此事件的结果。它应该可以让您访问刚刚加载的整个文档。目前,文档中没有太多内容,但要点是这个匿名函数是在事件发生时执行的。

面试问题

命名一个事件,让您知道页面的内容已经被加载和解析。

可以添加和删除事件侦听器。当您想要为同一个对象分配不同的侦听器时,这为您提供了灵活性。

清单 6-2 展示了一个为按钮添加和移除事件监听器的例子。

document.addEventListener("DOMContentLoaded",onPageLoad);

function onPageLoad(){
   let theButton = document.getElementById("myButton");
         theButton.addEventListener("click", handleButtonClick);
}

function handleButtonclick(){
   console.log("button clicked");
   let theButton = document.getElementById("myButton");
         theButton.removeEventListener("click", handleButtonClick);
}

Listing 6-2Adding and Removing Event Listeners

此示例使用命名函数而不是匿名函数。逻辑是这样的。事件DOMContentLoaded被触发并调用函数onPageLoad *。*该函数创建一个名为theButton的局部变量,并通过其 id 引用 HTML 页面中的按钮。然后,它添加一个事件侦听器,等待按钮被单击。

当按钮被点击时,它调用一个名为handelButtonClick的函数。该函数在控制台中打印出按钮被单击的信息。然后,它创建自己的引用文档中相同按钮的局部变量,并删除事件侦听器。当用户想第二次点击它的时候,这个按钮就不起作用了。

在这里,您使用DOMContentLoaded事件来设置应用做您想做的一切。

当页面加载时,将事件侦听器添加到按钮。在这个用例中,您只希望它在一次点击中处于活动状态。当单击按钮时,删除事件侦听器,使用命名函数使其不活动。这很有帮助,因为在出现错误的情况下,您可以直接引用函数,如果您使用匿名函数,就不能这样做。

向对象添加事件侦听器还允许您为不同类型的事件添加侦听器。清单 6-3 展示了一个向同一个按钮添加多个事件的例子。

document.addEventListener("DOMContentLoaded",onPageLoad);

function onPageLoad(){
   let theButton = document.getElementById("myButton");
         theButton.addEventListener("click", handleButtonClick);
         theButton.addEventListener("mouseover", mouseOverEvent);
         theButton.addEventListener("mouseout", mouseOutEvent);
}

function handleButtonclick(){
   console.log("button clicked");
}

function mouseOverEvent (){
   console.log("mouseover");
}

function mouseOutEvent (){
   console.log("mouseout");
}

Listing 6-3Adding Multiple Events to a Button

您使用DOMContentLoaded事件告诉应用向您的按钮添加多个事件监听器。这些侦听器知道按钮何时被单击、滚动和关闭。在每个实例中,当事件被触发时,它会触发一个函数,在浏览器控制台中打印出一条消息。

有时候,您可能想让浏览器停止它通常会做的事情,而让 JavaScript 接管。下一节将使用preventDeault方法解决这个问题。

使用预防默认

一些阻止浏览器执行正常功能的例子是在使用链接或表单时。链接通常被认为是一种进入网站中另一个页面的方式。

如果你想让某人点击一个链接,而不是让浏览器转到另一个页面,你可以使用preventDefault方法。

当事件发生时,事件对象被传递给事件处理函数。该对象包含关于刚刚发生的事件的信息。

例如,如果你想知道被点击的链接,你可以询问target。清单 6-4 给出了一个例子。

document.addEventListener("DOMContentLoaded",onPageLoad);

function onPageLoad(){
let  theButton = document.getElementById("myLink");
       theButton. addEventListener("click", function(evt){
        evt.preventDefault();
        console.log(evt);
       console.log(evt.target);
});

Listing 6-4Finding the Object That Was Clicked

这个例子类似于前面的例子,在前面的例子中,当加载文档时,click 事件侦听器通过它的 id 被添加到 HTML 页面中的 anchor 标记。

单击后,一个事件对象被传递给包含刚刚发生的事件信息的函数。这个对象拥有的方法之一是preventDefault方法。它阻止浏览器访问分配给该链接的 URL。

还可以通过使用console.log方法查看整个事件对象。它在浏览器开发工具中打印结果。

下一行显示target。这是调度事件的对象。在您的情况下,它是被点击的项目。开发者工具显示目标的 HTML。

表格提交后通常会转到另一页。如果您想在提交前对表单内容进行验证,可以阻止表单向服务器提交内容。这将使您有机会在发送数据之前验证表单。

当使用 JavaScript 阻止浏览器在内容被验证之前提交表单时,也可以使用preventDefault方法*。*见清单 6-5 。

document.addEventListener("DOMContentLoaded",onPageLoad);

function onPageLoad(){
    let myForm = document.querySelector("form");
         myForm.addEventListener("submit", onSubmit);
}

function onSubmit(evt){
   console.log("form submitted");
  evt.,preventDefault();
}

Listing 6-5Using the preventDefault Method with a Form

使用其他例子中的一些代码,等待DOMContentLoaded事件发生。一旦事件发生,就调用onPageLoad函数。

方法搜索 HTML 页面中的表单对象。然后在表单中添加一个事件侦听器,希望知道它何时会尝试提交任何数据。

当点击提交按钮时,提交事件触发onSubmit函数*。*这将覆盖试图提交数据并通过使用preventDefault方法转到另一个 HTML 页面的浏览器。

它还在控制台中打印消息“表单已提交”。使用这种方法将确保浏览器不会做任何意外的事情。

面试问题

如何阻止浏览器执行其正常功能?

您已经看到了可以控制浏览器正常工作方式的例子。这使您有机会在提交之前评估数据,或者创建一个单页面应用,而无需转到另一个 HTML 页面。

浏览器中的大量工作都与事件相关联。需要理解的一个重要概念是事件传播。事件传播是指您希望同时讨论事件的冒泡阶段和捕获阶段。下一节将详细解释这一点。

事件传播

事件传播处理浏览器中的各种对象如何能够接收事件。在上一节中,我展示了如何将侦听器分配给一个对象。该对象将等待事件发生,然后调用一个函数来处理该事件。

传播的一个阶段是事件 冒泡 *。*这种情况的一个例子是点击div中的一个按钮。点击的target是按钮本身。

但是,事件会从按钮向上传播到其父级。这个过程将继续,直到事件遇到window对象。清单 6-6 展示了这是如何工作的。

document.addEventListener("DOMContentLoaded",onPageLoad);

function onPageLoad(){
  let myButton  = document.getElementById("myButton");
        myButton.addEventListener("click", onButtonClick);

 let container = document.getElelmentById("container");
       container.addEventeListener("click", onContainerClick);

     document.addEventListener("click", onDocumentClick);
     window.addEventListener("click", onWindowClick);

}

function onButtonClick (evt){
   console.log("button clicked");
}

function onContainerClick (evt){
   console.log("container clicked");
}
function onDocumentClick (evt){
   console.log("document clicked");
}

function onWindowClick (evt){
   console.log("window clicked");
}

Listing 6-6Illustrating Event Bubbling

一种模式正在形成:您等待文档加载,然后将事件侦听器分配给 HTML 文档中的项目。

在本例中,您正在寻找 ID 为myButton的按钮。当侦听器被添加时,它调用函数onButtonClick并在控制台中打印出按钮被单击的消息。

由于事件冒泡,事件上升到div标记,它的事件监听器处理该事件。

从这里开始,documentwindow对象都可以处理事件。

您可以在浏览器控制台中看到结果:

button click
container click
documnnt click
window click

您可以看到,一旦按钮被单击,事件就传播到窗口对象,而无需您做任何事情。这是浏览器处理事件的默认方式。您可以通过使用stopPropagation方法来阻止这种情况发生。它防止其他对象在事件冒泡时监听事件。

清单 6-7 与清单 6-6 大部分相同。唯一的区别是在div标签接收事件后添加了stopPropagation方法。这使得documentwindow无法接收事件。

function onContainerClick(evt){
  console.log("container clicked");
  evt.stopPropagation();
}

Listing 6-7Using the stopPropagation Method

这不是代码的完整示例;我只是展示了重要的部分来说明这个例子。当调用事件时,事件对象被发送到事件处理程序。您使用这个对象来调用stopPropagation方法。

这将阻止链中更高位置的任何其他对象接收该事件,即使它们有该事件的侦听器。

浏览器处理事件的另一种方式是通过事件捕获。这与冒泡正好相反,在冒泡中,浏览器从文档顶部开始,一路向下到达目标。

要让浏览器使用这种处理事件的方法,您需要告诉addEventListener方法使用捕获而不是冒泡。清单 6-8 显示了事件如何从window对象向下传播。

function onPageLoad(){
   let myButton = document.getElementById("myButton");
        myButton.addEventListener("click", onButtonClick, true);

 let container = document.getElementById("container");
      container.addEventListener("click", onContainerClick, true);

      document.addEventListener("click", onDocumentClick, true);
    window.addEventListener("click", onWinowClick, true);
'}

Listing 6-8Using Event Capturing

将最后一个属性添加到addEventListener方法告诉浏览器启用事件捕获。然后堆栈从上到下开始:

window click
documnnt click
container click
button click

将最后一个属性添加到addEventListener方法告诉浏览器启用事件捕获。然后堆栈从上往下开始。

这两个例子展示了事件如何在浏览器中移动,要么从事件源移动到顶部,要么从window对象向下。

此时,您使用的事件已经构建到环境中。有时,您可能需要制作一个自定义事件。下一节将介绍如何实现这一点。

面试问题

事件冒泡和捕获有什么区别?你如何防止它们发生?

创建自定义事件

大多数情况下,当您需要知道应用中发生了什么事情时,您可以使用环境中已经内置的事件。但是,可能会出现内置事件不总是符合您的需求的情况。这就是自定义事件的用武之地。

你工作过的大部分都是一样的。您需要侦听一个事件,并且该事件需要被调度。这里的一个不同之处是,您将调度自己的自定义事件。清单 6-9 是一个定制事件的例子。

function onPageLoad(){
   let myButton = document.getElementById("myButton");
        myButton.addEventListener("click", onButtonClick, true);
        myButton.addEventLIstener("WORLD_ENDING_EVENT", onWorldEnd);

 }

function onButtonClick(evt){
    let custEvent = new CustomEvent("WORLD_ENDING_EVENT", {
     detail: message: { "And I feel fine"}
     });

    let myButton = document.getElementById("myButton");
          myButton.dispatchEvent(custEvent);

};

functon onWorldEnd(evt){
   console.log(evt); // returns  CustomEvent {isTrusted: false, detail: {...}, type: "WORLD_ENDING_EVENT", target: button#myButton, currentTarget: button#myButton, ...}
   console.log(evt.type); //returns WORLD_ENDING_EVENT
   console.log(evt.detail); //returns {message: "And I feel fine"}
}

Listing 6-9Creating and Using Custom Events

该按钮监听两种不同类型的事件。一个是正常的点击事件,一个是自定义的WORLD_ENDING_EVENT *。*因为它不在环境或 JavaScript 语言提供的事件列表中,所以它必须是一个自定义事件。

监听此事件的代码与监听任何其他事件的代码相同。添加名称,然后添加事件发生时将执行的事件处理函数。

在这种情况下,该函数被称为onWorldEnd,它打印出发送给该函数的事件对象的不同值。第一个示例打印出整个事件对象。第二个输出事件对象的类型属性。最后一个输出了从定制事件发送的细节对象的值。

面试问题

如何实现自定义事件?

摘要

事件是应用开发的重要组成部分。事件可以让您了解环境中正在发生什么,以及您的用户正在做什么。

本章讲述了什么是事件,环境中的一些事件,以及如何创建自己的自定义事件。您现在知道了如何侦听事件,并且知道了自定义事件和本机事件之间没有区别。

事件处理程序可以使用匿名函数和命名函数。事件对象被传递给函数处理程序来描述函数刚刚发生的一切。使用 event 对象,您可以看到哪个 HTML 元素调度了该事件,并且您能够阻止浏览器执行某些本机操作。您还可以更清楚地了解事件如何通过浏览器传播以及如何控制它们。

下一章将介绍 JavaScript 中继承的概念。您将学习如何创建一个类,以及组合和继承之间的区别。

七、JavaScript 和编程范式

在这一章中,我将解释一些不同的 JavaScript 编程范式。我所说的编程范式是指编写 JavaScript 代码的不同方式或“风格”。

JavaScript 是一种灵活的语言。本章将涵盖两种主要的编程范式:面向对象编程(OOP)和函数式编程。在面试中,你可能会被要求解释这两种范式之间的区别。此外,您可能会发现由其他开发人员编写的代码可能属于这两个类别中的一个或两个。

面试问题

举出两种不同的 JavaScript 编程范式。

用 JavaScript 进行面向对象编程

物体可以被认为是可以为你工作的东西。数组可以保存一组(或一堆)数据。Math对象可以为您执行计算。在任何可以运行 JavaScript 的环境中,都有一长串可以使用的对象。

面向对象编程是一种范式,在这种范式中,您既可以创建自己的对象来为您执行特定的操作,也可以使用环境中可用的对象。

当以这种方式谈论物体时,它们经常被比作现实生活中的事物。教程经常使用人作为例子来解释一个对象是如何工作的。你可以让一群人在一个房间里。他们有相似的特征,如身高、发色、名和姓。

即使有这些相似之处,他们仍然是个体。这给了你一个机会,把你知道的相似的东西分组,并把它们抽象成所谓的

一个类通常被认为是一个蓝图。在这个蓝图里面是处理你所知道的关于一个人的所有事情的代码,没有任何细节。

在下一个例子中,您将使用 ES6 class关键字。还有其他方法可以做到这一点,但为了清楚起见,您将使用最新的语法。一旦您习惯了这一点,您将学习用 JavaScript 创建类的其他方法。

如果你想给你的对象添加细节,你可以创建这个类的一个实例。实例是从类实例化的对象。该实例具有在该类中定义的所有方法和属性。如果一个类是蓝图,那么实例可以被认为是根据蓝图构建的东西。

在深入之前,您应该在代码中看到这一点,所以请查看清单 7-1 。

7-1.js

class Robot {
   constructor(name, type){
        this.name = name;
        this.type = type;
        this.greeting = function(){
        return "I'm " + this.name + " I'm a " + this.type + ".";
        }
    }
}

Listing 7-1Creating a Robot Class

创建一个指向 JavaScript 文件的 HTML 页面,其中包含以下代码。在浏览器中加载 HTML 页面。这将把类加载到浏览器的内存中。现在,您可以使用浏览器控制台创建实例。开发人员工具打开后,您可以执行从 JavaScript 页面加载的代码。

在这个例子中,您正在基于Robot类创建一个对象。要创建一个实例,首先要创建一个变量,然后给它分配一个Robot类的新实例的值。

创建实例时,传递两个参数供类使用。就像向函数传递数据一样,这个类将消耗参数并在内部使用它们。

您可以通过调用属于该类的方法greeting来查看结果。图 7-1 展示了一个基于同一个类创建两个实例的例子。

在图 7-1 中,你可以看到同一个Robot类的两个不同实例。每个实例持有自己的数据。

img/313453_3_En_7_Fig1_HTML.jpg

图 7-1

创建机器人类的两个实例

Robot类,可以参考关于对象的章节。使用关键字this可以让您将属性nametype的值保存在对象内部。

你可能会注意到这个课程以一个大写字母开始。这是用 JavaScript 创建类的惯例。

创建类实例时,使用关键字new *。*这将创建一个包含该类中定义的所有属性和方法的对象。根据类的定义方式,每个实例可以有唯一的值。这使您能够实例化或创建该类的多个实例,每个实例包含不同的属性。

类中的构造函数获取创建实例时传递的属性,并将它们分配给实例。

至此,您已经抽象出了机器人的一些更通用的属性。有时,您可能希望将通用属性与所讨论的机器人的特定属性结合起来。

原子的孩子

在上一节中,您抽象了机器人的一些通用属性,并能够使用该蓝图作为实例化Robot类的单个实例的方法。现在让这些机器人根据它们的类型执行一些特定的任务。这意味着您需要使类从类中继承属性。清单 7-2 探索了扩展父类的概念。

7-2.js

class BendingUnit extends Robot {
   constructor(name, type){
        super(name, type);
        }
    }
}

Listing 7-2Inheriating from a Parent Class Using the ES6 extends Keyword

这段代码使用extends关键字告诉环境,除了当前类中的特性之外,它还想使用来自父类的所有可用特性。

因为您知道您想要使用类的一些属性和方法,所以您使用关键字super将父类需要的所有东西传递给函数属性,在您的例子中是相同的两个属性。

此时,您没有向BendingUnit类添加任何新的东西,但是如果您从这个类创建一个对象并从父类运行相同的方法,您将得到相同的结果。

创建这个对象与上一个示例中的相同。因为这是父对象的子对象,所以您不需要做任何特殊的事情。该类的内部函数将负责这一部分。图 7-2 在这一点上看起来应该非常相似。

img/313453_3_En_7_Fig2_HTML.jpg

图 7-2

创建从父类扩展函数的类

您可以看到,尽管这个类中不存在name属性和greeting方法,但是它们是可用的,因为您是从Robot类扩展而来的。通过使用extends关键字,您可以继承这些属性和方法。

既然您已经能够重用父类中的函数,那么您也可以为子类创建独特的函数。参见清单 7-3 。

7-3.js

class BendingUnit extends Robot {
   constructor(name, type){
        super(name, type);
        }
       primaryFunction(){
       cosole.log(this.name + " bends things");
       }
    }

let bendingUnit = new BendingUnit("Bender", "Bending Robot");
bendingUnit.primaryFunction(); //returns Bender bends things.

Listing 7-3Creating Unique Methods for the Robot Subclass

在这个例子中,您有一个方法,它对于当前类是唯一的,并且不依赖于父类。该方法仍然可以利用父类的属性来执行其独特的功能。

像上一个例子一样创建一个对象,并将其命名为primaryFunction。结果应该如清单 7-3 所示。

这就是继承的工作方式。您可以将属性和函数抽象到一个类中,然后为了获得更多的细节,您可以创建一个子类,该子类可以利用父类的优势,同时添加自己独特的功能。

使用extends关键字给你从另一个类继承的能力。JavaScript 只允许你继承一个父类。

因为您在您的类中使用 ES6 语法,所以在幕后有很多事情要做。下一节将为您提供一个深入探索的机会。

JavaScript 类和原型继承

在上一节中,您使用关键字extends开始了对继承主题的探索。其思想是一个对象可以获得另一个类的属性和方法。这另一个类被认为是它的超级父类。

其他语言有所谓的基于类的继承。这意味着一个对象可以从其他类继承它的属性和方法。

然而,JavaScript 是基于原型的,这意味着如果属性或方法在当前对象中不存在,它将查看对象的原型属性,并移动到其父对象以查看该属性是否存在。这通常被称为沿着原型链向上移动。

在前面的示例中,您使用了 ES6 语法来创建对象。你用的一个关键词是constructor。函数构造器实际上只是一个创建函数对象的函数。

为了进行适当的比较,让我们使用函数构造函数重新创建robot类。参见清单 7-4 。

7-4.js

const Robot = function(name, type) {
      this.name = name;
      this.type = type;
    }

Robot.prototype.greeting = function(){
        return "I'm " + this.name + " I'm a " + this.type + ".";
}

let bender = new Robot ("Bender", "Bending Robot");
bender.greeting() // "My name is Bender I'm a Bending Robot."

Listing 7-4Creating the Robot Object Using Function Contructors

在这里,您创建一个常数,并为其分配一个函数。这个函数接受两个属性,并将值赋给它的内部对象。

下一行是您开始了解 JavaScript 中的继承是如何工作的地方。您访问对象的prototype属性并创建一个名为greeting *的函数。*这就像使用类语法创建函数一样。

属性和方法被添加到对象的原型中。这意味着如果有任何类可以从这个类继承,JavaScript 会将原型链从子对象移动到其父对象以访问属性。

清单 7-5 展示了如何创建一个从另一个对象继承方法和属性的对象。

7-5.js

const Robot = function(name, type) {
      this.name = name;
      this.type = type;
    }

Robot.prototype.greeting = function(){
        return "I'm " + this.name + " I'm a " + this.type + ".";
}

const BendingUnit = function(){
     Robot.apply(this, arguments);
}

BendingUnit.prototype = Object.create(Robot.prototype);
BendingUnit.prototype.constructor = BendingUnit;

let bender = new BendingUnit("Bender", "Bending Unit");
bender.greeting() // "My name is Bender I'm a Bending Unit."

Listing 7-5Creating Inheritance Without Using the ES6 Syntax

这里的一个关键区别是函数BendingUnit调用原始的Robot类并使用apply方法。

apply方法告诉浏览器BendingUnit类可以作为访问Robot类属性的起点。

下一行告诉环境更新BendingUnit类的原型对象,并将Robot原型的值赋给它。

既然原型已经更新,构造函数认为它是Robot类的副本,而不是自己的BendingUnit类。

要解决这个问题,将 contractor 函数分配给BendingUnit函数,这样它将知道即使它从Robot类继承了函数,它本身也不是机器人

本例中最后两行的功能与使用 ES6 语法时相同。您可以看到,使用 ES5 语法使您更接近 JavaScript 的实际工作方式,但是可能会使解释继承的概念更加困难。

总的来说,继承对于重用代码而不必重写代码是有用的。还有一种模式叫*构图。使用复合,*你在类内部创建对象,直接使用它们,不需要继承。这不是 JavaScript 语言的特性,而是重用代码以赋予对象更多能力的一种方式。

对继承的批评之一是,你不仅继承了你想要的特性,还继承了你不需要的特性。这是因为它们是父类或父类的一部分。

在下一节中,我将讨论另一种范式:用 JavaScript 进行函数式编程。

JavaScript 函数式编程

如前所述,JavaScript 是一种非常灵活的语言。经常使用的两种范式是面向对象编程和函数式编程。

以前,您使用包含关键字class的 ES6 语法和依赖于您对原型继承的理解的 ES5 语法在 JavaScript 中创建了对象。

使用 JavaScript 时,理解原型继承非常重要。然而,函数式编程不依赖于原型。

函数式编程有几个概念需要解释,以便充分理解它与面向对象编程的区别。

我将介绍的一些概念是

  • 纯函数

  • 副作用/共享状态

  • 不变

  • 命令式代码之上的声明式代码

纯函数

它的纯粹之处在于,无论你调用函数多少次,结果总是一样的。如果你有一个函数,取一个数,乘以 2(x * 2),然后返回结果,无论你传递给函数什么数,结果都是一致的。清单 7-6 显示了一个纯函数的例子。

7-6.js

const timesTwo = (x) =>  x * 2;

timesTwo(2) //returns 4
timesTwo(3) //returns 6

Listing 7-6Creating a Pure Function

这给了你的函数一种叫做引用透明性的东西。这意味着你可以用它返回的值来替换函数本身,而应用的行为不会改变。如果不是这样,它被称为不透明*。*

使一个函数不纯的原因是,尽管传递了相同的值,但函数返回不同的结果。例如,开发人员有时希望每次都调用具有唯一属性的服务,以防止浏览器缓存结果。为此,他们将当前时间添加到 URL 中。因为在 24 小时内你只能得到一次准确的时间,所以这不是一个纯粹的函数。任何使函数产生不一致的结果或者除了返回值之外还必须做任何事情的事情都会使函数不纯。

副作用/共享状态

纯函数的另一个特点是它们不会产生副作用,也就是说它们不会改变或者变异在函数之外可以访问的对象。

因为 JavaScript 将对象属性作为引用传递,所以当对象或数组的属性发生变化时,它会改变该对象的状态。同样的对象可以在函数之外访问。纯函数不得改变外部状态。

小费

赋给非原始值(一个对象)的 JavaScript 变量被赋予该值的引用。引用是指对象在计算机内存中的位置。变量本身不包含值。

不变

不变性是函数式编程的一个重要概念。对象不变或突变的想法使得调试之类的事情变得更简单,因为每个状态都是恒定的。

提醒一下,使用关键字const将保持变量不变,但不会阻止对象被更新。

经常发生的一件事是,你有一种情况,你通常会改变数据,例如一个数组。如果您正在以函数方式编程,您不希望改变数据。你如何解决这个问题?

在这种情况下,您可以制作数据的副本,然后将副本发送回来。这可以防止原始对象发生突变,并提供您需要的结果。

要向数组中添加项目,大多数人会使用push方法。这会将一个项目添加到数组的末尾。但是,这也会使数组发生变异。要解决这个问题,您可以使用内置的Object数据类型并使用assign方法。为了更清楚,请看清单 7-7 。

7-7.js

const twoArray = ["One", "Two"];
const threeArray = Object.assign([...twoArray], {2:"Three"});
console,log(threeArray); //returns (3) ["One", "Two", "Three"]

Listing 7-7Returning a Copy of an Array So Data Is Not Mutated

在本例中,第一行创建了一个数组。第二行使用了assign方法。此方法接收两个参数。第一个是原数组。您不用遍历整个数组来获取所有元素,而是使用 spread 操作符来传递 iterable 对象(在本例中为数组)的所有属性。第二个参数是要添加到数组中的对象。这将创建一个新的数组,而不会改变原始对象。

命令式代码之上的声明式代码

声明性代码和命令性代码的主要区别在于,命令性代码描述了如何实现预期结果的步骤。虽然声明性代码关注的是应该对数据做什么,但是它应该如何工作的细节却被抽象掉了。清单 7-8 展示了一个基于现有数组创建新数组的例子。

7-8.js

const threeArray = ["One", "Two", "Three"];
//Imperative code describing how a program operates
for(let i = 0; threeArray.length > i; i++){
      console.log(threeArray[i]); //returns One, Two, Three
}

//Declarative code showing how a program should work
 threeArray.map((value, index, arr) => {
      console.log(value); //returns One, Two, Three
});

Listing 7-8Imperative Code vs. Declarative Code

第一个实例展示了一种基于数组长度遍历数据的方法。在for循环中有检查来判断你能走多远。

第二个实例利用了内置的map方法。此方法将函数作为参数。该函数接收数组中的每一项,而不需要知道数组的长度。

这里的主要区别是,您不需要描述如何浏览数据。重点只在于你对数据做了什么。

如果您在函数式编程环境中工作,这些是需要理解的重要概念。JavaScript 中的函数可以作为函数的属性传递。它们也可以是从其他函数返回的值。

链接函数的能力是你会经常看到的。使用fetch方法从远程数据源检索数据需要链接方法,然后处理从服务器返回的结果。这里有一个例子:

fetch('https://swapi.co/api/people/1').then(results => results.json()).then(json => console.log(json));

//results
//{name: "Luke Skywalker", height: "172", mass: "77", hair_color: "blond", skin_color: "fair", ...}

这里使用fetch方法从星球大战 API ( https://swapi.co )中检索数据。这个方法返回所谓的承诺。它将一直等到请求被解决或失败。

当请求被解析时,then方法被链接到它。您可以在这里处理结果。第一个then方法获取结果并将它们转换成一个 JSON 对象。使用箭头函数时,如果没有花括号,只有一行代码,结果会自动返回。

这被发送到第二个then方法,在那里第一个方法的结果变成一个名为json的参数,并被打印在浏览器控制台中。

摘要

在这一章中,你探索了两种不同的编程风格或范式。第一种是面向对象的编程,在这种编程中,你可以创建代表你想要建模的事物的对象。

记住 JavaScript 使用原型来创建继承是很重要的。当前对象中不存在的属性和方法将沿着原型链向上移动到对象的父类。

第二种是函数式编程,纯函数变得极其重要。如果相同的参数传递给函数,这些函数总是返回相同的结果。它们也不会产生副作用,因为它们不仅仅是返回一个一致的值。

您还学习了不变性的概念。对象不直接更新;这会使物体变异。相反,会制作一个副本,并将更改添加到该副本中,然后发送回应用。

最后一节介绍了声明性代码和命令性代码之间的区别。声明性代码使用代码来确定如何在数据中导航,命令性代码允许代码自己确定它应该如何呈现自己,并让您专注于您想要用它做什么。该示例使用了数组的map方法;它允许您遍历数组中的每一项,而无需提前知道长度。

下一章将介绍如何使用浏览器中的工具来帮助调试应用。随着您的应用变得越来越复杂,了解这些工具的工作原理将非常有帮助。