谈谈JavaScript中的类型判断

数据为什么需要类型?

在我们学习JavaScript的过程中,最初接触的基础概念之一便是:JavaScript是一门弱类型语言。自然地,在之后的学习过程中,我们会了解到JavaScript中有关弱类型的具体细节。比如,在变量声明的时候,统一用var声明所有变量。比如,同一个变量可以存取各种类型的数据。比如,同一个数组可以存放不同类型的元素。但是仅仅了解这些是不够的,语言的细节就好比交通规则,你遵守了交通规则只能说明你是一个守法公民。或许你还可以更优秀一点,比如更深入地去解读这些规则,从而在语言设计层面对该语言特性有更透彻的理解。关于程序最流行的说法就是:程序就是数据结构和算法。如果站在CPU的角度,还有更简单的定义:程序就是数据和指令。程序运行的过程就是指令对数据进行运算和存取的过程。那么,问题在于,数据类型在这其中扮演了什么角色?
当我们给数值做加法时,会得到两数之和。当我们给字符串做加法时,它们会合并成一个长字符串。也就是说,在程序运行过程中,数据类型规定了程序用何种指令对数据进行何种操作。因此,我们不难得出结论,数据类型是高级语言对底层CPU指令的一种封装,它避免了指令操作的复杂性。以加法操作为例,如果我们以CPU的思维方式去编程,当我们遇到数字时需要调用加法指令,当我们遇到字符时需要调用连接指令,我们的程序将非常复杂。当我们有了数据类型后,不管是数字还是字符串,就都可以用同一个操作符了,我们不需要记那么多具体指令。具体的指令转换就交给编译器去做了。数据类型使编程变得更简单,更高效。

强类型,弱类型,无类型

在讨论强类型,弱类型,无类型之前,我们也需要了解一些背景知识。我们的计算机(CPU)是不认识数据类型的,数据类型是人们对各种复杂运算进行分类和分组,以方便人们更快速阅读和编写代码,它是高级语言对机器语言的一种抽象。CPU不需要知道你给它的操作数是数字还是字符,它需要编译器告诉它执行什么指令,是加法指令还是字符连接指令。编译器的一项职责就是将程序语言中的针对不同类型的数据运算解释成机器指令,从而简化开发工作。在高级语言中,强类型指的是在变量定义时就必须指定数据类型,以C语言代码为例:

int i, j;

上面的代码虽然没有对变量赋值,但是在运行时计算机仍然分配的4字节的空间给变量。我们再看JavaScript代码:

var i, j;

var关键字仅仅告诉解释器,接下来的程序中可能会用到i,j变量。如果接下来有对变量赋值,则在赋值操作的时候分配内存空间。如果接下来没有对变量赋值,则该变量声明和没有声明几乎是一样的。
强类型语言在编译时即做类型检查,变量一旦定义,内存空间就已经固定,这就是我们说的数据精度,即使是在之后的代码中没有对变量进行赋值,它也占据固定空间。弱类型语言在编译时不做类型检查,它根据运行时的赋值操作确定数据类型。很自然的,数据精度也是运行时确定的。因此,我们JavaScript中几乎没有听过数据精度的概念,因为这些都是解释器在运行时才会产生的。在C语言中,有一种动态内存分配机制,就和JavaScript的弱类型机制有点相似。以下面代码为例:

void* i, j;
i = malloc(sizeof(int));
int* k = (int*)i;
*k = 1;

上述代码中的void*就很像JavaScript中的var,表示一个无类型的指针引用。这之后,分配了一个4字节内存给变量,然后进行了类型转换和赋值。实际上,上述代码等价于JavaScript中:

var i, j;
i = 1;

在JavaScript中,简单的赋值操作相当于C语言中的内存分配,类型转换(确认精度),赋值三步操作。不过,这些操作都是由解释器执行,对开发者不可见。弱类型语言似乎在坚持一种理念,即所有的内存都是动态分配,并且所有动态内存都是由系统根据数据类型分配,不能像C语言一样在代码中分配指定大小的内存。
无类型语言一般是指标记语言,这种语言功能很有限,甚至连数据和指令都不做区分,比如html,正则表达式,xml这一类的。

JavaScript中的typeof

