数据类型
ECMAScript 有七种简单数据类型(也称为原始类型):
Undefined
Null
Boolean
Number
String
Symbol
BigInt
Symbol(符号)是 ECMAScript 6 新增的。 BigInt 是第七种原始类型,于 ECMAScript 2020 (ES11) 中正式加入。还有一种复杂数据类型叫 Object(对象)。因为在 ECMAScript 中不能定义自己的数据类型,所有值都可以用上述 8 种数据类型之一来表示。
Undefined 类型
Undefined 类型只有一个值,就是特殊值 undefined。当使用变量声明了变量但没有初始化时,就相当于给变量赋予了 undefined 值。
在对未初始化的变量调用 typeof 时,返回的结果是 "undefined",但对未声明的变量调用它时,返回的结果还是 "undefined"。无论是声明还是未声明,typeof 返回的都是字符串 "undefined"。逻辑上讲这是对的,因为虽然严格来讲这两个变量存在根本性差异,但它们都无法执行实际操作。
一般来说,永远不用显式地给某个变量设置 undefined 值。字面值 undefined 主要用于比较,而且在 ECMA-262 第 3 版之前是不存在的。增加这个特殊值的目的就是为了正式明确空对象指针(null)和未初始化变量的区别。
Null 类型
Null 类型同样只有一个值,即特殊值 null。逻辑上讲,null 值表示一个空对象指针,这也是给 typeof 传一个 null 会返回 "object" 的原因。
任何时候,只要变量要保存对象,而当时又没有那个对象可保存,就要用 null 来填充该变量。这样就可以保持 null 是空对象指针的语义,并进一步将其与 undefined 区分开来。
typeof 操作符
因为 ECMAScript 的类型系统是松散的,所以需要一种手段来确定任意变量的数据类型。typeof 操作符就是为此而生的。对一个值使用 typeof 操作符会返回下列字符串之一:
- “undefined” 表示值未定义
- “boolean” 表示值为布尔值
- “string” 表示值为字符串
- “number” 表示值为数值
- “object” 表示值为对象(而不是函数)或 null
- “function” 表示值为函数
- “symbol” 表示值为符号
- “bigint” 表示值为大整数
typeof 操作符最适合用来判断一个变量是否为原始类型。更确切地说,它是判断一个变量是否为字符串、数值、布尔值、大整数、符号或 undefined 的最好方式。
下面是使用 typeof 操作符的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const value1 = 1; const value2 = "1"; const value3 = true; const value4 = Symbol(); const value5 = 10n; const value6 = undefined; const value7 = null; const value8 = {}; const value9 = []; const value10 = function () {};
console.log(typeof value1); console.log(typeof value2); console.log(typeof value3); console.log(typeof value4); console.log(typeof value5); console.log(typeof value6); console.log(typeof value7); console.log(typeof value8); console.log(typeof value9); console.log(typeof value10);
|

typeof 在某些情况下返回的结果可能会让人费解,但技术上讲还是正确的。比如,调用 typeof null 返回的是 "object"。这是因为在 JavaScript 初代(1995 年,Netscape Navigator 2),值在内存中以 32 位存储,前 3 位作为类型标签:对象标签为 000,而 null 被表示为全 0(即 0x00000000),导致类型检查误认其为对象。
严格来讲,函数在 ECMAScript 中被认为是对象,并不代表一种数据类型。可是,函数也有自己特殊的属性。为此,就有必要通过 typeof 操作符来区分函数和其他对象。
instanceof 操作符
typeof 虽然对原始值很有用,但它对引用值的用处不大。我们通常不关心一个值是不是对象,而是想知道它是什么类型的对象。为了解决这个问题,ECMAScript 提供了 instanceof 操作符,语法如下:
1
| result = variable instanceof constructor
|
instanceof 的内部实现机制是:通过判断对象的原型链上是否能找到对象的 prototype,来确定 instanceof 返回值,如果变量是给定引用类型(由其原型链决定)的实例,则 instanceof 操作符返回 true。
**使用 instanceof 的问题是假定只有一个全局执行上下文。**如果网页里有多个框架,则可能涉及两个不同的全局执行上下文,因此就会有两个不同版本构造函数。如果要把数组从一个框架传给另一个框架,则这个数组的构造函数将有别于在第二个框架内本地创建的数组。
按照定义,所有引用值都是 Object 的实例,因此通过 instanceof 操作符检测任何引用值和 Object 构造函数都会返回 true。类似地,如果用 instanceof 检测原始值,则始终会返回 false,因为原始值不是对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const value1 = {}; const value2 = []; const value3 = function () {}; const value4 = null; const value5 = Symbol("value5"); const pattern = new RegExp(/[^0-9]/);
console.log("instanceof value1:", value1 instanceof Object); console.log("instanceof value2:", value2 instanceof Object); console.log("instanceof value3:", value3 instanceof Object); console.log("instanceof value4:", value4 instanceof Object); console.log("instanceof value5:", value5 instanceof Object); console.log("instanceof pattern:", pattern instanceof Object); console.log("--------------------------"); console.log("instanceof value1:", value1 instanceof Object); console.log("instanceof value2:", value2 instanceof Array); console.log("instanceof value3:", value3 instanceof Function); console.log("instanceof pattern:", pattern instanceof RegExp);
|

Object.prototype.toString
Object.prototype.toString() 返回 "[object Type]",这里的 Type 是对象的类型。如果对象有 Symbol.toStringTag 属性,其值是一个字符串,则它的值将被用作 Type。许多内置的对象,包括 Map 和 Symbol,都有 Symbol.toStringTag。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const toString = Object.prototype.toString;
console.log(toString.call(1)); console.log(toString.call("1")); console.log(toString.call(true)); console.log(toString.call(undefined)); console.log(toString.call(null)); console.log(toString.call(Symbol())); console.log(toString.call(2n)); console.log(toString.call({})); console.log(toString.call([])); console.log(toString.call(() => {})); console.log(toString.call(new RegExp(/[^0-9]/))); console.log(toString.call(new Date())); console.log(toString.call(new Map())); console.log(toString.call(new Set())); console.log(toString.call(new WeakMap())); console.log(toString.call(new WeakSet())); console.log(toString.call(new Error())); console.log(toString.call(new Promise(() => {})));
|

以这种方式使用 toString() 是不可靠的;对象可以通过定义 Symbol.toStringTag 属性来更改 Object.prototype.toString() 的行为,从而导致意想不到的结果。例如:
1 2 3 4 5 6 7 8 9 10
| const toString = Object.prototype.toString;
const myDate = new Date(); console.log(toString.call(myDate));
myDate[Symbol.toStringTag] = "myDate"; console.log(toString.call(myDate));
Date.prototype[Symbol.toStringTag] = "prototype polluted"; console.log(toString.call(new Date()));
|
