Skip to content

给猫咪的 JavaScript 编程指南

Posted on:2024年8月10日 at 07:26

本文章翻译自:http://jsforcats.com/

给新程序员的介绍 cat

简单到你的“人类”伙伴也能做得到!

JavaScript 是一种编程语言,换句话说,就是一种指示计算机做事的方式。就像你用嘶嘶声和喵喵声控制人类一样,你也可以用编程语言写的语句来控制计算机。所有的网络浏览器都能理解JavaScript,你可以利用它来让网页做出疯狂的事情!

JavaScript 起初是用来让网页更具互动性的。如今,JavaScript 不仅仅运行在网络浏览器中——它还能运行在网络服务器、手机甚至机器人上!本页面将教你一些 JavaScript 的基础知识,这样你就能迅速入门*。

* 实际时间:不是“无”。可能需要一到两个小时。此外,由于你是一只猫,你可能不会跑步,而是更喜欢在阳光下懒洋洋地躺着

JavaScript for Cats 是 CC0 授权 的。

目录

别做胆小猫

cat

即使在编程时,你也总会“脚踏实地” — 不会出错!与在笔记本电脑上用爪子打翻水杯不同,这些教程中的内容_绝不会_以任何方式损坏你的电脑,即使你输入错误的命令或点击了错误的按钮。就像猫一样,计算机程序员总是犯错误:拼写错误、忘记引号或括号,以及忘记基本函数(和毛线、激光)是如何工作的。程序员更关心的是最终让它运行起来,而不是第一次就让它工作。最好的学习方法就是通过犯错误!

所以,别做胆小猫!最糟糕的情况不过是你可能需要在浏览器中刷新此页面,如果你卡住了。别担心,这种情况很少发生。

# 基础知识

这页上现在正运行着 JavaScript。让我们来玩一下。为了简便起见,我假设你正在使用 Google Chrome 阅读此页面(如果不是的话,跟着 Chrome 走可能对我们双方都更容易些)。

首先,在屏幕上的任何地方右键单击并选择检查元素,然后点击控制台选项卡。你应该能看到一个看起来像这样的东西:

console

这是一个控制台,也叫做“命令行”或“终端”。基本上,它是一种可以逐条输入内容到计算机中并立即获取计算机答案的方式。它们作为学习工具非常有用(我几乎每天编写代码时都使用控制台)。

控制台能做一些很酷的事情。这里我已经开始输入内容,而控制台正在帮助我,给出了所有我可能继续输入的选项列表!你还可以在控制台中输入1 + 1,然后按下Enter键,看看会发生什么。

使用控制台是学习 JavaScript 的非常重要的一部分。如果你不确定某些内容是否有效,或者不知道某个命令是什么,去控制台查一下!这里有个例子:

# 字符串

作为一只猫,我想把互联网上所有 这个词都替换成 那些该死的狗。首先,进入你的控制台,输入几句话,这些句子中至少要包含一次 这个词。在 JavaScript 中,一串字母、数字、单词或其他任何内容都被称为字符串(即一字符)。字符串必须以引号开始和结束。单引号 ' 或双引号 " 都可以,只要确保开头和结尾使用相同的引号。

console

看到这个讨厌的错误信息了吗?别担心——你没有触犯任何法律。SyntaxError ILLEGAL 只是当机器人告诉你程序有问题时的表现。前两句话的引号是匹配的,但当我混用了单引号和双引号时,它就出错了。

好了,要修正这些句子中的一个(通过将 替换为我们的增强版),我们首先要保存原句,以便稍后在进行替换时调用它。注意,当我们将字符串输入控制台时,字符串会以红色显示出来吗?这是因为我们没有告诉它要将句子保存到哪里,所以它只是把句子直接返回(或者如果我们搞砸了,它会返回一个错误)。

# 值和变量

