在 ECMAScript 规范中,共定义了 7 种数据类型,分为 基本类型 和 引⽤类型 两⼤类,如下所示:
基本类型:String、Number、Boolean、Symbol、Undefined、Null
引⽤类型:Object、ArFunction
基本类型也称为简单类型,由于其占据空间固定,是简单的数据段,为了便于提升变量查询速度,将其 存储在栈中,即按值访问
引⽤类型也称为复杂类型,由于其值的⼤⼩会改变,所以不能将其存放在栈中,否则会降低变量查询速 度,因此,其值存储在堆(heap)中,⽽存储在变量处的值,是⼀个指针,指向存储对象的内存处,即按 址访问。引⽤类型除 Object、 Function 、Array、外,还包括 RegExp、Date 等等,鉴于 ECMAScript 是松散类型的,因此需要有⼀种⼿段来检测给定变量的数据类型。对于这个问题, JavaScript 也提供了多种⽅法,但遗憾的是,不同的⽅法得到的结果参差不⻬
下⾯介绍常⽤的4种⽅法,并对各个⽅法存在的问题进⾏简单的分析
1.typeof
typeof 检测数据类型会返回对应的数据类型小写字符。 引用数据类型中的:Array,Object,Date,RegExp。不可以用 typeof 检测。都会返回小写的 object
基本数据类型除了null都可以判断,还可以判断function
typeof 是⼀个操作符,其右侧跟⼀个⼀元表达式,并返回这个表达式的数据类型。返回的结果⽤该类型 的字符串(全⼩写字⺟)形式表示,包括以下 7 种:number、boolean、symbol、string、object、 undefined、function 等
typeof '';// string 有效
typeof 1;// number 有效
typeof Symbol();// symbol 有效
typeof true;//boolean 有效
typeof undefined;//undefined 有效
typeof null;//object ⽆效
typeof [] ;//object ⽆效
typeof new Function();// function 有效
typeof new Date();//object ⽆效
typeof new RegExp();//object ⽆效
有些时候,typeof 操作符会返回⼀些令⼈迷惑但技术上却正确的值:
- 对于基本类型,除 null 以外,均可以返回正确的结果
- 对于引⽤类型,除 function 以外,⼀律返回 object 类型
- 对于 null ,返回 object 类型
- 对于 function 返回 function 类型
其中,null 有属于⾃⼰的数据类型 Null , 引⽤类型中的 数组、⽇期、正则 也都有属于⾃⼰的具体类 型,⽽ typeof 对于这些类型的处理,只返回了处于其原型链最顶端的 Object 类型,没有错,但不是我 们想要的结果
2.instanceof
只能判断引用数据类型
除了使用 typeof 来判断,还可以使用 instanceof。instanceof 运算符需要指定一个构造函数,或者说指定一个特定的类型,它用来判断这个构造函数的原型是否在给定对象的原型链上。
console.log(arr instanceof Array);
instanceof 是⽤来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回 true, 否则返回 false。 在这⾥需要特别注意的是:instanceof 检测的是原型,我们⽤⼀段伪代码来模拟其内部 执⾏过程:
instanceof (A,B) = {
var L = A.__proto__;
var R = B.prototype;
if(L === R) {
// A的内部属性 __proto__ 指向 B 的原型对象
return true;
}
return false;
}
从上述过程可以看出,当 A 的 proto 指向 B 的 prototype 时,就认为 A 就是 B 的实例,我们再来看⼏个:
[] instanceof Array;// true
{} instanceof Object;// true
new Date() instanceof Date;// true
function Person(){};
new Person() instanceof Person;
[] instanceof Object;// true
new Date() instanceof Object;// true
new Person instanceof Object;// true
我们发现,虽然 instanceof 能够判断出 [ ] 是Array的实例,但它认为 [ ] 也是Object的实例,为什么呢?
我们来分析⼀下 [ ]、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 操作符的问题在于,它假定只有⼀个全局执⾏环境。如果⽹⻚中包含多个框架,那实际上就 存在两个以上不同的全局执⾏环境,从⽽存在两个以上不同版本的构造函数。如果你从⼀个框架向另⼀ 个框架传⼊⼀个数组,那么传⼊的数组与在第⼆个框架中原⽣创建的数组分别具有各⾃不同的构造函数
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
xArray = window.frames[0].Array;
var arr =new xArray(1,2,3);// [1,2,3]
arr instanceof Array;// false
针对数组的这个问题,ES5 提供了 Array.isArray() ⽅法 。该⽅法⽤以确认某个对象本身是否为 Array 类 型,⽽不区分该对象在哪个环境中创建。
if (Array.isArray(value)){
//对数组执⾏某些操作
}
Array.isArray() 本质上检测的是对象的 [[Class]] 值,[[Class]] 是对象的⼀个内部属性,⾥⾯包含了对象的 类型信息,其格式为 [object Xxx] ,Xxx 就是对应的具体类型 。对于数组⽽⾔,[[Class]] 的值就是 [object Array]
3.constructor
引用数据类型和基本数据类型都可以判断,但undefined和null除外
当⼀个函数 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 就是新对象的类型。这样做的 意义是,让新对象在诞⽣以后,就具有可追溯的数据类型。 同样,JavaScript 中的内置对象在内部构建时也是这样做的:
细节问题:
1. null 和 undefined 是⽆效的对象,因此是不会有 constructor 存在的,这两种类型的数据需要通 过其他⽅式来判断。
2. 函数的 constructor 是不稳定的,这个主要体现在⾃定义对象上,当开发者重写 prototype 后, 原有的 constructor 引⽤会丢失,constructor 会默认为 Object
为什么变成了 Object?
因为 prototype 被重新赋值的是⼀个 { }, { } 是 new Object() 的字⾯量,因此 new Object() 会将 Object 原 型上的 constructor 传递给 { },也就是 Object 本身。
因此,为了规范开发,在重写对象原型时⼀般都需要重新给 constructor 赋值,以保证对象实例的类型不被篡改
4.toString
所有数据类型都能判断
toString() 是 Object 的原型⽅法,调⽤该⽅法,默认返回当前对象的 [[Class]] 。这是⼀个内部属性,其格 式为 [object Xxx] ,其中 Xxx 就是对象的类型
对于 Object 对象,直接调⽤ toString() 就能返回 [object Object] 。⽽对于其他对象,则需要通过 call / apply 来调⽤才能返回正确的类型信息
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
的引⽤