JavaScript数据类型(六)多种数据类型判断方法

日极则仄,月满则亏。物极则反,命曰环流。——《管子·白心》

简介

在 ECMAScript 规范中,共定义了 7 种数据类型,分为基本数据类型和引用类型两大类,如下所示:

基本类型: Null、Undefined、Symbol(ES6)、Number、Boolean、String
引用类型: Obeject、Array、Date等等

  • 基本类型也称为简单类型,由于其占据空间固定,是简单的数据段,为了便于提升变量查询速度,将其存储在中,即按值访问。

  • 引用类型也称为复杂类型,由于其值的大小会改变,所以不能将其存放在栈中,否则会降低变量查询速度,因此,其值存储在堆(heap)中,而存储在变量处的值,是一个指针,指向存储对象的内存处,即按址访问。

原始值( primitive values )

除 Object 以外的所有类型都是不可变的(值本身无法被改变)。例如,与 C 语言不同,JavaScript 中字符串是不可变的(译注:如,JavaScript 中对字符串的操作一定返回了一个新字符串,原始字符串并没有被改变)。我们称这些类型的值为“原始值”。

注: 想看 Java​Script 数据类型和数据结构 可以在: mdn中查看

有四种方法可以判断 Java​Script 中的类型如:

  • typeof 运算符
  • instanceof 运算符
  • constructor(原型对象的构造函数)
  • toString(内置[[Class]]属性)、Array.isArray()
    下面就分别来讲一下他们能判断什么类型,判断不了什么类型,因为什么还有一些注意事项。

typeof 运算符

typeof 语法

typeof 运算符后跟操作数:

typeof operand
or
typeof (operand)

参数

operand 是一个表达式,表示对象或原始值,其类型将被返回。

下面表格总结了 typeof 可能返回的值。

类型 结果
Undefined “undefined”
Null “object”
Boolean “boolean”
Number “number”
String “string”
Symbol (ECMAScript 6 新增) “symbol”
宿主对象(由 JS 环境提供) Implementation-dependent
函数对象([[Call]] 在 ECMA-262 条款中实现了) “function”
任何其他对象 “object”

实例

1
2
3
4
5
6
7
8
9
10
typeof undefined; // undefined 有效
typeof null; // object 无效
typeof true; // boolean 有效
typeof 1; // number 有效
typeof '123'; // string 有效
typeof Symbol('foo'); // symbol 有效
typeof window; // object
typeof function () {}; // function 有效
typeof { a: 1 }; // object
typeof []; // object

在这其中要注意的是:

  • 其中null返回了object是因为JavaScript语言设计遗留的问题。
  • 对于引用类型,除 function 以外,一律返回 object 类型
  • 对于 function 返回 function

typeof null

在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。由于 null 代表的是机器代码的空指针,一个对象类型的引用,值是零(大多数平台下值为 0x00)。自然前三位也是 0,所以执行typeof时会返回"object".

这个 bug 是第一版 Javascript 留下来的。在这个版本,数值是以32 字节存储的,由标志位(1~3 个字节)和数值组成。标志位存储的是低位的数据。这里有五种标志位:

  • 000:对象,数据是对象的应用。
  • 1:整型,数据是 31 位带符号整数。
  • 010:双精度类型,数据是双精度数字。
  • 100:字符串,数据是字符串。
  • 110:布尔类型,数据是布尔值。
    最低位有一位,那么标志位只有一个 1 字节长度;或者是零位,标志位有 3 个字节长度,多出两个了字节,一共多出四种类型。

原文地址英文
翻译地址中文

判断 null 类型也很简单,就用 null === null 来判断 > 看 typeof 所有的类型细节请看 mdn-typeof

instanceof 运算符

instanceof 运算符用于测试构造函数的 prototype 属性是否出现在对象的原型链中的任何位置.
原始值使用instanceof都会返回false,如果使用new 声明 是可以检测出来。对于是使用new声明的类型,它还可以检测出多层继承关系。

语法

object instanceof constructor

参数

object 要检测的对象
constructor 某个构造函数

实例

instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var str = '123';
str instanceof String; // false

function C() {}
var c1 = new C();
c1 instanceof C; // true,因为 Object.getPrototypeOf(c1) === C.prototype

C.prototype = {};

c1 instanceof C; // false, C.prototype指向了一个空对象,这个空对象不在o的原型链上.

var c2 = new C();

c2 instanceof C; // true,因为 Object.getPrototypeOf(c2) === C.prototype

c2.__proto__ = {}; // 借助于非标准的__proto__伪属性

c2 instanceof C; // false

注:需要注意的是,如果表达式 obj instanceof Foo 返回 true,则并不意味着该表达式会永远返回 true,因为 Foo.prototype 属性的值有可能会改变,改变之后的值很有可能不存在于 obj 的原型链上,这时原表达式的值就会成为 false。另外一种情况下,原表达式的值也会改变,就是改变对象 obj 的原型链的情况,虽然在目前的 ES 规范中,我们只能读取对象的原型而不能改变它,但借助于非标准的proto伪属性,是可以实现的。比如执行 obj.proto = {}之后,obj instanceof Foo 就会返回 false 了。