是 JavaScript 中最简单的组成部分。1 是一个值,true 是一个值,"hello" 是一个值,function() {} 也是一个值,这个列表可以继续下去!在 JavaScript 中有几种不同类型的值,但我们不需要马上全部了解——你在编程的过程中会自然学到它们!

要存储值,我们使用被称为变量的东西。‘变量’ 这个词的意思是 ‘可以改变’,因为变量可以存储多种不同类型的值,并且可以多次改变其值。它们就像邮箱一样。我们把某个东西放进一个变量,比如我们的句子,然后给变量一个地址,以便我们以后可以用这个地址查找句子。在现实生活中,邮箱必须有邮政信箱号码,但在 JavaScript 中,你通常只用小写字母或数字,不带空格。

console

var 是变量的缩写,= 表示将右侧的内容存储在左侧的东西中。如你所见,现在我们将句子存储在一个变量中,控制台不再直接返回句子,而是返回 undefined,这意味着没有什么可以返回的

如果你在控制台中简单地输入变量名,它将打印出存储在该变量中的值。关于变量需要注意的是,默认情况下,它们会在你切换到另一个页面时消失。例如,如果我在 Chrome 中按下刷新按钮,我的 dogSentence 变量就会被清除,就像它从未存在过一样。但现在不用太担心这个——你可以在控制台中按键盘上的上下箭头来浏览你最近输入的所有内容。

# 函数

现在我们已经将句子存储在变量中了,让我们来更改其中存储的一个词吧!我们可以通过执行一个函数来实现这一点。函数是一种值,它为我们执行特定的功能(也就是目的或动作)。我猜把它们称为“动作”听起来有点怪,所以他们选择了“函数”这个词。

JavaScript 有一个叫做 replace 的函数,它正好能实现我们想要的功能!函数在其括号内可以接受任意数量的值(零个、一个或多个),并返回要么什么也没有(undefined),要么返回更改后的字符串。replace 函数可以用于任何字符串,并接受两个值:要替换的字符和要替换成的字符。描述这些东西有点令人困惑,所以这里有一个直观的例子:

console

注意到即使我们对 dogSentence 运行了 replace 函数后,它的值仍然是一样的吗?这是因为 replace 函数(以及大多数 JavaScript 函数)会接收我们传递的值并返回一个新值,而不会修改我们传递的值。由于我们没有存储结果(在 replace 函数的左侧没有 =),所以它只是将返回值打印在控制台中。

# “标准库”

你可能会想知道 JavaScript 中还有哪些其他函数。答案是:非常多。有很多内置的、标准库,你可以在 MDN 上了解这些内容(这是一个由 Mozilla 运行的网站,上面有很多关于网络技术的有趣信息)。例如,这是 MDN 上关于 JavaScript Math 对象的页面

# 第三方 JavaScript

还有很多非内置的 JavaScript 代码可用。来自第三方的 JavaScript 通常被称为“库”或“插件”。我最喜欢的其中一个叫做 Underscore.js。让我们去下载它并加载到我们的页面中吧!首先访问 Underscore 网站,http://underscorejs.org/,点击下载链接(我通常使用开发版本,因为它们更容易阅读,但两者都提供相同的基本功能),然后将所有代码复制到剪贴板上(你可以使用编辑菜单中的“全选”来选择所有内容)。然后将其粘贴到你的控制台中并按回车键。现在你的浏览器中有了一个新变量:_。Underscore 提供了大量有用的函数供你使用。我们稍后会学习如何使用它们。

console

# 编写新函数

你不仅限于使用别人的函数——你也可以自己编写它们。这非常简单!让我们编写一个叫做 makeMoreExciting 的函数,给字符串的末尾加上一堆感叹号。

function makeMoreExciting(string) {
  return string + '!!!!'
}

在我脑海中,我会这样大声读出来:“这里有一个叫做‘make more exciting’的函数,它接受一个字符串,并返回一个新副本,该副本在字符串末尾添加了一堆感叹号”。如果我们不使用函数,手动在控制台中编写这段代码的话,这里是如何操作的:

console

