日极则仄,月满则亏。物极则反,命曰环流。——《管子·白心》
简介
在 ECMAScript 规范中,共定义了 7 种数据类型,分为基本数据类型和引用类型两大类,如下所示:
基本类型: Null、Undefined、Symbol(ES6)、Number、Boolean、String
引用类型: Obeject、Array、Date等等
基本类型也称为简单类型,由于其占据空间固定,是简单的数据段,为了便于提升变量查询速度,将其存储在
栈
中,即按值访问。引用类型也称为复杂类型,由于其值的大小会改变,所以不能将其存放在栈中,否则会降低变量查询速度,因此,其值存储在
堆(heap)
中,而存储在变量处的值,是一个指针,指向存储对象的内存处,即按址访问。
原始值( primitive values )
除 Object 以外的所有类型都是不可变的(值本身无法被改变)。例如,与 C 语言不同,JavaScript 中字符串是不可变的(译注:如,JavaScript 中对字符串的操作一定返回了一个新字符串,原始字符串并没有被改变)。我们称这些类型的值为“原始值”。
注: 想看 JavaScript 数据类型和数据结构 可以在: mdn中查看
有四种方法可以判断 JavaScript 中的类型如:
- 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 | typeof undefined; // undefined 有效 |
在这其中要注意的是:
- 其中
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 | var str = '123'; |
注:需要注意的是,如果表达式 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
就在内部形成了一条原型链:
从原型链可以看出,[]
的 __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 | // L instanceof R |
constructor(原型对象的构造函数)
当一个函数 F 被定义时,JS 引擎会为 F 添加 prototype
原型,然后再在 prototype
上添加一个 constructor
属性,并让其指向 F 的引用。如下所示:
当执行 var f = new F()
时,F 被当成了构造函数,f 是 F 的实例对象,此时 F 原型上的 constructor
传递到了 f 上,因此 f.constructor === F
.
可以看出,F 利用原型对象上的 constructor 引用了自身,当 F 作为构造函数来创建对象时,原型上的 constructor 就被遗传到了新创建的对象上, 从原型链角度讲,构造函数 F 就是新对象的类型。这样做的意义是,让新对象在诞生以后,就具有可追溯的数据类型。
实例
1 | ''.constructor === String; // true; |
nul
l 和undefined
是无效的对象,因此是不会有constructor
存在的,这两种类型的数据需要通过其他方式来判断- 函数的
constructor
是不稳定的,这个主要体现在自定义对象上,当开发者重写prototype
后,原有的constructor
引用会丢失,constructor 会默认为 Object
总结: 手动设置或更新构造函数可能会导致不同且有时令人困惑的后果。为了防止它,只需在每个特定情况下定义构造函数的角色。在大多数情况下,不使用构造函数,并且不需要重新分配构造函数。
toString(内置[[Class]]属性)
原型上的toString()
方法返回一个表示该对象的字符串。
调用该方法,默认返回当前对象的 [[Class]]
。这是一个内部属性,其格式为 [object Xxx]
,其中 Xxx 就是对象的类型。
对于 Object 对象,直接调用 toString()
就能返回 [object Object]
。而对于其他对象,则需要通过 call / apply
来调用才能返回正确的类型信息。
1 | Object.prototype.toString.call(''); // [object String] |
注:但是它不能检测非原生构造函数的构造函数名。
jquery
中的$.type
原理就是通过Object.prototype.toString.call()
;Array.isArray
其实也是通过[[Class]]
来判定当前是否维数组.
总结
以上就是已知的 4 中检测类型的方法,那个方法都不识最完美的,就看你要检测的是那个对应的类型,就用对应的检测方法。
我们可以通过四种方式获取数据类型:
typeof 运算符,用来区分对象和原始值
instanceof 运算符,用来分类对象
constructor,用来创建实例对象的 Object 构造函数的引用
[[Class]]是一个内部属性字符串,用来给对象分类
参考
判断 JS 数据类型的四种方法
MDN 中的 constructor
MDN 中的 toString
MDN 中的 typeof
js 判断数据类型