通过上面的分析,你应该明白,在强类型语言中,变量一旦定义,对它进行typeof运算就会返回一个固定值。而在JavaScript中却不一样,一个变量的typeof值是根据运行时决定的,也就是说它可能是数值,也可能是字符串,也可能一会儿是数值一会儿是字符串,这就是动态类型的特点。尽管JavaScript的类型在运行时可以动态变化,但系统提供的类型是有限的几种,程序再怎么变化,都不会超出它的范围。它们是:

Null,
Undefined,
String,
Number,
Boolean,
Object

上述类型是ES标准给出的基本数据类型,但在具体实践中,却和标准有些出入。我们用typeof对各种数据进行运算,可以得以下数据类型:

Undefined,
String,
Number,
Boolean,
Function,
Object

和标准规定的区别是,typeof将Null处理为Object类型。另外,typeof将函数视为Function类型,尽管从理论上来说,Function也是Object类型的。在实际开发中,我个人倾向将第二种情况视为JavaScript的基本数据类型参照,因为标准规范毕竟只是书面的东西,它除了干扰你的判断,实际也没什么用。和其他面向对象语言一样,JavaScript也分为值类型和引用类型,typeof在对值类型数据进行类型判断的时候还能游刃有余,但一遇到引用类型的时候就捉襟见肘了。typeof把所有的引用类型都视为Object,要想进行更细微的判断,typeof显然是一脸懵逼。

class属性

所有的引用类型都有一个class属性,它表示该对象的类型。这似乎能解决引用类型的类型判断问题,但令人不解的是,这个class属性只存在于想象中。你无法设置它,甚至无法直接获取它的值,你仅能通过Object.prototype.toString间接地获取它。以下面的代码为例:

function classof(o)
{
    return Object.prototype.toString.call(o).slice(8, -1);
}

console.log(classof(new RegExp('')));//RegExp
console.log(classof(new Date()));//Date
console.log(classof([]));//Array
console.log(classof({}));//Object
console.log(classof(function f(){}));//Function
console.log(classof(undefined));//Undefined
console.log(classof(null));//Null
console.log(classof(''));//String
console.log(classof(false));//Boolean
console.log(classof(0));//Number
console.log(classof(NaN));//Number
console.log(classof(Infinity));//Number
console.log(classof(new Fun()));//Object

和typeof相比,class属性将Null值按照标准规范处理为单独一个类型,Null本身为该类型的唯一值。另外,对于引用类型的处理,class属性比typeof更进了一步,例如typeof会将RegExp对象处理为Object类型,而class属性会返回RegExp类型,class属性对于所有的内置对象类型都有准确的类型判断,这是typeof做不到的。但是,class属性也有它的问题,就是在对自定义对象的类型判断,它仅能将所有的自定义对象都识别为Object类型,我们当然希望它能准确地反映对象的原型链继承关系,但是很遗憾,它做不到。对于自定义对象,系统提供instanceof和Object.isPrototypeOf来检测对象地构造函数和原型链。

instanceof和Object.isPrototypeOf

关于它们的使用,非常简单,以代码为例:

function Fun(){};
var f = new Fun();
console.log(f intanceof Fun);//true
console.log(f instanceof Object);//true
console.log(Object.prototype.isPrototypeOf(f));//true

实际上,在JavaScript的自定义对象中,没有其他类型,只有Object类型。原型链所实现的继承关系很容易使人造成困扰,我们很容易将它类比于静态类型语言中父类和子类的继承关系,但我们仔细分析,这是完全不一样的。JavaScript中的原型链是一种运行时构建的动态树状列表,由解释器模拟了一套由子到父逐级查找的访问操作。这和静态类型语言中子父级继承是完全不一样的概念。在静态语言中,new Fun()得到的对象类型就是Fun类型。如果我们一定要模拟这种操作,不妨这样写:

 //class
function classof(o)
{
    if(o === null)return 'Null';
    if(typeof(o) != 'object')
        return Object.prototype.toString.call(o).slice(8, -1);
    else
        return o.constructor.name;
}

但是,JavaScript有它自己的设计理念和类型系统,并不鼓励照搬别的语言的编程模式。比如,用JavaScript原型链模拟java的对象继承,怎么写都觉得别扭。问题在于,我们该如何更好地理解一门编程语言地语言特色,尤其是像JavaScript这样一门集众家之长的语言,以便写出更符合该语言特性的程序,这是值得思考的。我认为,在学习过程中,和其他语言进行比较学习,是一个很好的办法。
好了,就到这里吧,希望你有所收获,再会。。。

你可能感兴趣的:(谈谈JavaScript中的类型判断)