typeof
和 instanceof
:值的类型是什么?typeof
instanceof
在本章中,我们将研究 JavaScript 拥有的值类型。
辅助工具:===
在本章中,我们将偶尔使用严格相等运算符。如果 a
和 b
相等,则 a === b
的值为 true
。具体含义在 §13.4.2 “严格相等(===
和 !==
)” 中解释。
在本章中,我认为类型是值的集合——例如,类型 boolean
是集合 { false
, true
}。
图 6 显示了 JavaScript 的类型层次结构。我们从该图中了解到什么?
Object
的实例。Object
的每个实例也是一个对象,但反之则不然。但是,您在实践中遇到的几乎所有对象都是 Object
的实例——例如,通过对象字面量创建的对象。有关此主题的更多详细信息,请参见 §29.7.3 “并非所有对象都是 Object
的实例”。ECMAScript 规范总共只知道八种类型。这些类型的名称是(我使用的是 TypeScript 的名称,而不是规范中的名称)
undefined
,唯一元素为 undefined
null
,唯一元素为 null
boolean
,元素为 false
和 true
number
,所有数字的类型(例如,-123
、3.141
)bigint
,所有大整数的类型(例如,-123n
)string
,所有字符串的类型(例如,'abc'
)symbol
,所有符号的类型(例如,Symbol('My Symbol')
)object
,所有对象的类型(不同于 Object
,它是类 Object
及其子类的所有实例的类型)该规范对值进行了重要的区分
undefined
、null
、boolean
、number
、bigint
、string
、symbol
的元素。与 Java(JavaScript 在这里受到了它的启发)不同,原始值不是二等公民。它们与对象之间的区别更加微妙。简而言之
除此之外,原始值和对象非常相似:它们都具有属性(键值对),并且可以在相同的位置使用。
接下来,我们将更深入地研究原始值和对象。
您不能更改、添加或删除原始类型的属性
const str = 'abc';
.equal(str.length, 3);
assert.throws(
assert=> { str.length = 1 },
() /^TypeError: Cannot assign to read only property 'length'/
; )
原始类型是按值传递的:变量(包括参数)存储原始类型的内容。当将原始值赋值给变量或将其作为参数传递给函数时,其内容会被复制。
const x = 123;
const y = x;
// `y` is the same as any other number 123
.equal(y, 123); assert
观察按值传递和按引用传递之间的区别
由于原始值是不可变的并且按值比较(请参阅下一小节),因此无法观察到按值传递和按标识传递(如 JavaScript 中用于对象)之间的区别。
原始类型是按值比较的:当比较两个原始值时,我们比较的是它们的内容。
.equal(123 === 123, true);
assert.equal('abc' === 'abc', true); assert
要了解这种比较方式的特殊之处,请继续阅读并了解对象的比较方式。
对象在 §28 “对象” 及其后续章节中有详细介绍。在这里,我们主要关注它们与原始值的区别。
让我们首先探讨两种常见的创建对象的方法
对象字面量
const obj = {
first: 'Jane',
last: 'Doe',
; }
对象字面量以花括号 {}
开始和结束。它创建一个具有两个属性的对象。第一个属性的键为 'first'
(字符串),值为 'Jane'
。第二个属性的键为 'last'
,值为 'Doe'
。有关对象字面量的更多信息,请参阅 §28.3.1 “对象字面量:属性”。
数组字面量
const fruits = ['strawberry', 'apple'];
数组字面量以方括号 []
开始和结束。它创建一个具有两个元素的数组:'strawberry'
和 'apple'
。有关数组字面量的更多信息,请参阅 §31.3.1 “创建、读取、写入数组”。
默认情况下,您可以自由地更改、添加和删除对象的属性
const obj = {};
.count = 2; // add a property
obj.equal(obj.count, 2);
assert
.count = 3; // change a property
obj.equal(obj.count, 3); assert
对象是按标识传递的(我的术语):变量(包括参数)存储对象的标识。
对象的标识就像是指向堆(想想 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):
.equal(a === b, true);
assert
// Changing `a` also changes `b`:
.name = 'Tessa';
a.equal(b.name, 'Tessa'); assert
JavaScript 使用垃圾回收来自动管理内存
let obj = { prop: 'value' };
= {}; obj
现在,obj
的旧值 { prop: 'value' }
变成了垃圾(不再使用)。JavaScript 会在某个时间点自动对其进行垃圾回收(将其从内存中删除)(如果空闲内存足够多,则可能永远不会回收)。
详细信息:按标识传递
“按标识传递”意味着对象的标识(透明引用)是按值传递的。这种方法也称为“按共享传递”。
对象是按标识比较的(我的术语):只有当两个变量包含相同的对象标识时,它们才相等。如果它们引用的是内容相同但标识不同的对象,则它们不相等。
const obj = {}; // fresh empty object
.equal(obj === obj, true); // same identity
assert.equal({} === {}, false); // different identities, same content assert
typeof
和 instanceof
:值的类型是什么?运算符 typeof
和 instanceof
可让您确定给定值 x
的类型
if (typeof x === 'string') ···
if (x instanceof Array) ···
它们有何不同?
typeof
区分规范中的 7 种类型(减去一个省略,加上一个添加)。instanceof
测试哪个类创建了给定值。 经验法则:typeof
用于原始值;instanceof
用于对象
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 null
返回 'object'
而不是 'null'
。这是一个错误。不幸的是,它无法修复。TC39 尝试过这样做,但它破坏了网络上过多的代码。typeof
应该是 'object'
(函数是对象)。为函数引入单独的类别会造成混淆。以下是一些使用 typeof
的示例
> typeof undefined'undefined'
> typeof 123n'bigint'
> typeof 'abc''string'
> typeof {}'object'
练习:关于 typeof
的两个练习
exercises/values/typeof_exrc.mjs
exercises/values/is_object_test.mjs
instanceof
此运算符回答以下问题:值 x
是否由类 C
创建?
instanceof C x
例如
> (function() {}) instanceof Functiontrue
> ({}) instanceof Objecttrue
> [] instanceof Arraytrue
原始值不是任何东西的实例
> 123 instanceof Numberfalse
> '' instanceof Stringfalse
> '' instanceof Objectfalse
练习:instanceof
exercises/values/instanceof_exrc.mjs
JavaScript 中最初的对象工厂是构造函数:如果您通过 new
运算符调用它们,它们会返回自身的“实例”的普通函数。
ES6 引入了类,它们主要是构造函数的更好语法。
在本书中,我将术语构造函数和类互换使用。
类可以看作是将规范中的单个类型 object
划分为子类型——它们为我们提供了比规范中有限的 7 种类型更多的类型。每个类都是由它创建的对象的类型。
每个原始类型(undefined
和 null
的规范内部类型除外)都有一个关联的构造函数(想想类)
Boolean
与布尔值关联。Number
与数字关联。String
与字符串关联。Symbol
与符号关联。这些函数中的每一个都扮演着多个角色——例如,Number
您可以将其用作函数并将值转换为数字
.equal(Number('123'), 123); assert
Number.prototype
提供数字的属性——例如,方法 .toString()
.equal((123).toString, Number.prototype.toString); assert
Number
是数字工具函数的命名空间/容器对象——例如
.equal(Number.isInteger(123), true); assert
最后,您还可以将 Number
用作类并创建数字对象。这些对象与实数不同,应避免使用。
.notEqual(new Number(123), 123);
assert.equal(new Number(123).valueOf(), 123); assert
与原始类型相关的构造函数也称为包装类型,因为它们提供了将原始值转换为对象的规范方法。在此过程中,原始值被“包装”在对象中。
const prim = true;
.equal(typeof prim, 'boolean');
assert.equal(prim instanceof Boolean, false);
assert
const wrapped = Object(prim);
.equal(typeof wrapped, 'object');
assert.equal(wrapped instanceof Boolean, true);
assert
.equal(wrapped.valueOf(), prim); // unwrap assert
包装在实践中很少重要,但它在语言规范内部使用,以赋予原始类型属性。
在 JavaScript 中,有两种方式可以将值转换为其他类型
与原始类型关联的函数会将值显式转换为该类型
> 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 |
对于许多操作,如果操作数/参数的类型不匹配,JavaScript 会自动转换它们。这种自动转换称为强制转换。
例如,乘法运算符会将其操作数强制转换为数字
> '7' * '3'21
许多内置函数也会进行强制转换。例如,Number.parseInt()
会在解析参数之前将其强制转换为字符串。这解释了以下结果
> Number.parseInt(123.45)123
数字 123.45
在解析之前被转换为字符串 '123.45'
。解析在第一个非数字字符之前停止,这就是结果为 123
的原因。
练习:将值转换为原始值
exercises/values/conversion_exrc.mjs
测验
参见测验应用程序。