分析 JQ 中数据类型检测的方法

96 阅读2分钟

本文分析的 jquery 版本为 v3.6.0

当我们了解了 Object.prototype.toString 方法用来检测数据类型时,是不是觉得最优的方案就是它了呢,其实未必,因为 typeof 也有它的优势,特别是检测对象类型性能特别好,我们来看下 jquery 中如何处理的~

代码片段分析

var getProto = Object.getPrototypeOf;
// ...

var class2type = {};

var toString = class2type.toString; // Object.prototype.toString

var hasOwn = class2type.hasOwnProperty; // Object.prototype.hasOwnProperty

var fnToString = hasOwn.toString; // Function.prototype.toString

var ObjectFunctionString = fnToString.call( Object ); // 把 Object 构造函数转字符串

// ...
// 匿名函数具名化 是一种好的习惯
var isFunction = function isFunction( obj ) {
	return typeof obj === "function" && 
		   // 为了防止 obj 为 document.createElement("object") 创建一个 Object 标签
		   // 嵌套 flash 动画的
		   typeof obj.nodeType !== "number" && 
		   // 这个也不知道为了检测什么场景 反正我们想不到的 jq 都在做
		   typeof obj.item !== "function";
};


// 检测是否是一个 window 对象
var isWindow = function isWindow( obj ) {
	// window 特性之一:套娃操作 window.window == window
	return obj != null && obj === obj.window;
};

var arr = "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " );

// 建立映射表
jQuery.each(arr, function( _i, name ) {
	class2type[ "[object " + name + "]" ] = name.toLowerCase();
} );

// {
// 	'[object Array]': 'array',
// 	'[object Boolean]': 'boolean',
// 	...
// }


// 标准的检测数据类型的方法
var toType = function toType( obj ) {
	// 匹配 null 和 undefined
	if ( obj == null ) {
		return obj + "";
	}

	// 排除掉 null 和 undefined 后,这里的 object 和 function 都是用 typeof 来检测
	// 要学习这种组合用法
	return typeof obj === "object" || typeof obj === "function" ?
		   // 通过映射表 返回一个小写的数据类型(同 typeof 返回)
		   class2type[ toString.call( obj ) ] || "object" :
		   typeof obj;
}

// 检测是否为数组或者类数组
function isArrayLike( obj ) {
	// 数组和类数组的特性
	//   @1 都有 length
	//   @2 都有索引
	//   @3 都是对象
	var length = !!obj && "length" in obj && obj.length,
		type = toType( obj );

	// 函数和 window 有 length,代表形参个数和 iframe 个数
	//  
	if ( isFunction( obj ) || isWindow( obj ) ) {
		return false;
	}

	return type === "array" ||  // 是纯数组
		   // 不是数组 且有 length 是空类数组 
		   length === 0 || 
		   // 不是数组 且 length > 0 且 最大索引存在在 obj 上
		   typeof length === "number" && length > 0 && ( length - 1 ) in obj;
}


// 检测是不是一个纯粹对象「直属类是 Object 或者 Object.create(null)」
var isPlainObject = function( obj ) {
	var proto, Ctor;

	// Detect obvious negatives
	// Use toString instead of jQuery.type to catch host objects
	if ( !obj || toString.call( obj ) !== "[object 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;

	// 都转把实例的构造函数和 Object 都转 string 对比
	return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
}

// 是不是一个空对象
// var isEmptyObject = function( obj ) {
// 	var name;

// 	// 这种写法不好 for in 不能遍历到 Symbol 的属性
// 	// 也会遍历到原型链上的属性
// 	for ( name in obj ) {
// 		return false;
// 	}
// 	return true;
// }

// 我们实现一个优化过的空对象检测方法
var isEmptyObject = function( obj ) {
	var keys = Object.keys(obj);

	// 看浏览器是否支持 Symbol
	if (typeof Symbol !== "undefined") {
		keys = keys.concat(Object.getOwnPropertySymbols(obj));
	}

	return keys.length === 0;
}

// 检测是不是数字 支持 1   '1' 
var isNumeric = function( obj ) {
	// 获取类型 
	var type = jQuery.type( obj );

	return ( type === "number" || type === "string" ) &&
		!isNaN( obj );
};