JavaScript for impatient programmers (ES2022 edition)
请支持本书:购买捐赠
(广告,请勿屏蔽。)

12 值



在本章中,我们将研究 JavaScript 拥有的值类型。

  辅助工具:===

在本章中,我们将偶尔使用严格相等运算符。如果 ab 相等,则 a === b 的值为 true。具体含义在 §13.4.2 “严格相等(===!==)” 中解释。

12.1 什么是类型?

在本章中,我认为类型是值的集合——例如,类型 boolean 是集合 { false, true }。

12.2 JavaScript 的类型层次结构

Figure 6: A partial hierarchy of JavaScript’s types. Missing are the classes for errors, the classes associated with primitive types, and more. The diagram hints at the fact that not all objects are instances of Object.

6 显示了 JavaScript 的类型层次结构。我们从该图中了解到什么?

12.3 语言规范中的类型

ECMAScript 规范总共只知道八种类型。这些类型的名称是(我使用的是 TypeScript 的名称,而不是规范中的名称)

12.4 原始值与对象

该规范对值进行了重要的区分

与 Java(JavaScript 在这里受到了它的启发)不同,原始值不是二等公民。它们与对象之间的区别更加微妙。简而言之

除此之外,原始值和对象非常相似:它们都具有属性(键值对),并且可以在相同的位置使用。

接下来,我们将更深入地研究原始值和对象。

12.4.1 原始值(简称:原始类型)

12.4.1.1 原始类型是不可变的

您不能更改、添加或删除原始类型的属性

const str = 'abc';
assert.equal(str.length, 3);
assert.throws(
  () => { str.length = 1 },
  /^TypeError: Cannot assign to read only property 'length'/
);
12.4.1.2 原始类型是按值传递

原始类型是按值传递的:变量(包括参数)存储原始类型的内容。当将原始值赋值给变量或将其作为参数传递给函数时,其内容会被复制。

const x = 123;
const y = x;
// `y` is the same as any other number 123
assert.equal(y, 123);

  观察按值传递和按引用传递之间的区别

由于原始值是不可变的并且按值比较(请参阅下一小节),因此无法观察到按值传递和按标识传递(如 JavaScript 中用于对象)之间的区别。

12.4.1.3 原始类型是按值比较

原始类型是按值比较的:当比较两个原始值时,我们比较的是它们的内容。

assert.equal(123 === 123, true);
assert.equal('abc' === 'abc', true);

要了解这种比较方式的特殊之处,请继续阅读并了解对象的比较方式。

12.4.2 对象

对象在 §28 “对象” 及其后续章节中有详细介绍。在这里,我们主要关注它们与原始值的区别。

让我们首先探讨两种常见的创建对象的方法

12.4.2.1 对象默认是可变的

默认情况下,您可以自由地更改、添加和删除对象的属性

const obj = {};

obj.count = 2; // add a property
assert.equal(obj.count, 2);

obj.count = 3; // change a property
assert.equal(obj.count, 3);
12.4.2.2 对象是按标识传递

对象是按标识传递的(我的术语):变量(包括参数)存储对象的标识

对象的标识就像是指向(想想 JavaScript 引擎的共享主内存)上对象实际数据的指针(或透明引用)。

当将对象赋值给变量或将其作为参数传递给函数时,其标识会被复制。每个对象字面量都会在堆上创建一个新对象并返回其标识。

const a = {}; // fresh empty object
// Pass the identity in `a` to `b`:
const b = a;

// Now `a` and `b` point to the same object
// (they “share” that object):
assert.equal(a === b, true);

// Changing `a` also changes `b`:
a.name = 'Tessa';
assert.equal(b.name, 'Tessa');

JavaScript 使用垃圾回收来自动管理内存

let obj = { prop: 'value' };
obj = {};

现在,obj 的旧值 { prop: 'value' } 变成了垃圾(不再使用)。JavaScript 会在某个时间点自动对其进行垃圾回收(将其从内存中删除)(如果空闲内存足够多,则可能永远不会回收)。

  详细信息:按标识传递

“按标识传递”意味着对象的标识(透明引用)是按值传递的。这种方法也称为“按共享传递”

12.4.2.3 对象是按标识比较

