JS 对 基本数据类型 和 引用类型 的判断
啰啰嗦嗦写了很多,把原本几句话就可以交待的东西扯得那么长。。
主要是因为我曾经对这个问题感到困惑和混乱时,当时十分希望能找到让我知其然并知其所以然的讲解。
当然,我不奢求这篇文章能给人以启示,只要能有一点帮助就好。
我把自己对js的理解写出来,但限于自己的知识和文章篇幅,我无法讲得多么细致,甚至不能保证正确性,大家可以批判着看。
着急找答案的朋友可以直接翻到后面看总结。。
------------------------------------------------------------------------------------------------------------
对基本类型的检测:
JS有 6 种基本类型的数据:
Undefined、Null、Boolean、Number、String,和相对复杂的 Object。
其中,Undefined 和 Null 比较特殊:都只有一个值,都表示“无”,但前者表示未定义,后者表示空指针(对象)。
如果用 typeof 操作符对 6 种类型的值进行检测:
typeof undefined; // 得到 "undefined" typeof null; // 得到 "object" typeof false; // 得到 "boolean" typeof (1); // 得到 "number" typeof "str"; // 得到 "string" typeof {}; // 得到 "object"
就会发现 typeof null 得到的不是 “null” 而是 “object”。
从技术上说 null 属于 object,经由它赋值的变量会包含一个对象指针。然而与普通对象指针不同的是:这个指针不指向任何对象,它既存在又不存在。
js 用 null 来描述一个“还没存在或即将不存在的对象”。因为 null 在 object 类型中的特殊性,ecma 将它独立于普通的 Object 类型之外独自成为一个新的类型,又因为它与 Object 的本质关联,它的 typeof 结果仍是 "object"。
说到特殊性,函数(Function)也是具有特殊性的一个对象。
它属于对象,但又不局限于对象,甚至高于对象。函数用来描述“行为”,既可以是专属于某个对象的行为,又可以泛指整个程序的行为,它超脱 Object 之外存在。它有自己的特殊属性和应用方式,也因此跟其他对象不同。
null 再特殊充其量也只是个特殊的对象,因此 typeof null 仍然等于 "object";而函数因为其特殊性已经超脱于 Object,因此
typeof (function(){}); // 得到 "function"
函数拥有属于自己的特殊标识 "function",而不是 "object".
到此,typeof 出现了 6 个不同的返回值,但这 6 个返回值并不与 6 个基本类型一一对应。
其中 null 和 非函数类型对象 都返回 "object",在可能产生混淆的地方我们需要这么判断:if ( typeof of obj === "object && obj) { /* obj 指向一个 非null 对象 */ }
那 typeof 能不能产生更多的结果,或进行更多更细致的判断呢? 比如告诉我 [] 是一个 "array", /regExp/ 是个 "regexp"?
对引用类型的判断:
我们都知道 js 属于面向对象的语言,那对象的封装和使用当然必不可少。
对于具体的对象,如果仍旧使用 typeof:
typeof new Array(); // 得到 "object",而不是 "array" typeof new Date(); // 得到 "object",而不是 "date"。
那么结果可能跟你所意料的不一样,但并不能就此而简单地说结果是错的——数组和正则确实是对象,这没有错。
之所以产生这样的结果,(我个人以为)是因为 typeof 只负责从大方向上告诉你这是什么类型,于是面对一个对象的时候它只会告诉你这是一个对象而不管这个对象叫什么名字——没错,"array" 和 "date" 就只是一个名字,他们也可以是 "brray" 和 "cate", js并不限制也限制不了对象叫什么(尤其是自定义对象),甚至它无法限制对象是否要有名字(如匿名函数)。
它只确定能确定的,对不确定的要么不给,要么给出一个虽然笼统但仍能确保正确的答案。这种设计是合理的,正确性比准确性更重要。
因此,typeof 操作符的功能被限制在“检测基本类型”上,对引用类型的判断它是无能为力的。
那对于具体对象,又要怎么判断其类型呢?
用 instanceof 操作符:
var arr = new Array(); // 内置引用类型 arr instanceof Array; // 得到 true function Sup() {} // 自定义引用类型 var s1 = new Sup(); s1 instanceof Sup(); // 得到 true
或者用对象的 constructor 属性
var arr = new Array(); // 内置引用类型 arr.constructor === Array; // 得到 true function Sup() {} // 自定义引用类型 var s1 = new Sup(); s1.constructor === Sup; // 对于自定义对象,也能返回正确结果 true
两种方法看起来都不错,但都存在弊端:
instanceof 用以检测“一个对象是否某个原型对象的实例”,理想情况(单框架)下, var arr = new Array(), arr 自然是Array的实例,检测结果当然准确。但具体应用中,如果使用多个框架(使用frame),浏览器中就会存在多个 window(Global)对象,每个 window 又都有自己的 Array 等一套构造函数,它们一一对应,却并不相等。解释起来很拗口,其实道理很简单:在中国你说家在首都,谁都知道指的是北京。在美国如果你也说家在首都,别人又会理所当然地觉得你在讲华盛顿。每个国家(框架、执行环境)都有自己的“首都(原型对象)”,但这个“首都”却指向不同的位置(对象)。“首都”因所属国家的不同而不同,Array因所属的window不同而不同。使用 instanceof 的时候要确保“同一框架”这个前提条件。
而 constructor 的问题更大(只要可能,都建议使用 instanceof),它返回一个“当前对象的构造函数”。不仅存在和 instanceof 一样的跨框架问题,还可能因为实现对象继承时的疏忽导致错误结果:
function Super() {} function Sub() {} Sub.prototype = new Super(); // Sub.prototype.constructor = Sub; // 除非在这里进行指定,否则下一句代码结果为 false。 new Sub().constructor === Sub;
解决跨框架方法并不是没有,但都不完美:
首先,js对Array类型提供了一个Array.isArray()方法,但需要 ie9+、firefox4+、safari5+、 chrome 或 opera10.5+ 的版本支持,适用性不强。
此外,在 ie之外 的浏览器上使用 函数.name 也是个不错的方法,但由于web开发往往撇不掉ie,在此也不推荐使用:
arr.constructor.name === "Array";
最后,书上还提供了一个跨框架的方法,Object.prototype.toString.call():
var type = Object.prototype.toString.call(arr); // type 得到 "[object Array]"
var result = type.indexOf("Array") >= 0; // 判断 type 中是否包含 "Array",result 可跨框架为 true。
不过这方法也有弊端。。。(擦,毛病真多)
在面对 自定义对象 时,它总是都返回 "[object Object]",这点让我们无法对自定义对象进行准确判断。仍然能起作用的 instanceof 操作符 和 constructor 属性了,但 constructor 的弊端在自定义对象上是最容易显现的。到最后,最佳选择只有 instanceof。所幸跨框架的问题并非不可避免,只要在使用的时候多加注意、考虑周全就行。
总结:
1,对于 6 大基本类型和函数引用类型的判断:使用 typeof 操作符可以胜任,不存在跨框架的问题。
2,对js内置的引用类型的判断:使用 Object.prototype.toString.call() 方法可以避免跨框架的问题。
3,对于自定义类型,就只能用 instanceof 了,需要多加考虑跨框架的问题。

浙公网安备 33010602011771号