typeof 和 instanceof:值的类型是什么?typeofinstanceof在本章中,我们将研究 JavaScript 拥有的值类型。
辅助工具:
===
在本章中,我们将偶尔使用严格相等运算符。如果 a 和 b 相等,则 a === b 的值为 true。具体含义在 §13.4.2 “严格相等(=== 和 !==)” 中解释。
在本章中,我认为类型是值的集合——例如,类型 boolean 是集合 { false, true }。
图 6 显示了 JavaScript 的类型层次结构。我们从该图中了解到什么?
Object 的实例。Object 的每个实例也是一个对象,但反之则不然。但是,您在实践中遇到的几乎所有对象都是 Object 的实例——例如,通过对象字面量创建的对象。有关此主题的更多详细信息,请参见 §29.7.3 “并非所有对象都是 Object 的实例”。ECMAScript 规范总共只知道八种类型。这些类型的名称是(我使用的是 TypeScript 的名称,而不是规范中的名称)
undefined,唯一元素为 undefinednull,唯一元素为 nullboolean,元素为 false 和 truenumber,所有数字的类型(例如,-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';
assert.equal(str.length, 3);
assert.throws(
() => { 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
assert.equal(y, 123); 观察按值传递和按引用传递之间的区别
由于原始值是不可变的并且按值比较(请参阅下一小节),因此无法观察到按值传递和按标识传递(如 JavaScript 中用于对象)之间的区别。
原始类型是按值比较的:当比较两个原始值时,我们比较的是它们的内容。
assert.equal(123 === 123, true);
assert.equal('abc' === 'abc', true);要了解这种比较方式的特殊之处,请继续阅读并了解对象的比较方式。
对象在 §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 = {};
obj.count = 2; // add a property
assert.equal(obj.count, 2);
obj.count = 3; // change a property
assert.equal(obj.count, 3);对象是按标识传递的(我的术语):变量(包括参数)存储对象的标识。
对象的标识就像是指向堆(想想 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 会在某个时间点自动对其进行垃圾回收(将其从内存中删除)(如果空闲内存足够多,则可能永远不会回收)。
详细信息:按标识传递
“按标识传递”意味着对象的标识(透明引用)是按值传递的。这种方法也称为“按共享传递”。
对象是按标识比较的(我的术语):只有当两个变量包含相同的对象标识时,它们才相等。如果它们引用的是内容相同但标识不同的对象,则它们不相等。
const obj = {}; // fresh empty object
assert.equal(obj === obj, true); // same identity
assert.equal({} === {}, false); // different identities, same contenttypeof 和 instanceof:值的类型是什么?运算符 typeof 和 instanceof 可让您确定给定值 x 的类型
if (typeof x === 'string') ···
if (x instanceof Array) ···它们有何不同?
typeof 区分规范中的 7 种类型(减去一个省略,加上一个添加)。instanceof 测试哪个类创建了给定值。 经验法则:
typeof 用于原始值;instanceof 用于对象
typeofx |
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.mjsinstanceof此运算符回答以下问题:值 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
JavaScript 中最初的对象工厂是构造函数:如果您通过 new 运算符调用它们,它们会返回自身的“实例”的普通函数。
ES6 引入了类,它们主要是构造函数的更好语法。
在本书中,我将术语构造函数和类互换使用。
类可以看作是将规范中的单个类型 object 划分为子类型——它们为我们提供了比规范中有限的 7 种类型更多的类型。每个类都是由它创建的对象的类型。
每个原始类型(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);与原始类型相关的构造函数也称为包装类型,因为它们提供了将原始值转换为对象的规范方法。在此过程中,原始值被“包装”在对象中。
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包装在实践中很少重要,但它在语言规范内部使用,以赋予原始类型属性。
在 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
测验
参见测验应用程序。