ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
## 11 值 > 原文: [http://exploringjs.com/impatient-js/ch_values.html](http://exploringjs.com/impatient-js/ch_values.html) > > 贡献者:[lq920320](https://github.com/lq920320) 在本章中,我们将研究 JavaScript 具有哪些值。 ![](https://img.kancloud.cn/83/6f/836f0345de4fda5b9b81d7038ebb276d.svg) **支持工具:`===`** 在本章中,我们偶尔会使用严格相等运算符。如果`a`和`b`相等,`a === b`结果为`true`。具体含义在[12.4 严格相等](/docs/15.md#124平等与)详细解释。 ### 11.1 什么是类型? 在本章中,我将类型视为值的集合。例如,类型 `boolean` 是集 {`false`,`true`} 。 ### 11.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.](https://img.kancloud.cn/50/5e/505e0abfbb0e1099651698da61e9b156.svg) 图 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://img.kancloud.cn/7b/17/7b17729df07100ffa1c582a9b46ac13a.svg) **详情:通过身份传递** > > “通过标识传递”意味着对象(透明引用)的标识按值传递。这种方法也称为[“通过共享传递”](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` 测试哪个类创建了给定值。 > ![](https://img.kancloud.cn/9c/53/9c5358c6578dee8dea3427be5bbd7f12.svg) **经验法则:`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'`(函数是对象)。为功能引入单独的类别令人困惑。 > ![](https://img.kancloud.cn/3e/d5/3ed5755d562179ae6c199264f5e21157.svg) **练习:`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 ``` > ![](https://img.kancloud.cn/3e/d5/3ed5755d562179ae6c199264f5e21157.svg) **练习:`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 ``` > ![](https://img.kancloud.cn/3e/d5/3ed5755d562179ae6c199264f5e21157.svg) **练习:将值转换为基元** > > `exercises/values/conversion_exrc.js` >![](https://img.kancloud.cn/ff/a8/ffa8e16628cad59b09c786b836722faa.svg) **测验** > > 参见[测验应用程序](/docs/11.md#91测验)。