typeof 与 instanceof 做类型检测的异同
在 JavaScript 中进行数据类型检测常会用到 typeof
关键字和 instanceof
关键字,虽然上述两个关键字都可以进行类型校验,但实质上 instanceof
本质并不是检测类型,而是检测原型链,所以本文主要整理 typeof
与 instanceof
的区别,以及如何进行更好的数据类型检测。
typeof类型检测
使用 typeof
进行类型检测时,可能的返回结果有:
- undefined
- number
- string
- object
- boolean
- function
例如:
typeof 1 // number
typeof '1' // string
typeof true // boolean
typeof a // undefined
typeof window.alert // function
typeof window // object
typeof {} // object
typeof [] // object
typeof null // object
由上可以看到,在对数组进行类型检测是返回的是 object ,这并不符合预期的目标。出现以上类型检测的问题,一部分是由于历史原因,另一部分是由于类型的字节码所决定的。
在 JavaScript 中,数据类型可以大体分为 基础类型 和 引用类型 两大类,一般我们常用到的 字符串、数字 等都属于基础类型,基础类型的变量中存放的是变量的值。而 数组、对象、函数等都是引用类型,这些类型的数据变量中存放的不再是数据的值,而是数据的指针。
关于基础类型与引用类型的理解,如果有 C/C++ 语言开发基础则非常好理解。基础类型就相当于普通变量,普通变量在内存中的字节区间可以完全存放这个变量的值,所以直接存变量值即可;引用类型相当于指针,在普通变量存不下某个值的情况下,可以通过指针变量来存放变量的指针,已达到指示目标作用。
言归正传,在 JavaScript 中使用 typeof
检测数据类型时实际上检测的是数据类型的字节码,由于 对象、数组 以及 null 的末尾字节码相同,所以便得出了相同的检测结果。
instanceof 原型检测
与 typeof
不同,instanceof
并不检测字节码,而是进行链式检测。语法为:
A instanceof B
如果 A 是 B 的对象,则返回 true,否则返回 false。
这里的 A 和 B 不是直接进行类型检测,而是进行原型链检测,即表示:A 是否在 B 的原型链上。
由于 JavaScript 没有标注的类的概念,所以在 JavaScript 中实现继承是通过原型和原型链继承来实现的,而 类 作为一种自定义的类型,其实例化的对象则可以通过 instanceof
关键字来检测。
使用 instanceof
进行类型检测只会返回 true 或者 false,如:
function ClassA() {}
var objA = new ClassA();
console.log(objA instanceof ClassA); // true
console.log(ClassA instanceof Function); // true
console.log(Function instanceof Object); // true
console.log(objA instanceof Object); // true
同理:
var arr = [1, 2, 3];
console.log(arr instanceof Array); // true
console.log(Array instanceof Object); // true
console.log(arr instanceof Object); // true
所以,使用 instanceof
可以检测一个对象是否在另一个对象的原型链上,或者说,对象 A 是否与 B 存在继承关系。
更好的类型检测
虽然 typeof
与 instanceof
各有利弊,但在开发中这两个关键字依然不能完全满足我们的需要,我们希望对一个数组进行类型检测时可以返回是数组类型,而不是返回 object 。
可以使用Object.propotype.toString.call
方法来对类型进行字符串转化,即可得到符合预期的结果。
如:
Object.prototype.toString.call(1); // [object Number]
Object.prototype.toString.call('1'); // [object String]
Object.prototype.toString.call(true); // [object Boolean]
Object.prototype.toString.call([]); // [object Array]
Object.prototype.toString.call({}); // [object Object]
Object.prototype.toString.call(window.alert); // [object Function]
Object.prototype.toString.call(null); // [object NUll]
Object.prototype.toString.call(undefined); // [object Undefined]
使用 Object.prototype.toString.call
便可以得到更符合预期的结果。可以将该检测封装成一个函数:
function testType(data) {
let res = Object.prototype.toString.call(data).toLowerCase();
let type = Array.prototype.slice.call(res).slice(8, -1).join('');
return type;
}
console.log(testType(1)); // number
console.log(testType('1')); // string
console.log(testType(true)); // boolean
console.log(testType(alert)); // function
console.log(testType([])); // array
console.log(testType({})); // object
console.log(testType(null)); // null
console.log(testType(undefined)); // undefined
引申:基础类型检测
很多时候在项目的开发中,我们需要知道某个数据的类型是否为原始类型,即基础类型,包括(number,string,boolean),所以,使用以上函数,可以再额外封装出两个函数,来进行原始类型和引用类型的检测。
如下:
// 是否为基础类型
function isPrimitiveType(data) {
let type = testType(data);
return ['string', 'number', 'boolean'].indexOf(type) > -1 ? true : false;
}
// 是否为引用类型
function isReferenceType(data) {
let type = testType(data);
return ['function', 'object', 'array'].indexOf(type) > -1 ? true : false;
}