对象是按标识比较的(我的术语):只有当两个变量包含相同的对象标识时,它们才相等。如果它们引用的是内容相同但标识不同的对象,则它们不相等。

const obj = {}; // fresh empty object
assert.equal(obj === obj, true); // same identity
assert.equal({} === {}, false); // different identities, same content

12.5 运算符 typeofinstanceof:值的类型是什么?

运算符 typeofinstanceof 可让您确定给定值 x 的类型

if (typeof x === 'string') ···
if (x instanceof Array) ···

它们有何不同?

  经验法则:typeof 用于原始值;instanceof 用于对象

12.5.1 typeof

表 2:typeof 运算符的结果。
x typeof x
undefined 'undefined'
null 'object'
Boolean 'boolean'
Number 'number'
Bigint 'bigint'
String 'string'
Symbol 'symbol'
Function 'function'
所有其他对象 'object'

2 列出了 typeof 的所有结果。它们大致对应于语言规范中的 7 种类型。唉,有两个区别,它们是语言怪癖

以下是一些使用 typeof 的示例

> typeof undefined
'undefined'
> typeof 123n
'bigint'
> typeof 'abc'
'string'
> typeof {}
'object'

  练习:关于 typeof 的两个练习

12.5.2 instanceof

此运算符回答以下问题:值 x 是否由类 C 创建?

x instanceof C

例如

> (function() {}) instanceof Function
true
> ({}) instanceof Object
true
> [] instanceof Array
true

原始值不是任何东西的实例

> 123 instanceof Number
false
> '' instanceof String
false
> '' instanceof Object
false

  练习:instanceof

exercises/values/instanceof_exrc.mjs

12.6 类和构造函数

JavaScript 中最初的对象工厂是构造函数:如果您通过 new 运算符调用它们,它们会返回自身的“实例”的普通函数。

ES6 引入了,它们主要是构造函数的更好语法。

在本书中,我将术语构造函数互换使用。

类可以看作是将规范中的单个类型 object 划分为子类型——它们为我们提供了比规范中有限的 7 种类型更多的类型。每个类都是由它创建的对象的类型。

12.6.1 与原始类型关联的构造函数

每个原始类型(undefinednull 的规范内部类型除外)都有一个关联的构造函数(想想类)

这些函数中的每一个都扮演着多个角色——例如,Number

12.6.1.1 包装原始值

与原始类型相关的构造函数也称为包装类型,因为它们提供了将原始值转换为对象的规范方法。在此过程中,原始值被“包装”在对象中。

const prim = true;
assert.equal(typeof prim, 'boolean');
assert.equal(prim instanceof Boolean, false);

const wrapped = Object(prim);
assert.equal(typeof wrapped, 'object');
assert.equal(wrapped instanceof Boolean, true);

assert.equal(wrapped.valueOf(), prim); // unwrap

包装在实践中很少重要,但它在语言规范内部使用,以赋予原始类型属性。

12.7 类型转换

在 JavaScript 中,有两种方式可以将值转换为其他类型

12.7.1 类型之间的显式转换

与原始类型关联的函数会将值显式转换为该类型

> Boolean(0)
false
> Number('123')
123
> String(123)
'123'

您还可以使用 Object() 将值转换为对象

> typeof Object(123)
'object'

下表更详细地描述了此转换的工作原理

x Object(x)
undefined {}
null {}
布尔值 new Boolean(x)
数字 new Number(x)
大整数 BigInt 的实例(new 会抛出 TypeError
字符串 new String(x)
符号 Symbol 的实例(new 会抛出 TypeError
对象 x

12.7.2 强制转换(类型之间的自动转换)

对于许多操作,如果操作数/参数的类型不匹配,JavaScript 会自动转换它们。这种自动转换称为强制转换

例如,乘法运算符会将其操作数强制转换为数字

> '7' * '3'
21

许多内置函数也会进行强制转换。例如,Number.parseInt() 会在解析参数之前将其强制转换为字符串。这解释了以下结果

> Number.parseInt(123.45)
123

数字 123.45 在解析之前被转换为字符串 '123.45'。解析在第一个非数字字符之前停止,这就是结果为 123 的原因。

  练习:将值转换为原始值

exercises/values/conversion_exrc.mjs

  测验

参见测验应用程序