表达式 string + '!!!!' 返回一个新字符串,而我们名为 string 的变量保持不变(因为我们从未使用 = 将其更新为其他内容)。

让我们使用函数而不是手动操作。首先,将函数粘贴到控制台中,然后通过传入一个字符串来调用函数:

console

你也可以通过传入指向一个字符串的变量来调用相同的函数(在上面的例子中,我们只是将字符串直接作为一个值输入,而不是先将其保存到变量中):

console

这行代码 makeMoreExciting(sentence) 相当于说 sentence + '!!!!'。如果我们想要就地修改(也就是更新)sentence 的值呢?只需将函数的返回值重新保存到我们的 sentence 变量中:

var sentence = "time for a nap"
sentence = makeMoreExciting(sentence)

现在 sentence 将包含感叹号!请注意,你只需要在初始化变量时使用 var — 也就是你第一次使用它的时候。之后你不应该再使用 var,除非你想重新初始化(重置/清空)变量。

如果我们在函数中去掉 return 语句,会发生什么?

console

为什么 sentence 为空?因为函数默认返回 undefined!你可以选择通过 return 返回一个值。函数应该接收一个值,并且如果它们改变了这个值或创建了一个需要后续使用的新值,就会 return 一个值(有趣的事实:这种风格的术语是函数式编程)。这里有另一个函数,它不返回任何值,而是使用另一种方法来显示输出:

function yellIt(string) {
  string = string.toUpperCase()
  string = makeMoreExciting(string)
  console.log(string)
}

这个函数 yellIt 使用了我们之前的函数 makeMoreExciting 以及内置的字符串方法 toUpperCase。方法只是函数的一种名称,当它属于某个对象时 — 在这个例子中,toUpperCase 是属于 String 的函数,所以我们可以将其称为方法函数。另一方面,makeMoreExciting 不属于任何对象,所以将它称为方法在技术上是不正确的(我知道这很容易混淆)。

函数的最后一行是另一个内置函数,它只是接收你给的任何值,并将它们打印到控制台中。

console

那么上面的 yellIt 函数有什么问题吗?这取决于情况!以下是两种主要的函数类型:

console.log 就是第二种函数类型的一个例子:它将内容打印到控制台 — 这是你可以用眼睛看到的操作,但不能用 JavaScript 值来表示。我的经验法则是尽量将这两种函数分开使用,因此我会这样重写 yellIt 函数:

function yellIt(string) {
  string = string.toUpperCase()
  return makeMoreExciting(string)
}

console.log(yellIt("i fear no human"))

这样一来,yellIt 就变得更加通用,意味着它只做一两件简单的小事,而不会涉及到自己如何打印到控制台 — 这一部分可以在函数定义之外的任何地方编程实现。

# 循环

现在我们已经掌握了一些基本技能(作者注:猫真的会系腰带吗?),我们可以开始偷懒了。什么?!没错:编程就是为了偷懒。Perl 编程语言的发明者 Larry Wall 称懒惰是优秀程序员的最重要的美德。如果没有计算机,你就得手动完成各种繁琐的任务,但如果你学会编程,你就可以整天躺在阳光下,让某个地方的计算机为你运行程序。这是一种充满轻松的美好生活方式!

循环是利用计算机强大功能的最重要方式之一。还记得之前提到的 Underscore.js 吗?确保你已经在页面中加载了它(记住:如果需要,你可以按几次键盘上的向上箭头,然后按 Enter 重新加载它),然后尝试将以下代码复制粘贴到控制台中:

function logANumber(someNumber) {
  console.log(someNumber)
}
_.times(10, logANumber)

这段代码使用了 Underscore 的 times 方法,该方法接收一个数字和一个函数,然后从 0 开始,逐步加 1,一共执行 10 次,每次用当前的数字调用该函数。

console

如果我们手动写出上面代码中 times 所做的事情,它看起来会像这样:

