JavaScript数据类型检测方法总结

825 阅读2分钟

typeof

返回一个字符串,包含了对应的数据类型。

typeof null // object
typeof new Number(10) // object
// typeof 不能细分对象
// 非null的原始值 和 函数类型的检测很友好

原理:根据计算机底层存储值的二进制来计算。性能会好一些。

  • 对象类型监测不友好,基础类型除了null都可以快速检测。
  • 000开始的值都被认为是对象类型,而null的值是000000,所以被认为是对象。
  • Function 类型判断是否实现了call 方法,有就是function 类型

instanceof

  • 本意不是检测数据类型,而是检测当前实例是不是属于这个类。

  • 可以根据instanceof 细分对象类型。

[] instanceof Array // true
[] instanceof Object // true

原理:首先按照构造函数的Symbol.hasInstance([实例]) 方法进行检测,如果存在这个属性方法,则方法执行返回的值就是最后检测结果。如果不存在这个属性方法,则会查找当前实例的原型链(一直找到Object.prototype为止),如果查找中途,找到了某个原型等于构造函数的原型「构造函数出现在实例的原型链上」则返回结果为true,反之是false。

  • 缺陷:

    • 无法检测原始值类型(不会像隐式调用一样有装箱操作)
    • 如果原型重定向后此方法就无法正确找到原型
10 instanceof Number // falsefunction Fn() {}
Fn.prototype = [];
let f = new Fn();
f instanceof Array // true
// es6 可以重写Symbol.hasInstance方法,es5不可以
class Fn {
  static [Symbol.hasInstance](value) {
    return true;
  }
}
1 instanceof Fn // true
function instance_of(obj, Ctor) {
  // 数据格式校验
  if (Ctor == null || Ctor == undefined) {
    throw new TypeError('Right-hand side of "instanceof" is not an object');
  };
  if (!Ctor.prototype) {
    throw new TypeError('Malformed arrow function parameter list');
  }
  if (typeof Ctor !== 'function') {
    throw new TypeError('Right-hand side of "instanceof" is not callable');
  }
  
  // 原始值类型忽略
  if (obj == null) return false;
  if (!/^(object|function)$/.test(typeof obj)) return false;
  
  // 检测是否有Ctor[Symbol.hasInstance]方法
  if (typeof Ctor[Symbol.hasInstance] === 'function') {
    return Ctor[Symbol.hasInstance](obj);
  }
  
  // 走到最后按照原型链查找
  let prototype = Object.getPrototypeOf(obj);
  while(prototype) {
    if (prototype === Ctor.prototype) return true;
    prototype = Object.getPrototypeOf(prototype);
  }
  return false;
}

constructor

  • 一个获取对象的构造函数,不是用来专门判断数据类型的。

  • 优点:

    • 为true的值至多只有一个
    • 原始值调用时会进行“装箱”(除了 null 和 undefined)
  • 缺点

    • constructor是可以被随意更改的,检测结果不一定准确
[].constructor === Array // true
[].constructor === Object // false

Object.prototype.toString.call()

最为推荐的一种方案。

原理:首先找到Object.prototype.toString方法,把toString执行之后,让方法中的this变为要检测的值,toString内部会返回对应this的数据类型信息。

返回值:'[object Object]'

大部分值所属类的原型上都有toString方法,除了Object.prototype.toString 是用来检测类型的,其余的一般都是用来转为字符串的。

非普通对象.toString 一般先调取自己所属类原型上的toString

普通对象.toString 会直接调取Object.prototype.toString

({}).toString() // [object Object]
Object.prototype.toString.call({}) // [object Object]
(1).toString() // '1'

步骤:首先看[value] [Symbol.toStringTag],如果存在这个属性,属性值是啥,返回的类型就是啥,如果没有这个属性,一般是返回所属的构造函数信息

不同场景下的类型检测

var class2type = {}; // Object
var getProto = Object.getPrototypeOf;
var toString = class2type.toString; // Object.prototype.toString
var hasOwn = class2type.hasOwnProperty; // Object.prototype.hasOwnProperty
var fnToString = hasOwn.toString; // Function.prototype.toString
// ObjectFunctionString = 'function Object() { [native code] }'
var ObjectFunctionString = fnToString.call(Object); // Object.toString()var typeArr = ['Boolean', 'Number', 'String', 'Function', 'Array', 'Date','RegExp', 'Object', 'Error', 'Symbol', 'BigInt', 'Map', 'Set']; // ...
typeArr.forEach(function (name) {
  class2type["[object " + name + "]"] = name.toLowerCase();
})
​
function toType(obj) {
  if (obj == null) {
    return obj + "";
  }
​
  return typeof obj === "object" || typeof obj === "function" ?
    class2type[toString.call(obj)] || "object" :
  typeof obj;
}
​
// 使用正则
function toType(obj) {
  if (obj == null) {
    return obj + "";
  }
  if (typeof obj !== "object" || typeof obj !== "function" ) {
    return typeof obj;
  }
  var reg = /^[object ([0-9A-Za-z]+)]$/, value = reg.exec(toString.call(obj))[1] || 'object';
  return value.toLowerCase();
}
​
// 是否是一个函数
function isFunction(obj) {
  // JS中获取object元素,在某些浏览器中,基于typeof检测这个对象,返回的是function
  return typeof obj === "function" && typeof obj.nodeType !== "number" &&
    typeof obj.item !== "function";
};
​
// 是否是window对象
function isWindow(obj) {
  // undefined == null -> true
  return obj != null && obj === obj.window;
};
​
​
// 检测是否为数组和类数组
function isArrayLike(obj) {
  var length = !!obj && "length" in obj && obj.length,
      type = toType(obj);
​
  if (isFunction(obj) || isWindow(obj)) {
    return false;
  }
​
  return type === "array" || length === 0 ||
    typeof length === "number" && length > 0 && (length - 1) in obj;
}
​
// 检测是否为存粹对象(直属类是Object,数组这类不是)
function isPlainObject(obj) {
  var proto, Ctor;
​
  if (!obj || toType(obj) !== 'object') {
    return false;
  }
​
  proto = getProto(obj);
​
  // Objects with no prototype (e.g., `Object.create( null )`) are plain
  if (!proto) {
    return true;
  }
​
  // Objects with prototype are plain iff they were constructed by a global Object function
  Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
  return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
}
​
// 检测是否是空对象
function isEmptyObject(obj) {
  if (obj == null) {
    return false;
  }
  // Object.getOwnPropertyNames 可以获取不可枚举的key Object.keys 获取可枚举的key
  var keys = Object.getOwnPropertyNames(obj);
  if (typeof Symbol !== 'undefined') {
    keys = keys.concat(Object.getOwnPropertySymbols(obj));
  }
  return keys.length === 0;
}
​
// 是否是数字
function isNumeric(obj) {
  var type = toType(obj);
  return (type === "number" || type === "string") && !isNaN(obj - parseFloat(obj));
};