## 11 值
> 原文: [http://exploringjs.com/impatient-js/ch_values.html](http://exploringjs.com/impatient-js/ch_values.html)
>
> 贡献者:[lq920320](https://github.com/lq920320)
在本章中,我们将研究 JavaScript 具有哪些值。
 **支持工具:`===`**
在本章中,我们偶尔会使用严格相等运算符。如果`a`和`b`相等,`a === b`结果为`true`。具体含义在[12.4 严格相等](/docs/15.md#124平等与)详细解释。
### 11.1 什么是类型?
在本章中,我将类型视为值的集合。例如,类型 `boolean` 是集 {`false`,`true`} 。
### 11.2 JavaScript 的类型层次结构

图 6: JavaScript 类型的部分层次结构。缺少的是错误类,与基元类型相关的类等等。此图暗示了并非所有的对象都是 `Object` 的实例。
图 [6](#fig:type_hierarchy) 显示了 JavaScript 的类型层次结构。我们从该图中学到了什么?
- JavaScript 区分两种值:原始值和对象。我们很快就会看到有什么区别。
- 该图区分了类 `Object` 的对象和实例。 `Object` 的每个实例也是一个对象,但反之亦然。但是,实际上你在实践中遇到的所有对象都是 `Object` 的实例。例如,通过对象字面值创建的对象。关于该主题的更多细节在[26.4.3.4 对象并非 `Object` 的实例](/docs/32.md#26434-不是object实例的对象)中进行解释。
### 11.3 语言规范的类型
ECMAScript 规范已知的总共 7 种类型。这些类型的名称是(我使用 TypeScript 的名称,而不是规范的名称):
- `undefined`:唯一元素`undefined`。
- `null`:唯一元素`null`。
- `boolean`:包含`false`和`true`元素。
- `number`:所有数字的类型(例如`-123`,`3.141`)。
- `string`:所有字符串的类型(例如`'abc'`)。
- `symbol`:所有符号的类型(例如`Symbol('My Symbol')`)。
- `object`:所有对象的类型(与`Object`不同,类`Object`及其子类的所有实例的类型)。
### 11.4 原始值与对象
规范对值进行了重要区分:
- *原始值*是`undefined`,`null`,`boolean`,`number`,`string`,`symbol`类型的元素。
- 所有其他值都是*对象*。
与 Java 相比(启发了 JavaScript 语言),原始值不是二等公民。它们和对象之间的区别更加微妙。简而言之,它是:
- 原始值:是 JavaScript 中的原子数据块。
- 它们是*值传递的*:当原始值分配给变量或传递给函数时,它们的内容被复制。
- 它们*按值*进行比较:比较两个原始值时,比较它们的内容。
- 对象:是复合数据。
- 它们是*通过标识*(我的术语)传递:当对象被分配给变量或传递给函数时,它们的*标识*(想一下指针)被复制。
- 它们是*通过标识*(我的术语)进行比较的:当比较两个对象时,他们的标识进行比较。
除此之外,原始值和对象非常相似:它们都具有*属性*(键值条目),并且可以在相同的位置使用。
接下来,我们将更深入地研究原始值和对象。
#### 11.4.1 原始值(简称:基元)
##### 11.4.1.1 原始值是不可改变的
您无法更改,添加或删除基元的属性:
```js
let str = 'abc';
assert.equal(str.length, 3);
assert.throws(
() => { str.length = 1 },
/^TypeError: Cannot assign to read only property 'length'/
);
```
##### 11.4.1.2 原始值的*值传递*
基元是*值传递的*:变量(包括参数)存储基元的内容。将原始值分配给变量或将其作为参数传递给函数时,会复制其内容。
```js
let x = 123;
let y = x;
assert.equal(y, 123);
```
##### 11.4.1.3 原始值*按值*进行比较
基元*按值*进行比较:当比较两个原始值时,我们比较它们的内容。
```js
assert.equal(123 === 123, true);
assert.equal('abc' === 'abc', true);
```
要了解这种比较方式有什么特别之处,请继续阅读并找出对象的比较方式。
#### 11.4.2 对象
对象的内容将会在[第25章 “单个对象”](/docs/31.md#25单个对象)以及接下来的介绍更多细节。这里我们主要集中于其与原始值有哪些不同。
让我们首先探讨创建对象的两种常见方法:
- 对象值:
```js
const obj = {
first: 'Jane',
last: 'Doe',
};
```
对象的值以花括号`{}`开头和结尾。它创建了一个具有两个属性的对象。第一个属性具有键`'first'`(字符串)和值`'Jane'`。第二个属性具有键`'last'`和值`'Doe'`。有关对象字值的更多信息,请参阅[25.2.1 对象值:属性](/docs/31.md#2521对象字面值属性)的章节。
- 数组值:
```js
const arr = ['foo', 'bar'];
```
Array 值以方括号`[]`开头和结尾。它创建了一个带有两个*元素*的数组:`'foo'`和`'bar'`。有关数组值的更多信息,请参阅[28.2.1 创建、读取、写入数组](/docs/35.md#2821数组创建阅读写作)。
##### 11.4.2.1 默认情况下,对象是可变的
默认情况下,您可以自由更改,添加和删除对象的属性:
```js
const obj = {};
obj.foo = 'abc'; // 添加一个属性
assert.equal(obj.foo, 'abc');
obj.foo = 'def'; // 改变某个属性的值
assert.equal(obj.foo, 'def');
```
##### 11.4.2.2 对象是*通过标识传递*
对象是*通过标识传递*(我的术语):变量(包括参数)存储对象的*标识*。
对象的标识就像是*堆*(想一下 JavaScript 引擎的共享主内存)上的对象实际数据的指针(或透明引用)。
将对象分配给变量或将其作为参数传递给函数时,会复制其标识。每个对象字面值在堆上创建一个新对象并返回其标识。
```js
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.foo = 123;
assert.equal(b.foo, 123);
```
JavaScript 使用*垃圾回收*自动管理内存:
```js
let obj = { prop: 'value' };
obj = {};
```
现在`obj`的旧值`{ prop: 'value' }`是*垃圾*(不再使用)。在某个时间点(如果有足够的可用内存,可能永远不会), JavaScript 会自动将它进行*垃圾回收*(从内存中删除)。
>  **详情:通过身份传递**
>
> “通过标识传递”意味着对象(透明引用)的标识按值传递。这种方法也称为[“通过共享传递”](https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_sharing)。
##### 11.4.2.3 通过标识比较对象
*通过标识*(我的术语)比较对象:如果两个变量包含相同的对象标识,则它们仅相等。如果它们引用具有相同内容的不同对象,则它们不相等。
```js
const obj = {}; // fresh empty object
assert.equal(obj === obj, true); // same identity
assert.equal({} === {}, false); // different identities, same content
```
### 11.5 运算符`typeof`和`instanceof`:值的类型是什么?
`typeof`和`instanceof`这两个运算符可以确定给定值`x`的类型:
```js
if (typeof x === 'string') ···
if (x instanceof Array) ···
```
他们有什么不同?
- `typeof` 区分规范的 7 种类型(减去一个遗漏,加上一个补充)。
- `instanceof` 测试哪个类创建了给定值。
>  **经验法则:`typeof`用于原始值,`instanceof`用于对象**
#### 11.5.1 `typeof`
表 2: `typeof` 操作符的结果集
| `x` | `typeof x` |
| --- | --- |
| `undefined` | `'undefined'` |
| `null` | `'object'` |
| 布尔值 | `'boolean'` |
| 数字 | `'number'` |
| 字符串 | `'string'` |
| 符号 | `'symbol'` |
| 函数 | `'function'` |
| 所有其他对象 | `'object'` |
表 [2](#tbl:typeof-results) 列出`typeof`的所有结果。它们大致对应于语言规范的 7 种类型。唉,有两个不同之处,它们是语言怪癖:
- `typeof null` 返回 `'object'` 而不是`'null'`。那是一个错误。不幸的是,它无法修复。 TC39 尝试这样做,但它在网络上打破了太多代码。
- 函数的`typeof`应该是`'object'`(函数是对象)。为功能引入单独的类别令人困惑。
>  **练习:`typeof`** 的两个练习
>
> - `exercises/operators/typeof_exrc.js`
> - 额外:`exercises/operators/is_object_test.js`
#### 11.5.2 `instanceof`
该运算符回答了问题:是否有一个类`C`创建了值`x`?
```js
x instanceof C
```
例如:
```js
> (function() {}) instanceof Function
true
> ({}) instanceof Object
true
> [] instanceof Array
true
```
原始值不是任何实例:
```js
> 123 instanceof Number
false
> '' instanceof String
false
> '' instanceof Object
false
```
>  **练习:`instanceof`**
>
> `exercises/operators/instanceof_exrc.js`
### 11.6 类和构造函数
JavaScript 的对象原始工厂是*构造函数*:如果通过 `new` 操作符调用它们,则返回自身的“实例”的普通函数。
ES6 引入了构造函数最好的语法,*类*。
在本书中,我可以互换地使用术语*构造函数*和*类*。
类可以看作是将规范的单一类型 `object` 划分为子类型——它们给出了比规范中有限的 7 种类型更多的类型。每个类都是由它创建的对象的类型。
#### 11.6.1 与基本类型关联的构造函数
每个基本类型(`undefined`和`null`的规范内部类型除外)都有一个关联的*构造函数*(想一下类):
- 构造函数`Boolean`与布尔值相关联。
- 构造函数`Number`与数字相关联。
- 构造函数`String`与字符串相关联。
- 构造函数`Symbol`与符号相关联。
每个函数都扮演着几个角色。例如,`Number`:
- 可以将其用作函数并将值转换为数字:
```js
assert.equal(Number('123'), 123);
```
- `Number.prototype`提供数字的属性。例如,方法`.toString()`:
```js
assert.equal((123).toString, Number.prototype.toString);
```
- `Number`是数字工具函数的命名空间/容器对象。例如:
```js
assert.equal(Number.isInteger(123), true);
```
- 最后,你还可以将`Number`用作类并创建数字对象。这些对象与实数不同,应该避免。
```js
assert.notEqual(new Number(123), 123);
assert.equal(new Number(123).valueOf(), 123);
```
##### 11.6.1.1 包装原始值
与基本类型相关的构造函数也称为*包装类型*,因为它们提供了将原始值转换为对象的规范方法。在此过程中,原始值被“包装”在对象中。
```js
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
```
包装在实践中很少有用,但它在语言规范内部使用,以提供原语属性。
### 11.7 在类型之间转换
在 JavaScript 中,有两种方法可以将值转换为其他类型:
- 显式转换:通过`String()`等功能。
- *强制*(自动转换):当操作接收到无法使用的操作数/参数时发生。
#### 11.7.1 类型之间的显式转换
与基本类型关联的(构造)函数显式地将值转换为该类型:
```js
> Boolean(0)
false
> Number('123')
123
> String(123)
'123'
```
你还可以使用`Object()`将值转换为对象:
```js
> typeof Object(123)
'object'
```
#### 11.7.2 强制(类型之间的自动转换)
对于许多操作,如果操作数/参数的类型不匹配,JavaScript 会自动转换它们。这种自动转换称为*强制*。
例如,乘法运算符将其操作数强制转换为数字:
```js
> '7' * '3'
21
```
许多内置函数也强制执行。例如,`parseInt()`将其参数强制转换为字符串(解析在第一个不是数字的字符处停止):
```js
> parseInt(123.45)
123
```
>  **练习:将值转换为基元**
>
> `exercises/values/conversion_exrc.js`
> **测验**
>
> 参见[测验应用程序](/docs/11.md#91测验)。
- I.背景
- 1.关于本书(ES2019 版)
- 2.常见问题:本书
- 3. JavaScript 的历史和演变
- 4.常见问题:JavaScript
- II.第一步
- 5.概览
- 6.语法
- 7.在控制台上打印信息(console.*)
- 8.断言 API
- 9.测验和练习入门
- III.变量和值
- 10.变量和赋值
- 11.值
- 12.运算符
- IV.原始值
- 13.非值undefined和null
- 14.布尔值
- 15.数字
- 16. Math
- 17. Unicode - 简要介绍(高级)
- 18.字符串
- 19.使用模板字面值和标记模板
- 20.符号
- V.控制流和数据流
- 21.控制流语句
- 22.异常处理
- 23.可调用值
- VI.模块化
- 24.模块
- 25.单个对象
- 26.原型链和类
- 七.集合
- 27.同步迭代
- 28.数组(Array)
- 29.类型化数组:处理二进制数据(高级)
- 30.映射(Map)
- 31. WeakMaps(WeakMap)
- 32.集(Set)
- 33. WeakSets(WeakSet)
- 34.解构
- 35.同步生成器(高级)
- 八.异步
- 36. JavaScript 中的异步编程
- 37.异步编程的 Promise
- 38.异步函数
- IX.更多标准库
- 39.正则表达式(RegExp)
- 40.日期(Date)
- 41.创建和解析 JSON(JSON)
- 42.其余章节在哪里?