Skip to content
快速预览

数据类型检测

✍️ chunyi.wang 🕒 2023-07-17 09:58:22(10 months ago) 🔗 A.前端知识整理

判断类型常用方法typeof、instanceof、Object.prototype.toString、constructor

使用typeof 区分数据类型

  1. typeof 是一个操作符,不是方法因此使用的时候可以加括号,也可以省略

  2. 使用 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'
  1. 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。

参考链接1

参考链接2

判断是否是对象

  1. 在function 也是对象但是typeof function 得到是function ,当想对是否为对象判断时候并且包含function 可以写成
js
if (val !== null && /^(object|function)$/i.test(typeof val)) {
    // ...
}

instanceof -- 检测引用类型

  1. a instanceof B a是不是B的实例,即a的原型链上是否有B
js
var a = [1,2,3]
console.log(a instanceof Array)  // 变量 A 是数组么 true
  1. ECMAScript7规范中的instanceof操作符则是根据 Symbol.hasInstance 进行,用于判断某对象是否为某构造器的实例。因此你可以用它自定义 instanceof 操作符在某个类上的行为。例如,对于数组构造函数上的Symbol.hasInstance属性,可以使用以下代码进行判断:
js
var arr = []
Array[Symbol.hasInstance](arr) // true

下图可以看出,数组构造函数上的Symbol.hasInstance属性是继承自Function的prototype属性,ECMAScript7规范中,在Functionprototype属性上定义了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
  1. 像上面案例可以知道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
  1. 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

  1. toString()是Object的原型方法,调用该方法可以统一返回格式为'[object Xxx]'的字符串,其中Xxx就是对象的类型。对于Object对象,直接调用toString()就能返回'[object Object]',其他对象需要通过call来调用。

  2. 这是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]"
  1. 检测返回值遵循规则,一般都是返回当前实例所属的构造函数信息。但是如果实例对象拥有'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;

如何选择数据类型检测

  1. typeof 在引用类型检测的时候不能具体,除了function 可以判断出来,剩下的引用类型和null统一都是'object'
  2. instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型;
  3. Object.prototype.toString 万能方法最终解

Released under the MIT License.