Appearance
判断类型常用方法typeof、instanceof、Object.prototype.toString、constructor
使用typeof 区分数据类型
typeof 是一个操作符,不是方法因此使用的时候可以加括号,也可以省略
使用 typeof 运算符来进行检查,8 种基本数据类型,分别是 undefined、boolean、number、string、object、bigint 和 symbol。要注意的是typeof 运算符将 null 值归类为对象类型,并且可以检测是否是function(也是一种特殊的对象)
js
var num = 10;
var str = "小白";
var flag = true;
var nll = null;
var undef;
var obj = new Object();
var a = function(){}
//是使用typeof 获取变量的类型
console.log(typeof num); // number
console.log(typeof str); // string
console.log(typeof flag); // boolean
console.log(typeof nll); // object
console.log(typeof undef); // undefined
console.log(typeof obj); // object
console.log(typeof a); // function
// 这个小案例
typeof object // undefined 未定义 他就是一个变量名
typeof Object // function 他是一个构造函数
/ 除 Function 外的所有构造函数的类型都是 'object'
var str = new String('String');
var num = new Number(100);
typeof str; // 返回 'object'
typeof num; // 返回 'object'
var func = new Function();
typeof func; // 返回 'function'
- typeof检测未被声明的变量,不会报错,结果是 'undefined'
js
// 报错 因为a变量没有声明
if (!a) {
console.log(a)
}
// typeof 即使对没声明的变量也不会报错
if (typeof a !== 'undefined') {
console.log(a)
}
利用这个判断特性
针对这种特性在封装第三方包的时候就可以使用,在node 环境中是没有window 变量,相对的在浏览器环境中没有module和module.exports种cjs 导出变量,为了让我们封装的东西可以在两个环境运行就可以利用typeof 这种对未声明变量的不报错的性质做出下面形式的写法
js
// 支持浏览器导入 && 支持NODE端运行{CommonJS规范}
(function () {
let utils = {
// ...
};
/!* 暴露API *!/
if (typeof module === "object" && typeof module.exports === "object") module.exports = utils;
if (typeof window !== "undefined") window.utils = utils;
})();
在es6的let 和 const 声明变量,前用typeof访问变量
在es6出现后 let const在其被声明之前对块中的 let 和 const 变量使用 typeof 会抛出一个 ReferenceError。块作用域变量在块的头部处于暂存死区,直至其被初始化,在这期间,访问变量将会引发错误。
js
typeof undeclaredVariable === 'undefined';
typeof newLetVariable; // ReferenceError
typeof newConstVariable; // ReferenceError
typeof newClass; // ReferenceError
let newLetVariable;
const newConstVariable = 'hello';
class newClass{};
typeof null === 'object' 最开始的设计bug
所有数据类型值,在计算机底层中都是以二进制形式存储的(64位),相对的二进制检测类型效率更高。在 JavaScript 中,不同数据类型对应 的二进制存储形式如下:
- 000:对象
- 1:整数
- 010:浮点数
- 100:字符串
- 110:布尔
- 000000….:null
可以发现,由于 null 的存储单元(全是 0)最后三位和 object 完全一样是 000,因此判断 null 也为 Object。但是内部识别为对象后,会再次检测这个对象是否有内部实现[[call]]。如果实现了,结果是 function,如果没有实现,结果是 object。
判断是否是对象
- 在function 也是对象但是typeof function 得到是function ,当想对是否为对象判断时候并且包含function 可以写成
js
if (val !== null && /^(object|function)$/i.test(typeof val)) {
// ...
}
instanceof -- 检测引用类型
a instanceof B
a是不是B的实例,即a的原型链上是否有B
js
var a = [1,2,3]
console.log(a instanceof Array) // 变量 A 是数组么 true
- 在
ECMAScript7
规范中的instanceof
操作符则是根据 Symbol.hasInstance 进行,用于判断某对象是否为某构造器的实例。因此你可以用它自定义 instanceof 操作符在某个类上的行为。例如,对于数组构造函数上的Symbol.hasInstance属性,可以使用以下代码进行判断:
js
var arr = []
Array[Symbol.hasInstance](arr) // true
下图可以看出,数组构造函数上的Symbol.hasInstance属性是继承自Function的prototype属性,ECMAScript7
规范中,在Function
的prototype
属性上定义了Symbol.hasInstance
属性
Symbol.hasInstance属性特点 writable/enumerable/configurable 都为false 不可写,不可枚举 不可修改属性即不可以属性赋值。下面代码进行验证,即使已经修改了返回值为false 但实际打印是true 证明不可修改的
js
Array[Symbol.hasInstance] = function (){return false }
arr instanceof Array // true
因此,在普通的构造函数上直接修改Symbol.hasInstance属性是不行的。但是,可以使用class进行重写。例如,对于class Fn,可以使用以下代码进行重写:
js
class Fn {
static[Symbol.hasInstance](obj) {
console.log('OK');
if (Array.isArray(obj)) return true;
return false;
}
}
let f = new Fn;
let arr = [10, 20, 30];
console.log(f instanceof Fn); //=>false
console.log(arr instanceof Fn); //=>true
console.log(Fn[Symbol.hasInstance](f)); //=>true
- 像上面案例可以知道Symbol.hasInstance是一个内置的Symbol值,它是一个用于定义对象的instanceof运算符的方法。在一个对象上使用instanceof运算符时,会调用该对象的Symbol.hasInstance方法,该方法接受一个参数,即要检测的值,如果该方法返回true,则表示该值是该对象的实例,否则返回false。如果在没有Symbol.hasInstance 属性浏览器上则会像以前一样去原型链上中
js
class A extends Array{}
const a = new A()
a instanceof A // true
a instanceof Array // true
Array[Symbol.hasInstance](a) // true
A[Symbol.hasInstance](a) // true
A[Symbol.hasInstance](new Map) // false
- instanceof 不能检测原始值类型的值,下面案例即使 1 确实是Number 类型但不是Number对象的实例,因此使用instanceof操作符检测时会返回false。如果使用new Number(1)创建一个Number对象,那么该对象就是Number构造函数的实例,使用instanceof操作符检测时会返回true。
js
console.log(new Number(1) instanceof Number); //=>true
console.log(1 instanceof Number); //=>false
TIP
在JavaScript中,使用instanceof操作符检测一个对象是否是某个构造函数的实例时,实际上是调用了一个名为InstanceofOperator的抽象操作。该操作的步骤如下:
- 如果C的数据类型不是对象,抛出一个类型错误的异常。
- 如果O的数据类型不是对象,返回false。
- 如果O的原型链上存在C.prototype,返回true。
- 递归地检查O的原型链上的下一个原型对象,重复步骤3和步骤4,直到原型链结束或找到C.prototype为止。 因此,如果C不是一个对象,而是一个原始值类型的值,那么在第一步就会抛出一个类型错误的异常。例如,如果我们使用1 instanceof Number进行检测,由于Number是一个构造函数,而1是一个数字类型的值,不是一个对象,因此会抛出一个类型错误的异常。
参考ECMAScript7规范中的instanceof操作符
实现一个instanceof
js
function myInstanceof(left, right) {
// 这里先用typeof来判断基础数据类型,如果是,直接返回false
if(typeof left !== 'object' || left === null) return false;
// getProtypeOf是Object对象自带的API,能够拿到参数的原型对象
let proto = Object.getPrototypeOf(left);
while(true) { //循环往下寻找,直到找到相同的原型对象
if(proto === null) return false;
if(proto === right.prototype) return true;//找到相同原型对象,返回true
proto = Object.getPrototypeof(proto);
}
}
// 验证一下自己实现的myInstanceof是否OK
console.log(myInstanceof(new Number(123), Number)); // true
console.log(myInstanceof(123, Number)); // false
Object.prototype.toString
toString()是Object的原型方法,调用该方法可以统一返回格式为'[object Xxx]'的字符串,其中Xxx就是对象的类型。对于Object对象,直接调用toString()就能返回'[object Object]',其他对象需要通过call来调用。
这是JS中唯一一个检测数据类型没有任何瑕疵的方法,可以检测内置类型,例如"[object Number/String/Boolen/Null/Undefined/Symbol/BigInt/Object/Function/Array/RegExp/Date/Math/Error...]"。
js
Object.prototype.toString({}) // "[object Object]"
Object.prototype.toString.call({}) // 同上结果,加上call也ok
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(function(){}) // "[object Function]"
Object.prototype.toString.call(null) //"[object Null]"
Object.prototype.toString.call(undefined) //"[object Undefined]"
Object.prototype.toString.call(/123/g) //"[object RegExp]"
Object.prototype.toString.call(new Date()) //"[object Date]"
Object.prototype.toString.call([]) //"[object Array]"
Object.prototype.toString.call(document) //"[object HTMLDocument]"
Object.prototype.toString.call(window) //"[object Window]"
- 检测返回值遵循规则,一般都是返回当前实例所属的构造函数信息。但是如果实例对象拥有'Symbol.toStringTag'属性,属性值就是返回值。例如,Math[Symbol.toStringTag]="Math",则Object.prototype.toString.call(Math)返回"[object Math]"。
如果没有重写'Symbol.toStringTag',则返回值为"[object Object]"。如果重写了'Symbol.toStringTag',则返回值为"[object Xxx]",其中Xxx为重写的属性值。
示例代码:
js
// 没有重写'Symbol.toStringTag'
class Fn {}
let f = new Fn;
console.log(Object.prototype.toString.call(f)); // [object Object]
// 重写'Symbol.toStringTag'
class Fn {
[Symbol.toStringTag] = 'Fn';
}
let f = new Fn;
console.log(Object.prototype.toString.call(f)); // [object Fn]
typeof 和 Object.prototype.toString 搭配来判断数据类型
js
function getType(obj){
let type = typeof obj;
if (type !== "object") { // 先进行typeof判断,如果是基础数据类型,直接返回
return type;
}
// 对于typeof返回结果是object的,再进行如下的判断,正则返回结果或者用slice(8,-1)来截取获得
return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1'); // 注意正则中间有个空
}
/* 代码验证,需要注意大小写,类型首字母大写就是toString 小写就是typeof */
getType([]) // "Array" typeof []是object,因此toString返回
getType('123') // "string" typeof 直接返回
getType(window) // "Window" toString返回
getType(null) // "Null"首字母大写,typeof null是object,需toString来判断
getType(undefined) // "undefined" typeof 直接返回
getType() // "undefined" typeof 直接返回
getType(function(){}) // "function" typeof能判断,因此首字母小写
getType(/123/g) //"RegExp" toString返回
Object.toString.call 检测会报错
如果当你使用Object.toString.call 来判断类型你会发现,会报错,其中提示内容是报错内容如下Function.prototype.toString requires that 'this' be a Function
内置的Object构造函数是一个 Function(就像所有的原生构造函数一样),所以它在它自己的原型属性之前从'Function.prototype'继承 Object[[Prototype]] -> Function.prototype -> Object.prototype -> null
所以实际调用的是Function.prototype.toString,可以通过查看 下面的两个链接(ecma-262 Mdn-toString 阶段参考)
可以知道若 'this' 不是 Function 对象,则 toString() 方法将抛出 TypeError ("Function.prototype.toString called on incompatible object") 异常
在看下面的例子更好的理解,Object.toString(构造函数也是对象,虽然他的typeof 是function) 调用的是函数上toString,而真正能进行类型验证是对象上的toString。就近原则会先找函数上的现在来说
js
console.log(typeof Object); // function
console.log(typeof Object.prototype); // object
// Object.toString.call([]) // 报错
function a () { }
Object.toString.call(a) // 不报错因为a 是function
但如果我们此时是一个普通的对象你会发现,可以成功查询类型原因也是 a 是通过Object 创建的实列,此时a.toString 找到就是'Object.prototype.toString' 达到了想要的效果
js
var a = new Object()
a.toString.call(1) // '[object Number]'
constructor
js
const array = []
console.log( array.constructor)
const num = 1
console.log(num.constructor)
打印结果
ƒ Array() { [native code] }
ƒ Number() { [native code] }
var a = [];
a.constructor === Array;
如何选择数据类型检测
- typeof 在引用类型检测的时候不能具体,除了function 可以判断出来,剩下的引用类型和null统一都是'object'
- instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型;
- Object.prototype.toString 万能方法最终解