数据类型检测及jQuery数据类型检测源码解析

125 阅读4分钟

前言

数据类型检测是用来检测一个值属于哪个类型的,js中的类型有Number、String、Null、Boolean、Undefined、Symbol,引用数据类型有Object,Object中有很多种具体的类型,包括function、regexp、Date。

下面我们来看看有哪些具体的检测方法。

typeof

typeof是用来检测数据类型的运算符,通过“typeof [value]”来使用。

它的返回值是个字符串,字符串中包含对应的数据类型,例如:"number"、"object"、"undefined"、"function"、"boolean"、"symbol"...

它有自己的局限性:

  1. “typeof null”返回"object"字符串,因为它把null当做空对象指针;
  2. 不能具体区分对象数据类型的值“typeof []”,“typeof {}”,“typeof /^$/”,所以无法检测是正则还是数组等。

typeof使用方便,所以在真实项目中,我们也会大量应用它来检测,尤其是在检测基本类型值(除null之外)和函数类型值的时候,它还是很方便的。

instanceof

本意是用来检测实例是否隶属于某个类的运算符,我们基于这样的方式,也可以用来做某些数据类型的检测,例如:数组、正则等。它是基于原型链(__proto__)机制检测的。

但是使用它需要注意两点:

  1. 不能处理基本数据类型值,“1 instanceof Number”值为false,我们知道创建基本类型值有两种方式,字面量创建:let n = 12和构造函数:let m = new Number('12'),不管哪种方式都是所属类的实例,但是instanceof检测不了字面量创建的值;
  2. 只要在当前实例的原型链(__proto__)中出现过的类,检测结果都是true,用户可能会手动修改原型链的指向:example.__proto__ 或者 在类的继承中等情况。

constructor

在类的原型上一般都会带有constructor属性,存储当前类本身,我们也是利用这一点,获取某的实例constructor属性值,验证是否为所属的类,从而进行数据类型检测。但是constructor属性值太容易被修改了

let arr = [];
arr.constructor = 111; //=>设置私有属性
console.log(arr.constructor === Array); //   false

Object.prototype.toString.call([value])

调用Object原型上的toString方法,让方法执行的时候,方法中的this是要检测的数据类型,从而获取到数据类型所属类的详细信息。返回的值是"[object 所属类]",例如:"[object Array]"

在所有的数据类型类中,他们的原型上都有toString方法,除Object.prototype.toString不是把数据值转换为字符串,其余的都是转为字符串,而Object原型上的toString是检测当前实例所属类的详细信息的(检测数据类型)。

这个方法很强大,所有数据类型隶属的类信息检测的一清二楚,包括String/Boolean/Null/Undefined/Symbol/Object/Array/RegExp/Date/Math/Function...

obj.toString()
1.首先基于原型链查找机制,找到Object.prototype.toString;
2. 把找到的方法执行,方法中的this指向obj;
3. 方法内部把this(obj)的所属类信息输出;
4. 使用call传值改变this,对this进行检测。

let _obj = {};
console.log(_obj.toString.call(100)); //  "[object Number]"
console.log(Object.prototype.toString.call(100)); //  "[object Number]"

封装自己的数据类型检测方法

(function (win) {
  // 指定一个对象存储常用的数据类型
  let _obj = {
    isNumber: "Number",
    isBoolean: "Boolean",
    isString: "String",
    isNull: "Null",
    isUndefined: "Undefined",
    isSymbol: "Symbol",
    isPlainObject: "Object",
    isArray: "Array",
    isRegExp: "RegExp",
    isDate: "Date",
    isFunction: "Function",
    isWindow: "Window",
  };
  // 获取Object.toString方法
  let _toString = _obj.toString;
  // 定义一个对象用于存储常用的检测方法
  let _type = {};

  // 循环obj,遍历各个_obj属性
  for (let key in _obj) {
    if (!_obj.hasOwnProperty(key)) break;
    let checkCode = _obj[key];
    // 给对象添加常用的检测方法
    _type[key] = function checkData(val) {
      let reg = new RegExp("^\\[object " + checkCode + "\\]$");
      return reg.test(_toString.call(val));
    };
  }

  win._type = _type;
})(window);

jQuery中的数据类型检测方法

首先定义全局变量用于数据类型检测方法的使用

