在本章中,我们将研究 JavaScript 具有哪些值。
在本章中,我们偶尔会使用严格相等运算符。如果a
和b
相等,a === b
结果为true
。具体含义在 10.运算符 详细解释。
9.1 什么是类型?
在本章中,我将类型视为值的集合。例如,类型 boolean
是集 {false
,true
} 。
9.2 JavaScript 的类型层次结构
图 6: JavaScript 类型的部分层次结构。缺少的是错误类,与基元类型相关的类等等。此图暗示了并非所有的对象都是 Object
的实例。
图 6 显示了 JavaScript 的类型层次结构。我们从该图中学到了什么?
- JavaScript 区分两种值:原始值和对象。我们很快就会看到有什么区别。
- 该图区分了类
Object
的对象和实例。Object
的每个实例也是一个对象,但反之亦然。但是,实际上你在实践中遇到的所有对象都是Object
的实例。例如,通过对象字面值创建的对象。关于该主题的更多细节在 原型链和类的对象并非 Object 的实例 中进行解释。
9.3 语言规范的类型
ECMAScript 规范已知的总共 7 种类型。这些类型的名称是(我使用 TypeScript 的名称,而不是规范的名称):
undefined
:唯一元素undefined
。null
:唯一元素null
。boolean
:包含false
和true
元素。number
:所有数字的类型(例如-123
,3.141
)。string
:所有字符串的类型(例如'abc'
)。symbol
:所有符号的类型(例如Symbol('My Symbol')
)。object
:所有对象的类型(与Object
不同,类Object
及其子类的所有实例的类型)。
9.4 原始值与对象
规范对值进行了重要区分:
- 原始值 是
undefined
,null
,boolean
,number
,string
,symbol
类型的元素。 - 所有其他值都是对象 。
与 Java 相比(启发了 JavaScript 语言),原始值不是二等公民。它们和对象之间的区别更加微妙。简而言之,它是:
- 原始值:是 JavaScript 中的原子数据块。
- 它们是值传递的 :当原始值分配给变量或传递给函数时,它们的内容被复制。
- 它们按值 进行比较:比较两个原始值时,比较它们的内容。
- 对象:是复合数据。
- 它们是通过标识 (我的术语)传递:当对象被分配给变量或传递给函数时,它们的标识 (想一下指针)被复制。
- 它们是通过标识 (我的术语)进行比较的:当比较两个对象时,他们的标识进行比较。
除此之外,原始值和对象非常相似:它们都具有属性 (键值条目),并且可以在相同的位置使用。
接下来,我们将更深入地研究原始值和对象。
9.4.1 原始值(简称:基元)
9.4.1.1 原始值是不可改变的
您无法更改,添加或删除基元的属性:
let str = 'abc';
assert.equal(str.length, 3);
assert.throws(
() => { str.length = 1 },
/^TypeError: Cannot assign to read only property 'length'/
);
9.4.1.2 原始值的值传递
基元是值传递的 :变量(包括参数)存储基元的内容。将原始值分配给变量或将其作为参数传递给函数时,会复制其内容。
let x = 123;
let y = x;
assert.equal(y, 123);
9.4.1.3 原始值按值 进行比较
基元按值 进行比较:当比较两个原始值时,我们比较它们的内容。
assert.equal(123 === 123, true);
assert.equal('abc' === 'abc', true);
要了解这种比较方式有什么特别之处,请继续阅读并找出对象的比较方式。
9.4.2 对象
对象的内容将会在 单实例对象 以及接下来的介绍更多细节。这里我们主要集中于其与原始值有哪些不同。
让我们首先探讨创建对象的两种常见方法:
- 对象值:
对象的值以花括号const obj = { first: 'Jane', last: 'Doe', };
{}
开头和结尾。它创建了一个具有两个属性的对象。第一个属性具有键'first'
(字符串)和值'Jane'
。第二个属性具有键'last'
和值'Doe'
。 - 数组值:
Array 值以方括号const arr = ['foo', 'bar'];
[]
开头和结尾。它创建了一个带有两个元素 的数组:'foo'
和'bar'
。有关数组值的更多信息,请参阅 创建、读取、写入数组 。
9.4.2.1 默认情况下,对象是可变的
默认情况下,您可以自由更改,添加和删除对象的属性:
const obj = {};
obj.foo = 'abc'; // 添加一个属性
assert.equal(obj.foo, 'abc');
obj.foo = 'def'; // 改变某个属性的值
assert.equal(obj.foo, 'def');
9.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.foo = 123;
assert.equal(b.foo, 123);
JavaScript 使用垃圾回收 自动管理内存:
let obj = { prop: 'value' };
obj = {};
现在obj
的旧值{ prop: 'value' }
是垃圾 (不再使用)。在某个时间点(如果有足够的可用内存,可能永远不会), JavaScript 会自动将它进行垃圾回收 (从内存中删除)。
通过身份传递
“通过标识传递”意味着对象(透明引用)的标识按值传递。这种方法也称为“通过共享传递”。
9.4.2.3 通过标识比较对象
通过标识 (我的术语)比较对象:如果两个变量包含相同的对象标识,则它们仅相等。如果它们引用具有相同内容的不同对象,则它们不相等。
const obj = {}; // fresh empty object
assert.equal(obj === obj, true); // same identity
assert.equal({} === {}, false); // different identities, same content
9.5 运算符typeof
和instanceof
:值的类型是什么?
typeof
和instanceof
这两个运算符可以确定给定值x
的类型:
if (typeof x === 'string') ···
if (x instanceof Array) ···
他们有什么不同?
typeof
区分规范的 7 种类型(减去一个遗漏,加上一个补充)。instanceof
测试哪个类创建了给定值。
经验法则:
typeof
用于原始值,instanceof
用于对象
9.5.1 typeof
表 2: typeof
操作符的结果集
x |
typeof x |
---|---|
undefined |
'undefined' |
null |
'object' |
布尔值 | 'boolean' |
数字 | 'number' |
字符串 | 'string' |
符号 | 'symbol' |
函数 | 'function' |
所有其他对象 | 'object' |
表 2 列出typeof
的所有结果。它们大致对应于语言规范的 7 种类型。唉,有两个不同之处,它们是语言怪癖:
typeof null
返回'object'
而不是'null'
。那是一个错误。不幸的是,它无法修复。 TC39 尝试这样做,但它在网络上打破了太多代码。- 函数的
typeof
应该是'object'
(函数是对象)。为功能引入单独的类别令人困惑。
练习:
typeof
的两个练习
exercises/operators/typeof_exrc.js
- 额外:
exercises/operators/is_object_test.js
9.5.2 instanceof
该运算符回答了问题:是否有一个类C
创建了值x
?
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/operators/instanceof_exrc.js
9.6 类和构造函数
JavaScript 的对象原始工厂是构造函数 :如果通过 new
操作符调用它们,则返回自身的“实例”的普通函数。
ES6 引入了构造函数最好的语法,类 。
在本书中,我可以互换地使用术语构造函数 和类 。
类可以看作是将规范的单一类型 object
划分为子类型——它们给出了比规范中有限的 7 种类型更多的类型。每个类都是由它创建的对象的类型。
9.6.1 与基本类型关联的构造函数
每个基本类型(undefined
和null
的规范内部类型除外)都有一个关联的构造函数 (想一下类):
- 构造函数
Boolean
与布尔值相关联。 - 构造函数
Number
与数字相关联。 - 构造函数
String
与字符串相关联。 - 构造函数
Symbol
与符号相关联。
每个函数都扮演着几个角色。例如,Number
:
- 可以将其用作函数并将值转换为数字:
assert.equal(Number('123'), 123);
Number.prototype
提供数字的属性。例如,方法.toString()
:assert.equal((123).toString, Number.prototype.toString);
Number
是数字工具函数的命名空间/容器对象。例如:assert.equal(Number.isInteger(123), true);
- 最后,你还可以将
Number
用作类并创建数字对象。这些对象与实数不同,应该避免。assert.notEqual(new Number(123), 123); assert.equal(new Number(123).valueOf(), 123);
9.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
包装在实践中很少有用,但它在语言规范内部使用,以提供原语属性。
9.7 在类型之间转换
在 JavaScript 中,有两种方法可以将值转换为其他类型:
- 显式转换:通过
String()
等功能。 - 强制 (自动转换):当操作接收到无法使用的操作数/参数时发生。
9.7.1 类型之间的显式转换
与基本类型关联的(构造)函数显式地将值转换为该类型:
> Boolean(0)
false
> Number('123')
123
> String(123)
'123'
你还可以使用Object()
将值转换为对象:
> typeof Object(123)
'object'
9.7.2 强制(类型之间的自动转换)
对于许多操作,如果操作数/参数的类型不匹配,JavaScript 会自动转换它们。这种自动转换称为强制 。
例如,乘法运算符将其操作数强制转换为数字:
> '7' * '3'
21
许多内置函数也强制执行。例如,parseInt()
将其参数强制转换为字符串(解析在第一个不是数字的字符处停止):
> parseInt(123.45)
123