谈谈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这样一门集众家之长的语言,以便写出更符合该语言特性的程序,这是值得思考的。我认为,在学习过程中,和其他语言进行比较学习,是一个很好的办法。
好了,就到这里吧,希望你有所收获,再会。。。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容