// 定义一个对象,方便在上面添加方法和调取方法
var objectType = {};
// 获取对象的toString方法
var toString = objectType.toString; //=>Object.prototype.toString
// 获取对象的hasOwnProperty方法
var hasOwn = objectType.hasOwnProperty; //=>Object.prototype.hasOwnProperty
// 获取函数的toString方法,可以将js的基本类型和引用类型值转换位字符串
var fnToString = hasOwn.toString; //=>Function.prototype.toString
// 获取“Function.prototype.toString”字符串, 检测是否为纯粹的对象要用到
var ObjectFunctionString = fnToString.call(Object); //"function Object() { [native code] }"
// 获取getPrototypeOf方法
var getProto = Object.getPrototypeOf;

函数判断

检测一个对象是否为函数,直接受用typeof判断即可

var isFunction = function isFunction(obj) {
  return typeof obj === "function";
};

window判断

检测一个变量是否为window对象,使用“window.window === window”判断。

var isWindow = function isWindow(obj) {
  return obj != null && obj === obj.window;
};

全面的数据类型检测方法

当对引用类型的具体对象进行判断时,使用toString方法,包括new出来的基本类型。

// 可以处理new Number/new String等构造出来的基本类型
var typeArr = [
  "Boolean",
  "Number",
  "String",
  "Function",
  "Array",
  "Date",
  "RegExp",
  "Object",
  "Error",
  "Symbol",
];
typeArr.forEach((name) => {
  // 给objectType进行赋值,键为[`[object ${name}]`],值为"Boolean"...
  objectType[`[object ${name}]`] = name.toLowerCase();
});


function toType(obj) {
  if (obj == null) {
    return obj + "";
  }
  // 当是object或者function类型时,采用toString进行判断具体所属的对象
  return typeof obj === "object" || typeof obj === "function"
    ? objectType[toString.call(obj)] || "object"
    : typeof obj;
}

判断是否为数组或者类数组

首先判断传入的数据是否为对象或者数组,类数组也是对象。接着判断对象中是否有length属性,两者都有就返回true。

function isArrayLike(obj) {
  // obj存在并且length是obj上的一个属性
  // type拿到数据类型
  var length = !!obj && hasOwn.call(obj, "length") && obj.length;
  var type = toType(obj);
  // 排除函数和window
  if (isFunction(obj) || isWindow(obj)) {
    return false;
  }
  
  // typeof length === "number" && length > 0 && (length - 1) in obj 有lengt属性并且为了保证有数字索引并且是递增的,基于length - 1最大索引是否在OBJ中来验证  “类数组”
  return (
    type === "array" ||
    length === 0 ||
    (typeof length === "number" && length > 0 && length - 1 in obj)
  );
}

符合规则的有以下类型的数据:

  1. 数据是数组;
  2. 数据为对象并且有length属性,而且length值为等于0的数字;
  3. 数据为对象并且有length属性,而且length值为大于0的数字,并且length - 1的值是数据的一个属性。

当然,有不完美的地方,例如“{ a: 10, length: 0 }”,但是,已经很准确了。

检测是否为空对象

jQuery.isEmptyObject = function (obj) {
  if (!obj || toType(obj) !== "object") {
    return false;
  }

  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 如果可以进入FOR IN循环,说明对象是有属性的,返回false
      return false;
    }
  }
  return true;
};

检测是否为纯粹对象

最主要是检测直属原型链是否是Object.prototype

// 纯粹对象  obj.__proto__===Object.prototype
function isPlainObject(obj) {
  var proto, ctor;
  // 不存在或者检测数据类型不是OBJECT则返回FALSE
  if (!obj || toType(obj) !== "object") {
    return false;
  }
  // 获取当前值的原型
  proto = getProto(obj);
  //  使用Object.create( null )创造出来的也被认为是纯粹的对象
  if (!proto) {
    return true;
  }
  
  // 通过hasOwn获取ctor的constructor属性值,如果fnToString.call(ctor)是"function Object() { [native code] }",说明是Object函数直接构造出来的,就是纯粹的对象
  ctor = hasOwn.call(proto, "constructor") && proto.constructor;
  return (
    typeof ctor === "function" && fnToString.call(ctor) === ObjectFunctionString
  );
}

总结

通过以上方法基本上可以在实际工作中满足我们对数据类型检测的需求,当然,以上方法还有很多不完善,或者随着js的发展,会有很多新的数据类型没有检测到,需要我们进一步完善其中的方法。