JavaScript, jQuery, TypeScript: 第三章,第一部分
打下基础:JavaScript 数据类型。因为大脑的工作方式,一次只关注一件事情更容易学习。
注意:这篇文章摘录自我的博客(http://raddev.us/DevTech/post/2018/09/18/javascript-jquery-typescript-chapter-3-part-1^)。
打下基础:JavaScript 数据类型
因为大脑的工作方式,一次只关注一件事情更容易学习。然而,当学习像 JavaScript 这样庞大的主题时,JavaScript 自从 1995 年 9 月由 Brendan Eich 为 Netscape Navigator 2.0 创建以来一直在使用,要确定首先关注什么可能会是一个挑战。此外,如果你没有打好基础,以后学习高级概念会更加困难。
因此,我想带你回到一个非常基本但又贯穿 JavaScript 所有内容的概念:类型系统。
乍一看,你可能会觉得关注类型系统太简单无聊了,但我保证我会简短生动地讲解,完成后,你对 JavaScript 的理解将比以往任何时候都更好。
JavaScript 如何处理类型
在 JavaScript 中——就像许多高级语言一样——所有类型都可以分为两类:
- 原始类型——内存地址指示存储数据(或值)的位置*
- 引用类型——内存地址指示存储对象的位置*
* 这个说法可能令人困惑。我将在几分钟内解释。
首先,让我们看看原始类型。
JavaScript 中只有六种原始类型,它们是:
- 数字
- 布尔值
- 字符串
- 未定义
- Null (空值)
- Symbol(JavaScript 2015 新增)
最后三种可能看起来有点奇怪,我们会讲到它们,但首先让我们看看前三种,并了解为什么理解所有这些都是原始类型很重要。然后,我们将深入研究引用类型以及它们的不同之处。对这两种类型差异的了解将有助于你理解 JavaScript 解释器——运行所有 JavaScript 代码的引擎——如何工作,以及创建对象(引用类型)与创建这些原始类型有何不同。
在 JavaScript 中创建任何一种原始类型都非常容易。
只需键入关键字 `var`,后跟你想用来引用变量的名称,然后将其设置为某个值。
var counter = 0; // becomes a Number type
var isConfigured = false; // becomes a Boolean type
var firstName = “David”; // becomes a String
typeof 操作符从 JavaScript 解释器指示类型
我们可以使用 `typeof` 操作符来确定 JavaScript 解释器认为变量是什么类型。当然,JavaScript 解释器认为你的变量类型是什么,它就是什么。
以下是一些代码,展示了 `typeof` 操作符的工作方式。
var counter = 0; // becomes a Number type
var isConfigured = false; // becomes a Boolean type
var firstName = “David”; // becomes a String
console.log("counter is a " + typeof counter);
console.log("isConfigured is a " + typeof isConfigured);
console.log("firstName is a " + typeof firstName);
运行此代码并查看控制台输出后,你应该会看到以下内容:
counter is a number
isConfigured is a boolean
firstName is a string
在 JSFiddle 上运行查看
JSFiddle 是一个允许你创建 HTML、CSS 和 JavaScript 并在浏览器中运行它的网站。
我已经将前面的代码添加到那里的一个脚本中,如果你愿意,可以看看它的运行效果。要查看它,只需将浏览器指向 https://jsfiddle.net/raddevus/x7zw0cka/。
注意:脚本包含一些代码(document.getElementById(“1”)),该代码获取具有特定 ID 的 HTML DIV 并将其文本设置为输出。我现在不会解释这段代码,但我们稍后会全部学到。
深入了解你的 Number 变量
现在,请允许我回到我关于原始类型的那个有点奇怪的陈述,我说:
“……内存地址指示存储数据(或值)的位置。”
这是什么意思?
为了更好地理解它,你必须记住所有数据都存储在内存中的某个可寻址位置。数据必须这样存储,以便其他程序元素可以访问它。否则,如果无法知道数据存储在哪里,计算机程序将无法操作或显示数据,数据将变得无法访问且无用。
你编写 JavaScript,你控制内存芯片
另外,请记住,在底层,你想要存储的值实际上存储在内存的某个地方,它有一个唯一的地址。没错,你实际上正在通过输入来控制计算机中某个内存芯片上的开关。
var x = 55;
同样,要在 JavaScript 中创建 Number,你只需要编写如下代码:
var x = 31;
var z = 56;
JavaScript 解释器会做什么
JavaScript 解释器注意到你将值设置为数字,并决定其类型为 Number。由于它是 Number,解释器知道它需要一定数量的字节来存储直到最大值。
解释器必须知道它的类型,因为在底层计算中,实际发生的事情是这样的。当你简单地创建一个 Number 变量(`var counter = 5;`)时,JavaScript 解释器必须做一些事情,以便计算机的处理器知道你想做什么。
以下是解释器执行的一些操作:
- 确定数据类型,以便知道存储数据需要多少字节内存
- 找到一个具有足够连续槽位来存储 Number 类型的内存位置。
- 在内部查找表中创建一个条目,将你的变量名与你的变量名一起存储——这样,当你通过你定义的名称引用变量时,它就知道变量存储在哪里。
第一步:确定类型
解释器需要知道数据类型,因为每种类型需要不同量的内存。
第二步:查找内存
要完成第二步,JavaScript 解释器必须知道 Number 类型在内存中占用多少字节。
JavaScript 解释器的架构师
对于原始类型(Number、Boolean、String 等),创建 JavaScript 解释器的架构师已经决定,它们每种都将在内存中占用预定的字节数。
JavaScript 原始 Number:64 位(8 字节)
目前在浏览器中运行的 JavaScript 中,Number 类型获得 8 字节(64 位)的内存空间。某个架构师做出了这个决定,作为你可以存储的最大值和程序中每个 Number 所需内存之间的权衡。
请记住,由于他们为 Number 选择 8 字节,因此你可以在 Number 类型中存储的最大数字是:1.7976931348623157e+308。
这是一个巨大的数字,你可以通过在浏览器的开发者控制台中输入以下内容并按 **
Number.MAX_VALUE
现在,让我们继续讨论内存中发生的事情。
在 CPU(主处理器)运行的底层以及内存以比特读取的地方,对于低级程序员来说,当数据以连续的内存地址写入时,事情会变得容易得多,这基本上就是为什么数据存储在连续内存位置(彼此相邻的内存地址)的原因。
它看起来是什么样的
当你创建一个新的数字变量时,JavaScript 解释器会出去找到 8 个连续的可用字节地址空间,并设置其中的比特来表示你存储的值。
var counter = 27;
解释器在地址 0x00FFC0 存储 27。
仔细看看图 1。
想象每个矩形代表一个字节内存。大多数现代计算机只允许你访问一个字节的内存(不精确到比特级别),因此每个地址代表该字节。在我们的例子中,当我们编写 `var counter = 27;` 时,解释器找到了从地址 `0x00CCF0` 开始的 8 个字节内存,并设置了等于 `27` 的比特。
另外,请注意,即使我们只需要使用第一个字节就可以存储该值,解释器(以及 Web 浏览器和操作系统)实际上会预留整个 8 字节内存块。我已经将这些字节设置为所有比特都为零,以表明它们正在被使用。这意味着其他任何东西都不能使用这些字节,否则 JavaScript 会将其解释为 counter 的值已更改。这部分内存(这组八个字节)完全由 JavaScript 控制,用于存储 counter 变量的值。
读取和写入内存
JavaScript 解释器会记住 counter 这个名字是你访问该内存的方式。这样,当你的程序中更靠后的代码执行类似的操作时:
counter++; // increment the counter
然后它可以检索存储在那里的值(`27`),在该值上加一,然后再次存储在同一个位置,这样内存现在看起来就像你在图 2 中看到的那样:值 28 存储在位置。
内存地址是任意的
请在研究这些代表内存的图时记住,地址完全是任意的,并且是为教学目的而设定的。这只是解释内存中发生的事情的一种方式,而且内存地址在每次代码运行时都会改变。
之前的值是:00011011(十进制 27)
现在的值是:00011100(十进制 28)
存储在地址的值已更改
这里的关键是存储在内存地址的值已更改。此外,现在解释器已经存储了数据所在的位置,你无法在该程序中更改该地址。它是锁定的。这意味着你可以通过更改变量的值来更新该地址处的值,但你永远无法更改变量指向的地址——数据存储的位置。有趣的是,在其他语言(C、C++)中,存在指针变量,它们允许你更改变量指向的地址。
稍后,我们将将其与引用类型发生变化的情况进行对比,你将发现这对你理解 JavaScript 的工作原理非常重要。
undefined 现在会更有意义
在我解释了这一点之后,我就可以更好地向你解释 undefined 原始值了。还记得我告诉你,当你将变量设置为某个值时,JavaScript 解释器就会决定它的类型吗?
那么,假设你在 JavaScript 中这样做:
var x;
`x` 的类型是什么?JavaScript 解释器基于存储在变量中的数据来确定类型,但我们还没有在变量中存储任何数据,所以它的类型是 undefined。
它是 undefined,我们可以用以下代码来证明:
var x;
console.log("x is " + typeof x + " type");
现在,让我们想想 JavaScript 在这种情况下必须做什么。它无法确定类型,但它必须跟踪你试图引用一个名为 `x` 的新变量的事实。但是,`x` 的值应该是什么?在许多语言(C++、C#、Java——与 JavaScript 无关)中,它有一个称为 `null` 的特殊值。但在 JavaScript 中,架构师为这种情况创建了另一个称为 undefined 的特殊值。
在这种情况下,该变量还没有被分配任何内存。它不能,因为解释器不知道该事物在内存中会占用多少内存,因为它还不知道类型。
我对 undefined 的定义猜测
以下是我对 JavaScript 所做事情的猜测:
在内存深处,有一个孤独的地址,其中包含一个 undefined 类型。它可能看起来像你在图 3 中看到的那样:地址空间中的一个 undefined 对象。
然后,当你创建一个没有设置值的新变量时,解释器会在其查找表中存储你的变量名,然后将其值设置为该位置的地址(`0x000001`),这样当你询问 `var x` 的类型时,它会返回它指向的类型:undefined。
这应该向 JavaScript 开发人员表明,变量的值(或关联的类型)尚未设置。
到目前为止,我们已经研究了 Number 和 undefined 这两种原始类型。让我们仔细看看 Boolean 和 String 类型,因为它们与 Number 非常相似,然后我们再看看引用类型,这样就能更容易地理解 `null` 类型了。
布尔值很简单,但请记住 true/false 是小写的
布尔值简单地允许你创建一个只存储两种状态的变量:`true` 或 `false`。
你可以很容易地创建一个布尔变量:
var isConfigured = true;
console.log("isConfigured is a " + typeof isConfigured);
如果你在控制台窗口中运行该代码,你将看到图 4 中所示的结果。
请注意,`true` 是小写的。它必须是小写的,因为小写的 `true` 和小写的 `false` 是 JavaScript 中的关键字,而 JavaScript 是区分大小写的,所以 `True` 和 `False` 不是关键字。
你可以通过运行此代码来看到这一点:
var isConfigured = False;
console.log("isConfigured is a " + typeof isConfigured);
JavaScript 中 `False` 是 undefined,因此你在尝试设置变量时会立即收到错误。
布尔值,可能存储在一个字节中
布尔值可能存储在一个字节中,因为你实际上可以用一个比特(0 或 1)来存储它。布尔值可能看起来类似于图 6:存储在内存中的布尔值 `true`(1)。
如果值为 `false`,那么很可能所有比特都为 `0`,最终值为 `0`。但是,请再次注意,它在内存地址处存储变量的 *值*,因为它是原始类型(不是引用类型)。
第三章,第一部分总结
我们在这一部分涵盖了很多内容,我们从不同的角度审视了内存中的原始类型,以从计算机科学的角度描述数据类型是如何存储的。我相信理解这一点将使你更好地理解当你编写代码时,JavaScript 和你的浏览器在后台做了什么。