henry-fun / blog

My world
4 stars 0 forks source link

记一次关于js数组类型判断及js类型判断的细节探索 #3

Open henry-fun opened 6 years ago

henry-fun commented 6 years ago

一、前言

众所周知,js是门“动态”、“弱类型”编程语言,这意味着在js中可以任性定义变量,同时,“任性”也意味着需常在项目开发中对变量做类型判断,曾几何时,对数组变量的类型判断是件很痛苦的事情,开发人员想出多种方案来对数组做出准确的类型判断,但效果不佳,直到ES5标准“入主中原”,判断数组类型有了标准的isArray()官方利剑,才降伏了数组类型判断这条恶龙,世间得一清,但在此之前开发者是如何判断数组类型的?判断数组类型为何会如此玄学?为何要判断数组类型?带着这些疑问,吾跋山涉水,探寻各方资料,整理消化后遂成此文,以记之。

二、判断js数组类型为何麻烦?

1、语言本身的“缺陷”

js是门“动态”“弱类型”编程语言,这意味着js在定义和使用变量时可以“任性”,在ES6之前,我们定义变量一般使用“var”来定义:

var name = 'jack';
name = 20;
name = ['aa'];

在上述例子中,name变量初始定义为字符串类型,而后变为数字类型,最后摇身一变成为数组类型,这种任性摇摆的特性就是其“动态”特性,在java中我们定义一个字符串变量须如此定义:String name = 'jack',java通过一个String前缀“显式的”、“强制的”指定name变量为字符串类型,之后不得对该name变量进行类型变换(如果执行name = 22将会报type类型转换错误),但js采用的是弱类型定义方案,在定义变量时使用var声明了一个变量,弱化了类型前缀的限制,并没强制锁死变量类型,之后可以随意更改其类型。动态弱类型这种声明变量的方案用起来可以随性而为,无须顾虑太多,随性的代码书写如若不加管制必将招致灾难性的代码bug。

2、js类型判断的“不足”

其实动态弱类型的语言特性并不是决定js判断数组类型麻烦的必然原因,js语言因为历史原因,其创造者在开发之初将其定位为简单的网页小助手语言,为了轻巧、快速的完成小任务开发选择了“动态弱类型”的语言方案,PHP亦为动态弱类型语言,但在处理类型判断时,PHP用一个gettype()方法可以轻松、精准的搞定(PHP作为世界上世界上最好的语言还是有、东西的),PHP有gettype()这枚银弹,js有吗,嗯,算有吧,js最常用的是用typeof操作符来获取数据类型,看typeof这个名字是不是感觉很厉害?感觉会跟PHP一样轻松简单?但随后你会发现:typeof操作符是个很局限的类型获取方案,用它对基本数据类型做判断还算过得去,但在涉及到引用类型判断这种细活时就显得很low了(本以为是个王者,没想到是个青铜)。

三、判断js数组类型的几个“方案”

1、typeof操作符方案(Pass)

前言:typeof操作符本应是解决js类型判断的合适方案,奈何负了众望。

众所周知,js分两大数据类型:基本数据类型和引用数据类型,typeof操作符可以对两大数据类型做出基本的判断,我本以为typeof对基本数据类型做判断是可以的,但后来发现其实是有问题的,比如用typeof判断基本数据类型null:typeof null结果就是“object”(关于typeof null为object的问题属历史遗留问题,可查阅相关资料了解详细原因),typeof在判断基础数据类型时尚有问题,更别说用来判断子孙繁多的引用类型了,typeof在判断引用类型时一刀切的统统返回object

var obj = {};
var arr = [];
var map = new Map();

typeof obj; // object
typeof arr; // object
typeof map; //object

在上面的例子中可以看到typeof在判断{}时返回object'、[]亦返回object`,数组和对象的判断结果根本没区别!所以用typeof来判断数组类型的判断,pass!

2、instanceof运算符方案(存在缺陷)

instanceof是js用来判断继承关系的运算符(js基于原型链实现继承,故instanceof判断的就是对应的类是否存在于变量的原型链上),根据这个特性可以如此来判断数组类型:

['a'] instanceof Array; // true

打印数组['a']可以看到如下结果:

从打印的结果可以看到Array存在于数组['a']的原型链上,故['a'] instanceof Array === true;,利用instanceof的这个特性可以判断数组类型,但是instanceof运算符有个弊端,就是如果跨越frame会存在问题:

var iframe = document.createElement('iframe');
document.body.append(iframe);
var FrameArray = window.frames[window.frames.length-1].Array;
var array = new FrameArray();
console.log(array instanceof Array); // false

跨越frame导致的判断失误属于意料之中,也很好理解,前面说过instanceof运算符是用来判断继承关系的(判断是否存在血统连接关系),原型链好比家族里面的派系链,不同的frame相当于不同的家族,在同一个家族中同一派系上的族人存在着链接关系,但如若家族不同(frame不同),则派系链则更不可能相同了。所以利用instanceof运算符判断数组类型的方案,pass!

3、Object.prototype.toString()方案

instanceof运算符属于血统继承性判断,这种判断是基于实物的纽带性判断,在早前判断数组类型时很多类库时采用该方案(如早期的jquery),但不同frame导致instanceof出现的局限性不得不让开发者放弃该方案,转而寻求更合理的方案,这时候开发者想到既然这种血统继承性判断有弊端,那有没有含蓄而深入点的方案呢?嗯,有请 Object.prototype.toString()方法:

console.log(Object.prototype.toString.call([])); // "[object Array]"

该方案能获取到变量的“类目名”,在js中万物皆为对象,万物皆有“类目名”,每个变量、对象、数组等都有一个唯一的类目名(这个类目名类似于人类给各类动植物起的“学名”),该方案通过获取目标变量的类目名([object Array])进行判断,如果类目名一致则证明目标变量为数组类型:

var a = [];
Object.prototype.toString.call(a) === "[object Array]"; // true

关于“Object.prototype.toString()”这个方案,在这里我来好好叨叨: