JavaScript的数据类型及其检测方法

230 阅读8分钟

动态类型

JavaScript 是一种动态语言。这意味着你不用提前声明变量的类型,在程序运行过程中,类型会被自动确定。这也意味着你可以使用同一个变量保存不同类型的数据:

var foo = 42;    // foo is a Number now
var foo = "bar"; // foo is a String now
var foo = true;  // foo is a Boolean now

数据类型

最新的 ECMAScript 标准定义了 7 种数据类型:

6种 原始类型 primitive type,直接取值:

  • number
  • string
  • boolean
  • null
  • undefined
  • symbol (ES2015 新定义)

1种 引用类型/复合类型 complex type,引用取值

实例化对象的过程有两种,一种是通过new操作符,一种是通过对象字面量表示法。

基本数据类型

  • 值是不可变的

    var name = 'java';
    name.toUpperCase(); // 输出 'JAVA'
    console.log(name); // 输出  'java'
    

    由此可得,基本数据类型的值是不可改变的

  • 存放在栈区

    原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。

  • 值的比较

    var a = 1;
    var b = true;
    console.log(a == b);    // true
    console.log(a === b);   // false
    

    == : 只进行值的比较,会进行数据类型的转换。 === : 不仅进行值得比较,还要进行数据类型的比较。

引用数据类型

  • 值是可变的

    var a={age:20};
    a.age=21console.log(a.age)//21
    

    上面代码说明引用类型可以拥有属性和方法,并且是可以动态改变的。

  • 同时保存在栈内存和堆内存 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

  • 比较是引用的比较 当从一个变量向另一个变量赋引用类型的值时,同样也会将存储在变量中的对象的值复制一份放到为新变量分配的空间中。

    var a={age:20};
    var b=a;
    b.age=21;
    console.log(a.age==b.age)//true
    

    上面我们讲到基本类型和引用类型存储于内存的位置不同,引用类型存储在堆中的对象,与此同时,在栈中存储了指针,而这个指针指向正是堆中实体的起始位置。变量a初始化时,a指针指向对象{age:20}的地址,a赋值给b后,b又指向该对象{age:20}的地址,这两个变量指向了同一个对象。因此,改变其中任何一个变量,都会相互影响。

    此时,如果取消某一个变量对于原对象的引用,不会影响到另一个变量。

    var a={age:20};
    var b=a;
    a = 1;
    b // {age:20}
    

    上面代码中,a和b指向同一个对象,然后a的值变为1,这时不会对b产生影响,b还是指向原来的那个对象。

数据类型的检测方法

1. typeof

typeof null =='object'  // true 
typeof [] =='object' // true 
typeof {} =='object' // true 

typeof一般只能返回如下几个结果:number,boolean,string,function,object,undefined

在实际的项目应用中,typeof只有两个用途,就是检测一个元素是否为undefined,或者是否为function。

为何呢?我们可以使用typeof来获取一个变量是否存在,如if(typeof a!="undefined"){},而不要去使用if(a)因为如果a不存在(未声明)则会出错,对于Array,Null等特殊对象使用typeof一律返回object,这正是typeof的局限性。

Value Function typeof
"foo" String string
new String("foo") String object
1.2 Number number
new Number(1.2) Number object
true Boolean boolean
new Boolean(true) Boolean object
new Date() Date object
new Error() Error object
[1,2,3] Array object
new Array(1, 2, 3) Array object
new Function("") Function function
/abc/g RegExp object
new RegExp("meow") RegExp object
{} Object object
new Object() Object object

2. instanceof

所有可以通过构造函数创建的对象都可以用 instanceof 检查

instanceof 是用来判断 A 是否为 B 的实例对,表达式为:A instanceof B,如果A是B的实例,则返回true,否则返回false。

在这里需要特别注意的是:instanceof检测的是原型。当 A 的 proto 指向 B 的 prototype 时,就认为A就是B的实例。如:

var a=new Array();
alert(a instanceof Array);  // true
alert(a instanceof Object); // true