logANumber(0)
logANumber(1)
logANumber(2)
logANumber(3)
logANumber(4)
logANumber(5)
logANumber(6)
logANumber(7)
logANumber(8)
logANumber(9)

但猫咪拒绝做这种不必要的手动工作,所以我们必须时刻问自己,“我是不是在以最懒惰的方式做这件事?”

那么,为什么这叫循环?可以这样想:如果我们要用 JavaScript 数组列出 10 个数字(从 0 到 9),它看起来会像这样:

var zeroThroughTen = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

times 的实际作用是访问每个数字并重复执行一个任务:在上面的例子中,任务是用当前的数字调用 logANumber 函数。以这种方式重复任务被称为遍历数组。

# 数组

我已经多次提到过数组,但现在让我们花点时间来学习它们。想象一下你需要记录所有的伙伴。嗯,数组就能很好地满足这个需求。可以把数组看作是一个排序好的列表,你可以在其中存放大量的东西。

这是创建数组的方法:

var myCatFriends = ["bill", "tabby", "ceiling"]

太棒了!现在你有了一个猫咪伙伴的列表。

存储在数组中的元素(数组中的单个项目就叫做元素)从 0 开始编号,并从那里依次递增。所以 myCatFriends[0] 返回 billmyCatFriends[1] 返回 tabby… 依此类推。

要从你的新数组中取出伙伴,你可以直接访问一个元素,如下所示:

console.log(myCatFriends[0])

console

如果你在昨晚最时髦的猫咪俱乐部交了一个新猫咪朋友,并且你想把它加到列表中,这非常简单:myCatFriends.push("super hip cat")

要检查新猫是否被加入到数组中,你可以使用 .length

console

注意到 push 返回了数组的长度了吗?很方便吧!还要注意数组总是会保留顺序,这意味着它们会记住你添加或定义事物的顺序。并不是 JavaScript 中的所有东西都会保留顺序,所以记住数组的这个特殊属性吧!

# 对象

数组适合用于列表,但对于其他任务来说,使用它们可能会很麻烦。想想我们那数组形式的猫咪朋友们。如果你想存储的不仅仅是名字怎么办?

var myCatFriends = ["bill", "tabby", "ceiling"]
var lastNames = ["the cat", "cat", "cat"]
var addresses = ["The Alley", "Grandmas House", "Attic"]

有时候,把所有地址或名字都放在一个变量中是很方便的。但有时你有特定的一只猫,比如 Bill,你只是想查找那只猫的地址。使用数组的话,需要做很多工作,因为你不能简单地说“嘿,数组,给我 Bill 的地址”,因为“Bill”在一个数组里,而他的地址却在完全不同的另一个数组里。

console

这可能会很脆弱,因为如果我们的数组发生变化,比如在开始位置添加了一只新猫,我们还需要更新 billsPosition 变量,以指向数组中 Bill 信息的新位置!这里有一种更容易维护的方法,可以通过使用对象来存储这样的信息:

var firstCat = { name: "bill", lastName: "the cat", address: "The Alley" }
var secondCat = { name: "tabby", lastName: "cat", address: "Grandmas House" }
var thirdCat = { name: "ceiling", lastName: "cat", address: "Attic" }

为什么我们要这么做?因为现在我们有了每只猫的一个变量,我们可以用它来更方便和更直观地获取那只猫的相关信息。

console

你可以把对象想象成钥匙串上的钥匙。每把钥匙对应一个特定的门,如果你的钥匙上有清晰的标签,你就能非常快速地打开门。事实上,: 左侧的东西被称为(也称为属性),而右侧的东西则是

// 一个只有一个键 'name' 和一个值 'bill' 的对象
{ name: 'bill' }

那么,既然可以把数据放在对象里,为什么还要使用数组呢?因为对象不会记住你设置键的顺序。你可能会这样输入一个对象:

{ date: "10/20/2012", diary: "slept a bit today", name: "Charles" }

但计算机可能会这样返回给你:

{ diary: "slept a bit today", name: "Charles", date: "10/20/2012" }

