JS数据类型和判断
一、数据类型
1.1、JS数据类型
基本数据类型:Boolean、String、Number、null、undefined
引用数据类型:Object、Array、Function、RegExp、Date等
1.2、基本数据类型与引用数据类型的区别
- 保存位置不同。基本数据类型保存在栈内存中;引用数据类型保存在堆内存中,然后在栈内存中保存了一个对堆内存中实际对象的引用,即数据在堆内存中的地址。
为什么基本数据类型保存在栈中,而引用数据类型保存在堆中?
(1)堆比栈大,栈比堆速度快
(2)基本数据类型比较稳定,而且相对来说占用的内存小
(3)引用数据类型的大小是动态的、无限的,引用的值大小会改变,不能放在栈中,否则会降低变量查找的速度,因此放在变量栈中的值是该对象存储在堆中的地址,地址大小是固定的
(4)堆内存是无需内存,可以根据引用直接获取 - 数据类型的判断方法不一样。见后文中的数据类型的判断
- 创建方法不一样。引用数据类型的创建需要使用new操作符,后面再跟一个构造函数来创建。
1.3、其他
- ES6新增数据类型:Map,Set,Generator,Symbol
- 宿主对象:宿主”就是我们网页的运行环境,即“操作系统”和“浏览器”,所有非本地对象都是宿主对象(host object),即由 ECMAScript 实现的宿主环境提供的对象,所有的BOM和DOM对象都是宿主对象。
- JS内置对象:是指JS语言自带的一些对象,供开发者使用,这些对象提供了一些常用的或是最基本而必要的功能;
(1)Arguments:函数参数集合;
(2)Array对象:数组对象;
(3)Boolean:布尔对象;
(4)Error:异常对象;
(5)Number:数值对象;
(6)String对象:z字符串对象;
(7)Date对象:toUTCstring(),getTime();
(8)RegExp对象:test();
(9)Function对象:函数对象;
(10)Math对象:min(),max(),ceil(),floor(),round(),random();
(11)Global对象:encodeURI,encodeURIComponent,parseInt(),eval();
(12)Object对象:prototype,constructor;
二、数据类型的判断
2.1、typeof
typeof是一个操作符,其右侧跟一个一元表达式,返回这个表达式的数据类型,返回的结果用该类型的字符串形式(全小写字母)表示,共7种:number、boolean、string、undefined、object、symbol、function。
- 基本数据类型使用typeOf可以返回其对应的基本数据类型,只有Null会返回object
- 引用数据类型使用typeOf,除了function,其他一律返回object
2.2、instanceof
instanceof是用来判断A是否是B的实例。表达式为:A instanceof B(首字母大写);如果A是B的实例,则返回true,否则返回false。需要特别注意的是,instanceof检测的是原型。当A的————proto__指向B的prototype时,就认为A是B的实例。
[] instanceof Array; // true
{} instanceof Object;// true
new Date() instanceof Date;// true
function Person(){};
new Person() instanceof Person; // true
[] instanceof Object; // true
new Date() instanceof Object;// true
new Person instanceof Object;// true
由上可以发现,instanceof判断[]是Array的实例,但同时认为[]也是Object的实例,首先我们要了解一下它们的原型链:
首先[].__proto__指向Array.prototype,而Array.prototype.__proto__又指向了Object.prototype,最终Object.prototype.__proto__指向了null,最终形成了一条原型链:
从原型链可以看出,[]的__proto__直接指向Array.prototype,间接指向Object.prototype,所以[]就是Object的实例。同理,new Date(),new RegExp()也一样。因此,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.isArray() 用于确定传递的值是否是一个 Array,如果是Array,返回true,否则返回false
Array.isArray([1, 2, 3]); // true
Array.isArray({foo: 123}); // false
Array.isArray("foobar"); // false
Array.isArray(undefined); // false
因此,当检测Array实例时, Array.isArray 优于 instanceof,因为Array.isArray能检测iframes.
2.3、constructor
当一个函数P被定义时,JS引擎会为P添加Prototype原型,然后在prototype上添加一个constructor属性,并指向P的引用。
当执行var p=new P()时,p是构造函数P的实例化对象,此时P原型上的constructor传递到了p上,因此p.constructor == P(首字母大写)
由上可看出,当P作为构造函数来创建对象p时,原型上的constructor就被新创建的p对象继承了,从原型链角度讲,构造函数P就是新对象p的类型。这样的意义在于,新对象被创建以后,就有可追溯的数据类型。同理,JS中的内置对象在内部构件时也是这样的。
此处还有一个疑问待解决:{}.constructor会报错为什么?
1.null和undefined是无效的对象,因此是不会有constructor存在的,这两种类型的数据需要通过其他方法判断。
2.函数的constructor是不稳定的,这个主要体现在自定义对象上,当重写构造函数prototype后,原有的constructor引用会丢失
为什么变成了Object?因为prottype被重新赋值的是{},{}是new Object()的字面量,因此会将 Object 原型上的 constructor 传递给 { },也就是Object。因此,为了规范开发,在重写对象原型时一般都需要重新给 constructor 赋值,以保证对象实例的类型不被篡改。
2.4、toString
toString()是Object的原型方法,用法为Object.prototype.toString(A),调用该方法,默认返回当前对象的[[Class]],其格式为[object xxx],xxx即为数据的类型(返回的为字符串,前一个object为全小写,后面的xxx首字母大写)。
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 的引用
可以封装的判断方法如下:
var getType=Object.prototype.toString
var utility={
isObj: function(o){
return getType.call(o)=="[object Object]";
},
isArray: function(o){
return getType.call(o)=="[object Array]";
},
isNULL: function(o){
return getType.call(o)=="[object Null]";
},
isDocument: function(){
return getType.call(o)=="[object Document]"|| [object HTMLDocument];
}
........
}
也可以如下封装检测数据类型的功能函数
function checkeType(target) {
return Object.prototype.toString.call(target).slice(8, -1)
}
checkeType([]) // 'Array'