[]、Array、Object 三者之间的关系

instanceof 能够判断出 [ ].__proto__ 指向 Array.prototype,而 Array.prototype.__proto__ 又指向了Object.prototype,最终 Object.prototype.__proto__ 指向了null,标志着原型链的结束。因此,[]、Array、Object 就在内部形成了一条原型链:

[]-Array-Object

从原型链可以看出,[]__proto__ 直接指向Array.prototype,间接指向 Object.prototype,所以按照 instanceof 的判断规则,[] 就是Object的实例。依次类推,类似的 new Date()、new Person() 也会形成一条对应的原型链 。因此,instanceof 只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型。

instanceof 和多全局对象(多个 frame 或多个 window 之间的交互)

在浏览器中,我们的脚本可能需要在多个窗口之间进行交互。多个窗口意味着多个全局环境,不同的全局环境拥有不同的全局对象,从而拥有不同的内置类型构造函数。这可能会引发一些问题。比如,表达式 [] instanceof window.frames[0].Array 会返回false,因为 Array.prototype !== window.frames[0].Array.prototype,并且数组从前者继承。

实际上你可以通过使用 Array.isArray(myObj) 或者Object.prototype.toString.call(myObj) === "[object Array]"来安全的检测传过来的对象是否是一个数组。

实现一个简单的 instanceof

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// L instanceof R
function instance_of(L, R) {
//L 表示左表达式,R 表示右表达式
var O = R.prototype; // 取的R的显式原型
L = L.__proto__; // 取的L的隐式原型
while (true) {
if (L === null) {
// 已找到顶层
return false;
}
if (O === L) {
// 当 O严格等于L时,返回true
return true;
}
L = L.__proto__; // 继续向上一层原型链查找
}
}
// 测试代码
instance_of([], Array); // true
instance_of([], Object); // true

constructor(原型对象的构造函数)

当一个函数 F 被定义时,JS 引擎会为 F 添加 prototype 原型,然后再在 prototype上添加一个 constructor 属性,并让其指向 F 的引用。如下所示:

constructor

当执行 var f = new F() 时,F 被当成了构造函数,f 是 F 的实例对象,此时 F 原型上的 constructor 传递到了 f 上,因此 f.constructor === F.

可以看出,F 利用原型对象上的 constructor 引用了自身,当 F 作为构造函数来创建对象时,原型上的 constructor 就被遗传到了新创建的对象上, 从原型链角度讲,构造函数 F 就是新对象的类型。这样做的意义是,让新对象在诞生以后,就具有可追溯的数据类型。

实例

1
2
3
4
''.constructor === String; // true;
new Number(1).constructor === Number; // true;
true.constructor === Boolean; // true;
new Data().constructor === Date; // true
  • null 和 undefined 是无效的对象,因此是不会有 constructor 存在的,这两种类型的数据需要通过其他方式来判断
  • 函数的 constructor 是不稳定的,这个主要体现在自定义对象上,当开发者重写 prototype 后,原有的 constructor 引用会丢失,constructor 会默认为 Object

总结: 手动设置或更新构造函数可能会导致不同且有时令人困惑的后果。为了防止它,只需在每个特定情况下定义构造函数的角色。在大多数情况下,不使用构造函数,并且不需要重新分配构造函数。

toString(内置[[Class]]属性)


原型上的toString() 方法返回一个表示该对象的字符串。
调用该方法,默认返回当前对象的 [[Class]] 。这是一个内部属性,其格式为 [object Xxx] ,其中 Xxx 就是对象的类型。
对于 Object 对象,直接调用 toString() 就能返回 [object Object] 。而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
Object.prototype.toString.call(''); // [object String]
Object.prototype.toString.call(1); // [object Number]
Object.prototype.toString.call(true); // [object Boolean]
Object.prototype.toString.call(Symbol()); //[object Symbol]
Object.prototype.toString.call(undefined); // [object Undefined]
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call(new Function()); // [object Function]
Object.prototype.toString.call(new Date()); // [object Date]
Object.prototype.toString.call([]); // [object Array]
Object.prototype.toString.call(new RegExp()); // [object RegExp]
Object.prototype.toString.call(new Error()); // [object Error]
Object.prototype.toString.call(document); // [object HTMLDocument]
Object.prototype.toString.call(window); //[object global] window 是全局对象 global 的引用

注:但是它不能检测非原生构造函数的构造函数名。

jquery中的$.type原理就是通过Object.prototype.toString.call();
Array.isArray其实也是通过[[Class]]来判定当前是否维数组.

总结

以上就是已知的 4 中检测类型的方法,那个方法都不识最完美的,就看你要检测的是那个对应的类型,就用对应的检测方法。

我们可以通过四种方式获取数据类型:

  • typeof 运算符,用来区分对象和原始值
  • instanceof 运算符,用来分类对象
  • constructor,用来创建实例对象的 Object 构造函数的引用
  • [[Class]]是一个内部属性字符串,用来给对象分类

参考

判断 JS 数据类型的四种方法
MDN 中的 constructor
MDN 中的 toString
MDN 中的 typeof
js 判断数据类型