详细解读JavaScript中类型检测方案
source link: https://zhuanlan.zhihu.com/p/126291383
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
详细解读JavaScript中类型检测方案
数据类型检测
在JavaScript
中,数据类型检测有如下方法:
- typeof
- instanceof
- constructor
- Object.prototype.toString.call
- Array.isArray
接下来我们一一介绍每种方式的使用方式及使用技巧
typeof
首先看一下mdn
中关于typeof
返回值的介绍:
从上图中可以看到,typeof
对于简单数据类型(Number,String,Boolean,Undefined
)的判断是准确的。
对于复杂数据类型,typeof
会将function
进行单独处理。而function
之外的数据类型,不管是字面量对象,还是Date, Regexp, Math
都会统一返回object
。当typeof
用来处理null
时也会返回object
总结一下:
typeof function === 'function'
typeof null === 'object'
typeof execeptFunctionObject === 'object'
当我们使用typeof
来判断一个未声明的变量时,会返回undefined
,并不会报错:
const a = 1
console.log(typeof a) // 'number'
console.log(typeof b) // 'undefined' (不会报错)
需要注意的是,typeof
的返回值为string
类型,我们看一下下边的代码:
console.log(typeof typeof []) // typeof 'object' => 'string'
console.log(typeof typeof {}) // typeof 'object' => 'string'
instanceof
参考:
* 类检查:"instanceof"
我们先看下mdn
对于instanceof
的介绍:
instanceof
运算符用于检测构造函数的prototype
属性是否出现在某个实例对象的原型链上
对于instanceof
我们可以这样理解:
- 检测某个实例是否属于某个类
- 所有出现在实例原型链上的类都会返回
true
我们看下面一个例子:
console.log([] instanceof Array); // true
console.log([] instanceof Object); // true
instanceof
的执行过程就是判断实例的__proto__
是否为instanceof
后边的类的prototype
。
是的话就会返回true
,如果不是的话,继续从原型链向上查找,即实例的__proto__.__proto__
是否为instanceof
后边的类的prototype
,直到找最后Object.prototype
都没有找到的话,返回false
,否则返回true
这里我们用代码模拟一下instanceof
的查找过程,方便理解:
const _instanceof = function (instance, Class) {
// let proto = instanceof.__proto__
let proto = Object.getPrototypeOf(instance);
while (Class.prototype) {
if (proto === Class.prototype) {
return true;
}
// proto = proto.__proto__
proto = Object.getPrototypeOf(proto);
}
return false;
};
console.log(_instanceof([], Array)); // true
console.log(_instanceof([], Object)); // true
根据instanceof
的原理可以做出如下推论:
- 由于
[].__proto__ === Array.prototype
,所以[] instanceof Array
返回true
- 由于
Array.prototype.__proto__ === Object.prototype
,即[].__proto__.__proto === Object.prototype
,所以[] instanceof Object
返回true
由于instanceof
是根据原型链来进行检测的,所以我们可以得出以下俩个结论:
- 原型链的指向可以改变,所以
instanceof
用来检测数据类型并不完全准确 - 由于简单数据类型没有
__proto__
属性,所以无法使用instanceof
来检测类型
我们看下instanceof
无法准确进行检测的例子:
const fn = function() { };
// 更改原型和原型链指向
fn.__proto__ = Array.prototype;
fn.prototype = Array.prototype;
const fn1 = new fn();
console.log(fn instanceof Function); // false
console.log(fn instanceof Array); // true
console.log(fn1 instanceof fn); // true
console.log(fn1 instanceof Array); // true
// 简单数据类型检测不准确
console.log(1 instanceof Number); // false
constructor
每一个函数都有一个prototype
属性,该属性的值为对象。而prototype
相比于普通对象,其天生就带有constructor
属性,属性值为原型所属的函数:
const arr = [];
console.log(arr.constructor); // Array
上边我们定义了一个数组arr
,在获取arr
的constructor
属性时:
- 从本身的属性中查找并没有找到
constructor
属性 - 从
arr.__proto__
中查找 - 由于
arr
是Array
的实例,所以arr.__proto__ === Array.prototype
Array.prototype.constructor === Array
,所以arr.constructor
为Array
constructor
和instanceof
类似,都是基于原型链来进行数据类型检测,所以当我们修改原型指向后,检测结果并不准确。
const arr = [];
// 当然,一般情况下我们并不会这样做,这里仅仅举例说明
Array.prototype.constructor = null;
console.log(arr.constructor); // null
Array.isArray
Array.isArray
是构造函数Array
上的一个属性,它可以判断一个值的类型是否为数组:
Array.isArray([1,2,3]); // true
Array.isArray(1); // false
Array.isArray('abc'); // false
当然,该方法只能用于判断数组,并不能判断是否为其它数据类型
Object.prototype.toString.call
在上文中介绍的数据类型检测方法都有一定的不足,这里我们介绍的Object.prototype.toString.call
方法可以很好的判断数据类型,并且弥补了之前几种方法出现的问题。
下面我们看下如何使用该方法来进行数据类型检测:
const toString = Object.prototype.toString
// 判断简单数据类型
console.log(toString.call(1)); // '[object Number]'
console.log(toString.call('a')); // '[object String]'
console.log(toString.call(true)); // '[object Boolean]'
console.log(toString.call(null)); // '[object Null]'
console.log(toString.call(undefined)); // '[object Undefined]'
// 判断复杂数据类型,可以详细区分不同的对象
console.log(toString.call([])); // '[object Array]'
console.log(toString.call({})); // '[object Object]'
console.log(toString.call(new Date)); // '[object Date]'
即使我们更改原型链,也可以准确检测数据类型:
const arr = []
arr.__proto__ = Function.prototype
console.log(arr instanceof Array); // false
console.log(toString.call(arr)); // '[object Array]'
Object.prototype.toString
并不会将对象转换为字符串,而是将对象的信息作为[object Type]
的格式输出。
我们通过call
方法来借用Object.prototype.toString
,让其它非对象数据类型也能调用该方法,获取到其信息来进行数据检测。
如果你不知道选择哪种方式来检测数据类型的话,使用Object.prototype.toString.call
方法准没错!
jQuery
源码数据类型检测
在掌握了JavaScript
中关于数据类型检测的相关知识后,我们看一下在jQuery
中是如何进行数据类型检测的。
先看下如何使用jQuery
中的数据类型检测方法:
jQuery.type( true ) === "boolean"
jQuery.type( 3 ) === "number"
jQuery.type( "test" ) === "string"
jQuery.type( function(){} ) === "function"
jQuery.type( [] ) === "array"
jQuery.type( undefined ) === "undefined"
下面是笔者整理的jQuery
中的类型检测相关代码:
var class2type = {};
var toString = class2type.toString; // Object.prototype.toString
var hasOwn = class2type.hasOwnProperty; // Object.prototype.hasOwnProperty
var fnToString = hasOwn.toString; // Function.prototype.toString
// Function.prototype.toString.call(Object)
// Object是一个类,也属于函数,可以调用函数的toString方法
var ObjectFunctionString = fnToString.call(Object);
// 一些常见数据类型
'Boolean Number String Function Array Date RegExp Object Error Symbol'.split(' ').forEach(function anonymous (item) {
class2type['[object ' + item + ']'] = item.toLowerCase();
// 拼接成如下格式 class2type[object Type] = type
});
function toType (obj) {
// null/undefined == null
// 若果obj是null或者undefined,返回其对应的字符串('null'或者'undefined')
if (obj == null) {
return obj + ''; // return 'null' / return 'undefined'
}
// 复杂数据类型: typeof object = 'object' ; typeof function = 'function'
// 1. typeof obj === 'object' || typeof obj === 'function' , 这个逻辑表示obj为对象或者null,null之前已经进行了处理
// 2. class2type[toString.call(obj)) || 'object',
// 这个逻辑表示会根据obj调用Object.prototype.toString方法后是否是class2type的属性,
// 是的话返回其类型,否则返回'object'
// 3. 对象 ? class2type中的属性值类型或'object' : typeof obj,
// 即复杂数据类型通过class2type中的属性值判断或者返回'object',简单数据类型直接使用typeof进行判断
return typeof obj === 'object' || typeof obj === 'function' ? class2type[toString.call(obj)] || 'object' : typeof obj;
}
jQuery
会将类型经过Object.prototype.toString
方法转换后的结果作为key
放到class2type
中,并将类型的小写值作为属性值,最终在调用时返回
jQuery
还为我们封装了一些工具方法方便进行类型判断:
var isWindow = function isWindow (obj) {
// null和undefined返回false, window = window.window
return obj != null && obj === obj.window;
};
// 普通对象的逻辑:
// 1. toString.call(obj) === '[object Object]'
// 2. Object.create(null) 如果没有原型并且满足条件1就是普通对象
// 3. 如果对象的原型有constructor属性,并且置为Object的话,就是普通对象
var isPlainObject = function isPlainObject (obj) {
var proto, Ctor;
if (!obj || toString.call(obj) !== '[object Object]') {
return false;
}
// 获取obj的原型
proto = Object.getPrototypeOf(obj);
// 如果一个对象没有原型,那么是plain object
// Objects with no prototype (`Object.create( null )`)
if (!proto) {
return true;
}
// proto原型自身有constructor属性,赋值给Ctor,否则Ctor为false
// Objects with prototype are plain if they were constructed by a global Object function
Ctor = hasOwn.call(proto, 'constructor') && proto.constructor;
// 如果Ctor是一个函数,
// 那么Ctor调用Function.prototype.toString方法是否和Object调用Function.prototype.toString方法结果相同
return typeof Ctor === 'function' && fnToString.call(Ctor) === ObjectFunctionString;
};
// 空对象
var isEmptyObject = function isEmptyObject (obj) {
var name;
// 如果obj是空对象不会执行该循环
// in 关键字会遍历原型上的一些属性和方法,需要与hasOwnProperty进行结合使用
for (name in obj) {
return false;
}
return true;
};
在日常开发中,我们可以结合jQuery
源码封装自己的类型判断函数。
JavaScript
中的类型检测方法各有特点,对于其用法我们做一下小结:
- 简单数据类型可以使用
typeof
来判断,语法简单快捷 - 可以直接使用
Array.isArray
来检测数组 instanceof
和constructor
可以用来检测对象的具体类型,但是在修改了原型链后,结果会不准确Object.prototype.toString.call
是一个万能公式,基本上可以用来检测JavaScript
中所有的数据类
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK