js中常见的用于类型判断的操作符或属性有:typeof、instanceof、prototype。下面我们就来看看他们是如何判断数据类型的。

一.js数据类型

ECMAscript中有5种简单数据类型(基本数据类型):UndefinedNullBooleanNumberString。除了这五种基本数据类型之外,还有一种复杂类型值(引用类型)。引用类型又分为:原生引用类型和自定义引用类型。在ECMAscript中,引用类型是一种数据结构,用于将数据和功能无序地组织在一起,以key/value的形式在对象中存放。ECMAscript不支持任何创建自定义类型的机制,而所有值最终都将是上述6种数据类型之一。另外,对象是某个特定引用类型的实例,例如:

var person = new Object()

这行代码创建了Object引用类型的一个新实例,然后把该实例保存在了person中。使用的构造函数是Object,它只为新对象定义了默认的属性和方法。Object只是ECMAscript提供的原生引用类型的一种。诸如Array、Date、RegExp、Function、Boolean、Number、String也都是内置的原生引用类型。

二.typeof

基本类型的检验,经常使用typeof操作符。对一个值使用typeof操作符可能返回下列某个字符串:

"undefined"———如果这个值未定义
"boolean"———如果这个值是布尔值
"string"———如果这个值是字符串
"number"———如果这个值是数值
"object"———如果这个值是对象或null
"function"———如果这个值是函数

调用typeof null会返回"object",因为特殊值null被认为是一个空的对象的引用。

Safari及之前版本、Chrome7及之前版本在对正则表达式调用typeof操作符时会返回"function",而其它浏览器在这种情况下会返回"object"。

从技术角度讲,函数在ECMAscript中是对象,不是一种数据类型。然而,函数也确实有一些特殊的属性,因此通过typeof操作符来区分函数和其它对象是有必要的。

三.instanceof

在 JavaScript 中,判断一个变量的类型尝尝会用 typeof 运算符,在使用 typeof 运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回 "object"。ECMAScript 引入了另一个 Java 运算符 instanceof 来解决这个问题。instanceof 运算符与 typeof 运算符相似,用于识别正在处理的对象的类型。通常来讲,使用 instanceof 就是判断一个实例是否属于某种类型。例如:

// 判断 boy是否是 People类的实例
function People(){} 
var boy= new People(); 
console.log(boy instanceof People)//true

与 typeof 方法不同的是,instanceof 方法要求开发者明确地确认对象为某特定类型。例如:

var arr = [1,2,3]
if(arr instanceof Array){
    console.log(“arr是一个数组”)
}
//输出:arr是一个数组

这段代码问的是“变量 arr是否为 Array对象的实例?”arr的确是 Array对象的实例,因此结果是"true"。尽管不像 typeof 方法那样灵活,但是在 typeof 方法返回 "object" 的情况下,instanceof 方法还是很有用的。
但是,instanceof操作符并不是安全的检测方式。instanceof操作符的问题在于,它假定只有一个全局执行环境。如果网页中包含多个框架frame,那实际上就存在多个不同的全局执行环境,从而存在多个不同版本的Array构造函数。如果你从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。为了解决这个问题,ES5中新增了Array.isArray()方法。这个方法的目的是最终确定某个值到底是不是数组,而不管它是在哪个全局执行环境中创建的。这个方法是用如下:

if(Array.isArray(arr)){
    //do sometion
}

目前,支持Array.isArray()方法的浏览器有IE9+、Firefox 4+、Safari 5+、Opera 10.5+、Chrome。要在尚未实现这个方法的浏览器中准确检测数组,请参考“四.安全的类型检测”。

instanceof究竟是运算什么的?

我曾经简单理解instanceof只是检测一个对象是否是另个对象new出来的实例(例如var a = new Object(),a instanceof Object返回true),但实际instanceof的运算规则上比这个更复杂。

//假设instanceof运算符左边是L,右边是R
L instanceof R 
//instanceof运算时,通过判断L的原型链上是否存在R.prototype
L.__proto__.__proto__ ..... === R.prototype ?
//如果存在返回true 否则返回false

注意:instanceof运算时会递归查找L的原型链,即L.__proto__.__proto__.__proto__.__proto__...直到找到了或者找到顶层为止。

所以一句话理解instanceof的运算规则为:

instanceof检测左侧的__proto__原型链上,是否存在右侧的prototype原型。

知道了这个也就知道为什么以下这些奇怪的表达式为什么会得到相应的值了

 Function instanceof Object // true 
 Object instanceof Function // true 
 Function instanceof Function //true
 Object instanceof Object // true
 Number instanceof Number //false
 

那么知道了原理之后,如何自己手写一个instanceof,如下所示:

function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
 var O = R.prototype;// 取 R 的显示原型
 L = L.__proto__;// 取 L 的隐式原型
 while (true) { 
   if (L === null) {
     return false; 
   }
   if (O === L) {// 这里重点:当 O 严格等于 L 时,返回 true 
     return true;
   } 
   L = L.__proto__; 
 } 
}
instance_of(Function,Function)//true
instance_of(String,String)//true

JavaScript instanceof 运算符深入剖析https://www.ibm.com/developer...

四.安全的类型检测

在检测某个对象到底是原生对象还是开发人员自定义的对象的时候,也会有问题。出现这个问题的原因是浏览器开始原生支持JSON对象了。因为很多人一直在使用Douglas的JSON库,而该库定义了一个全局的JSON对象。于是开发人员很难确定页面中的JSON对象到底是不是原生的。
解决typeof、instanceof和判断是否是原生JSON对象的方法都一样。大家都知道,在任何值上调用Object原生的toString()方法,都会返回一个[object NativeConstructorName]格式的字符串。每个类在内部都有一个[[Class]]属性,这个属性中就指定了上述字符串中的构造函数。举个栗子:

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

由于原生数组的构造函数名与全局作用域无关,因此使用toString()就能保证返回一致的值。利用这一点,可以创建如下函数:

function isArray(value){
    return Object.prototype.toString.call(value) == "[object Array]"
}

同样,也可以基于这一思路来测试某个值是不是原生函数或正则表达式:

function isFunction(value){
    return Object.prototype.toString.call(value) == "[object Function]"
}

function isRegExp(value){
    return Object.prototype.toString.call(value) == "[object RegExp]"
}

clipboard.png

Object的toString()方法不能检测非原生构造函数的构造函数名,因此,开发人员定义的任何构造函数都将返回"[object Object]"。这也就是为什么在上图中的序号③处调用test(p)会返回"[object Object]"的原因了。


CodeMan
1.4k 声望58 粉丝

《伤害》