或者像这样!

{ name: "Charles", diary: "slept a bit today", date: "10/20/2012" }

所以你永远不能信任对象中键的顺序。如果你想玩得更高级一点,你可以创建一个包含对象的数组,或者一个包含数组的对象!

var moodLog = [
  {
    date: "10/20/2012",
    mood: "catnipped"
  }, 
  {
    date: "10/21/2012",
    mood: "nonplussed"
  },
  {
    date: "10/22/2012",
    mood: "purring"
  }
]

// 从最不喜欢到最喜欢排序
var favorites = {
  treats: ["bird sighting", "belly rub", "catnip"],
  napSpots: ["couch", "planter box", "human face"]
}

当你这样组合不同的东西时,你正在构建数据结构,就像玩乐高积木一样!

# 回调函数

回调函数并不像 ObjectArray 那样是 JavaScript 的一种功能,而是一种使用函数的特定方式。要理解回调函数为什么有用,首先你需要了解异步编程(通常缩写为 async)。异步代码的定义是以非同步方式编写的代码。同步代码易于理解和编写。以下是一个说明同步代码的例子:

var photo = download('http://foo-chan.com/images/sp.jpg')
uploadPhotoTweet(photo, '@maxogden')

这段同步伪代码 下载了一张可爱的猫咪照片,然后将照片上传到 Twitter 并将其推送给 @maxogden。非常直观!

(作者注:我 @maxogden 很乐意接受随机的猫咪照片推文)

这段代码是同步的,因为为了将照片上传到推文,照片下载必须先完成。这意味着第二行代码在第一行任务完全完成之前无法运行。如果我们要实际实现这段伪代码,我们需要确保 download 阻止执行,直到下载完成。这意味着它会阻止任何其他 JavaScript 被执行,直到下载完成,下载完成后它将解除阻止 JavaScript 的执行,然后第二行代码才会执行。

同步代码对于快速完成的事情来说是可以接受的,但对于需要保存、加载、下载或上传的事情来说就很糟糕了。如果你下载照片的服务器速度很慢,或者你使用的互联网连接很慢,或者你运行代码的计算机打开了太多 YouTube 猫咪视频标签导致运行缓慢呢?这意味着可能需要等待几分钟才能执行第二行代码。同时,由于在下载发生时页面上所有的 JavaScript 都被阻止运行,网页将完全冻结并且变得无响应,直到下载完成。

应该尽一切可能避免阻塞执行,尤其是当阻塞执行会导致程序冻结或变得无响应时。假设上面的照片下载需要一秒钟。为了说明一秒钟对现代计算机来说有多长时间,这里有一个程序,用于测试 JavaScript 在一秒钟内可以处理多少任务。

function measureLoopSpeed() {
  var count = 0
  function addOne() { count = count + 1 }

  // Date.now() 返回一个表示自 1970 年 1 月 1 日以来经过的毫秒数的大数字
  var now = Date.now()

  // 循环,直到 Date.now() 自我们开始循环以来已超过 1000 毫秒(1 秒)。在每次循环中调用 addOne
  while (Date.now() - now < 1000) addOne()
  
  // 最终已经 >= 1000 毫秒,所以让我们打印出总计数
  console.log(count)
}

measureLoopSpeed()

将上述代码复制粘贴到你的 JavaScript 控制台中,一秒钟后它应该会打印出一个数字。在我的计算机上,我得到了 8527360,大约是850 万。在一秒钟内,JavaScript 可以调用 addOne 函数 850 万次!所以如果你有同步的照片下载代码,并且照片下载需要一秒钟,这意味着在 JavaScript 执行被阻止的情况下,可能会阻止 850 万个操作的发生。

有些语言有一个名为 sleep 的函数,可以阻止执行某些秒数。例如,这里有一些在 Mac OS 的 Terminal.app 中运行的 bash 代码,使用了 sleep。当你运行命令 sleep 3 && echo 'done sleeping now' 时,它会阻塞 3 秒,然后打印出 done sleeping now