这是因为Array是object的子类。我们发现,虽然 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我们要多插入一个问题,就是function的arguments,我们大家也许都认为arguments是一个Array,但如果使用instaceof去测试会发现arguments不是一个Array对象,尽管看起来很像。

x instanceof Array

看上去不错?可惜这不是最佳答案!问题在于window.Array乃至[].constructor都不100%靠谱,因为检查的值可能来自另外一个 frame 也就是要算上执行环境的话,你怎么知道 Object.prototype.toString 没有被事先重载过?

公认的靠谱解法是

Object.prototype.toString.call(x) === '[object Array]'

还可以用ES6的Array.isArray()方法来判断一个值是否为数组。它可以弥补typeof运算符的不足。

var a = [1, 2, 3];

typeof a // "object"
Array.isArray(a) // true

如果是NodeJS还可以用util模块

var util = require("util");
var a = [];

console.log(util.isArray(a));

Node util 参考手册

instanceof的弊端

  • 对于基本数据类型来说,字面量方式创建出来的结果和实例方式创建的是有一定的区别的

    console.log(1 instanceof Number)//false
    console.log(new Number(1) instanceof Number)//true
    

    从严格意义上来讲,只有实例创建出来的结果才是标准的对象数据类型值,也是标准的Number这个类的一个实例;对于字面量方式创建出来的结果是基本的数据类型值,不是严谨的实例,但是由于JS的松散特点,导致了可以使用Number.prototype上提供的方法。

  • 只要在当前实例的原型链上,我们用其检测出来的结果都是true。在类的原型继承中,我们最后检测出来的结果未必准确。

    var arr = [1, 2, 3];
    console.log(arr instanceof Array) // true
    console.log(arr instanceof Object);  // true
    function fn(){}
    console.log(fn instanceof Function)// true
    console.log(fn instanceof Object)// true
    
  • 不能检测null 和 undefined

    对于特殊的数据类型null和undefined,他们的所属类是Null和Undefined,但是浏览器把这两个类保护起来了,不允许我们在外面访问使用。

3. 严格运算符===

只能用于判断null和undefined,因为这两种类型的值都是唯一的。

var a = null
typeof a // "object"
a === null // true

var b = undefined;
typeof b === "undefined" // true
b === undefined // true

4. constructor

constructor作用和instanceof非常相似。但constructor检测 Object与instanceof不一样,它可以处理基本数据类型的检测

var aa=[1,2];
console.log(aa.constructor===Array);//true
console.log(aa.constructor===RegExp);//false
console.log((1).constructor===Number);//true
var reg=/^$/;
console.log(reg.constructor===RegExp);//true
console.log(reg.constructor===Object);//false 

constructor 两大弊端:

  • null 和 undefined 是无效的对象,因此是不会有 constructor 存在的,这两种类型的数据需要通过其他方式来判断。

  • 函数的 constructor 是不稳定的,这个主要体现在把类的原型进行重写,在重写的过程中很有可能出现把之前的constructor给覆盖了,这样检测出来的结果就是不准确的

    function Fn(){}
    Fn.prototype = new Array()
    var f = new Fn
    console.log(f.constructor) //Array
    

5. Object.prototype.toString

toString是Object原型对象上的一个方法,该方法默认返回其调用者的具体类型,更严格的讲,是 toString运行时this指向的对象类型 , 返回的类型格式为[object,xxx],xxx是具体的数据类型,其中包括:String,Number,Boolean,Undefined,Null,Function,Date,Array,RegExp,Error,HTMLDocument,... 基本上所有对象的类型都可以通过这个方法获取到。

Object.prototype.toString.call('') ;   // [object String]
Object.prototype.toString.call(1) ;    // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
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的引用

需要注意的是,必须通过Object.prototype.toString.call来获取,而不能直接 new Date().toString(), 从原型链的角度讲,所有对象的原型链最终都指向了Object, 按照JS变量查找规则,其他对象应该也可以直接访问到Object的toString方法,而事实上,大部分的对象都实现了自身的toString方法,这样就可能会导致Object的toString被终止查找,因此要用call来强制执行Object的toString方法

参考链接