开发过程中,我们经常要判断一个变量的类型,那你知道js中有多少方式判断变量类型吗?
1. typeof
typeof
用于基本类型的判断,js有 7 中基本类型,他们分别是:
string
、number
、boolean
、undefined
、null
、symbol
、bigint
。
它们使用typeof
判断的结果如下:
typeof 'hello'; // string
typeof 123; // number
typeof true; // boolean
typeof undefined; // undefined
typeof null; // object
typeof Symbol(); // symbol
typeof Bigint(1); // bigint
这里有个特例,其他的基本类型使用typeof
都正确的返回了类型,只有null
返回的是object
。
这是一个错误的结果,但是为了兼容性,js保留了这一现象。
当typeof
用于引用类型时,结果永远返回object
。
typeof new Number(1); // object
typeof new Date(); // object
如果想要的判断一个引用类型的变量类型,就需要使用instanceof
。
2. instanceof
instanceof
用于引用类型的判断,比如:
new Number(1) instanceof Number; // true
new Boolean(true) instanceof Boolean; // true;
new Date() instanceof Date; // true
/\d/ instanceof RegExp; // true
({}) instanceof Object; // true
[] instanceof Array; // true
instanceof
的原理是原型链,手动实现instanceof
的代码可能像下面这样:
function instanceOf(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__;
}
}
因为原型可以被修改,所以instanceof
判断并不一定可靠。
let obj = {};
Object.setPrototypeOf(obj, Array.prototype);
obj instanceof Array; // true
上面的代码使用Object.setPrototypeOf
把对象的原型修改成了数组的原型,就产生了对象是Array
实例的奇怪现象。
3. Array.isArray
前面提到instanceof
可能因为对象原型被修改而产生奇怪的现象,对于数组类型的判断,要紧的做法是使用Array.isArray
。
Array.isArray({__proto__: Array.prototype}); // false
因为Array.isArray
不依赖原型链,因此能避免原型被修改引起的误判。
Array.isArray
还能检测iframes:
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
xArray = window.frames[window.frames.length-1].Array;
var arr = new xArray(1,2,3); // [1,2,3]
// Correctly checking for Array
Array.isArray(arr); // true
// Considered harmful, because doesn't work though iframes
arr instanceof Array; // false
你可能会疑问,为什么不同的iframe中instanceof
会失效。因为frames其实是window
的实例,而所有 JavaScript 全局对象、函数以及变量均自动成为 window
对象的成员。
Array
其实也是window
的成员:
window.Array === Array; // true
那么你可能会意识到,如果有两个window
对象,它们的Array
属性相同吗?答案是不相同。上面的代码改造一下就可以验证:
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
xArray = window.frames[window.frames.length-1].Array;
xArray === Array; // false
xArray.__proto__ === Array.prototype; // false
Array
不同,原型也不同,当然instanceof
的结果就是false
了。
用一句比喻,就是你不是平行世界中那个老爸生的,所以你不是他的儿子。
instanceof
也不可靠,什么可靠?
4.Object.prototype.toString.call
先来看看这句话的含义,就是用obj
作为this
来调用Object.prototype.toString
方法。
这里你要问了,对象不都是自动从Object.prototype
继承toString
,不能直接调用obj.toString
吗?
这就是关键,因为toString
可能被子类重写。
'hello'.toString(); // Hello
new Date().toString(); // Mon Mar 14 2022 16:52:58 GMT+0800 (中国标准时间)
new RegExp(/\d/).toString(); // /\d/
上面这几种类型都重写了toString
,想要调用Object.prototype.toString
,就要用到call
方法(或者apply
)。
这就是我们的目的,因为Object.prototype.toString
不像String.prototype.toString
、Date.prototype.toString
会对变量进行格式化输出,而是统一的输出'[object 类type…]'
,type就是值的类型,我们正好借助此特性来判断变量类型。
Object.prototype.toString.call(undefined); // [object Undefined]
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call('hello'); // [object String]
Object.prototype.toString.call(true); // [object Boolean]
Object.prototype.toString.call(123); // [object Number]
Object.prototype.toString.call(Symbol()); // [object Symbol]
Object.prototype.toString.call(BigInt(1)); // [object BigInt]
Object.prototype.toString.call(new RegExp(/\d/)); // [object RegExp]
Object.prototype.toString.call(new Date()); // [object Date]
代替instanceof
:
Object.prototype.toString.call(new Date()) === '[object Date]'; // true
可以看到,这种方式基本类型和引用类型都能用,如果你好奇它的原理,可以看我的另一篇文章。