console

JavaScript 没有 sleep 函数。作为一只猫,你可能会问自己,“为什么我要学习一种不涉及睡觉的编程语言?”。但请耐心点。JavaScript 的设计鼓励使用函数,而不是依赖 sleep 来等待事情发生。如果你必须等任务 A 完成后才能执行任务 B,你应该把任务 B 的所有代码放到一个函数中,并且只在任务 A 完成时调用该函数。

例如,这是阻塞风格的代码:

a()
b()

而这是非阻塞风格:

a(b)

在非阻塞版本中,ba 的回调函数。在阻塞版本中,ab 都被调用(它们后面都有 (),这意味着立即执行这些函数)。在非阻塞版本中,你会注意到只有 a 被调用,而 b 只是作为参数传递给 a

在阻塞版本中,ab 之间没有明确的关系。在非阻塞版本中,a 的任务是完成它的工作,然后在完成时调用 b。以这种方式使用函数被称为回调,因为你的回调函数(在本例中是 b)在 a 完成时会被调用。

以下是 a 的伪代码实现:

function a(done) {
  download('https://pbs.twimg.com/media/B4DDWBrCEAA8u4O.jpg:large', function doneDownloading(error, png) {
    // 如果有错误,处理错误
    if (err) console.log('糟糕!', error)
    
    // 完成后调用 done
    done()
  })
}

回想一下我们的非阻塞示例,a(b),我们调用 a 并将 b 作为第一个参数传递。在上面 a 的函数定义中,done 参数就是我们传递的 b 函数。这种行为起初很难理解。当你调用一个函数时,你传递的参数在函数内部不会使用相同的变量名。在这种情况下,我们称之为 b 的函数在函数内部称为 done。但 bdone 只是指向相同底层函数的变量名。通常回调函数会被命名为类似 donecallback 以使其明确它们是在当前函数完成时应调用的函数。

所以,只要 a 完成它的工作并在完成时调用 b,在非阻塞和阻塞版本中,ab 都会被调用。区别在于,在非阻塞版本中,我们不需要暂停 JavaScript 的执行。通常,非阻塞风格意味着你编写的每个函数都应该尽快返回,而不阻塞。

再进一步说明:如果 a 需要一秒钟完成,并且你使用阻塞版本,这意味着你只能做一件事。如果你使用非阻塞版本(即使用回调函数),你可以在同一秒钟内做数百万件其他事情,这意味着你可以完成数百万倍的工作量,然后睡一整天。

记住:编程就是关于懒惰的,你应该是那个睡觉的人,而不是你的计算机。

希望你现在能明白,回调函数只是在某些异步任务后调用其他函数的函数。常见的异步任务包括读取照片、下载歌曲、上传图片、与数据库对话、等待用户按键或点击某个元素等。任何需要时间的事情。只要你花时间学习如何使用回调函数,并避免 JavaScript 被阻塞,JavaScript 在处理这些异步任务时表现非常出色。

结束了!

这只是你与 JavaScript 关系的开始!你不能一次性学完所有内容,但你应该找到适合自己的方法,并努力学习这里的所有概念。

我建议你明天再回来,从头到尾再过一遍!你可能需要多次通读才能掌握所有内容(编程很难)。尽量避免在有闪亮物体的房间里阅读这页内容……它们会非常分散注意力。

有其他想要了解的话题吗?你可以在 Github 上提交一个 issue。

# 推荐阅读

JavaScript For Cats 省略了许多不重要的细节,这些细节对于入门来说并不重要(众所周知,猫的注意力不太集中),但如果你觉得需要深入了解,可以看看以下资源:

# 喜欢我们的小伙伴

satisfied customer satisfied customer satisfied customer satisfied customer satisfied customer

JSForCats.com 是 @maxogden 一项充满热情且不断完善的工作。如果你想贡献力量,让这个教程更好,GitHub 仓库就在这里 console