写在前面:
98年研二在读,方向人工智能,准备转行前端,在此账号记录下我的前端学习之路,也会更新一些面试经验贴。欢迎各位大佬指点赐教!
一、数据类型检测的方式
1. typeof
typeof一般用来判断基本类型。
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof 'str'); // string
console.log(typeof []); // object
console.log(typeof function(){}); // function
console.log(typeof {}); // object
console.log(typeof undefined); // undefined
console.log(typeof null); // object
//对应Function会被检测成function
console.log(typeof new Function) // function
可以看到数组、对象、null都会判断成为object,其中,null有属于自己的数据类型Null,这是一个存在很久的bug ,引用类型中的数组、日期、正则 也都有属于自己的具体类型,而 typeof对于这些类型的处理,只返回了处于其原型链最顶端的 Object类型。 所以当我们要判断一个对象是否是数组时,或者判断某个变量是否是某个对象的实例,则要选择使用另一个关键语法instanceof。
2. instanceof
instanceof可以正确判断对象的类型,其内部运行机制是判断在其原型链中能否找到该类型的原型。
console.log(2 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log('str' instanceof String); // false
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
[] instanceof Array; // true
[] instanceof Object; // true
instanceof 能够判断出[]是Array的实例,但它认为[]也是Object的实例,从 instanceof能够判断出[ ].__proto__指向Array.prototype,而Array.prototype.__proto__又指向了Object.prototype,最终Object.prototype.__proto__指向了null,标志着原型链的结束。因此,[ ]、Array、Object就在内部形成了一条原型链,如下图所示:
因此、
instanceof只能用来判断两个对象是否属于实例关系,而不能判断一个对象的实例具体属于哪种类型。
instanceof运算符用于检测构造函数的prototype 属性是否出现在某个实例对象的原型链上。 所以实现instanceof的思路就是判断右边变量的原型是否存在于左边变量的原型链上。
function instanceOf(left, right) {
// 获取对象的原型
// let proto = Object.getPrototypeOf(left)
let leftValue = left.__proto__;
let rightValue = right.prototype;
while (true) {
if (leftValue === null) return false;
if (leftValue === rightValue) return true;
leftValue = leftValue.__proto__;
}
}
const arr = new Array();
console.log(instanceOf(arr, Array)); //true
可以看到,instanceof只能正确判断引用数据类型,而不能判断基本数据类型。instanceof 运算符可以用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。
3. constructor
constructor有两个作用,一是判断数据的类型,而是对象实例通过constructor对象可以访问它的构造函数。
console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
需要注意,如果创建一个对象来改变它的原型,constructor就不能用来判断数据类型了:
function Fn(){};
Fn.prototype = new Array();
var f = new Fn();
console.log(f.constructor===Fn); // false 创建对象改变原型了
console.log(f.constructor===Array); // true
**当一个函数Fn被定义时,JS引擎会为Fn添加 prototype 原型,然后再在 prototype上添加一个 constructor属性,并让其指向F的引用。**如下图所示:
当执行
var f = new Fn()时,Fn被当成了构造函数,f是Fn的实例对象,此时Fn原型上的 constructor传递到了f上,因此f.constructor == Fn。
可以看出,
Fn利用原型对象上的constructor引用了自身,当Fn作为构造函数来创建对象时,原型上的constructor就被遗传到了新创建的对象上, 从原型链角度讲,构造函数Fn就是新对象的类型。这样做的意义是,让新对象在诞生以后,就具有可追溯的数据类型。
同样,JavaScript中的内置对象在内部构建时也是这样做的,如下图所示。
注意:
1. null 和 undefined 是无效的对象,因此是不会有 constructor 存在的,这两种类型的数据需要通过其他方式来判断。
2. 函数的constructor是不稳定的,这个主要体现在自定义对象上,当开发者重写prototype后,原有的constructor引用会丢失,constructor 会默认为 Object。
4. Object.prototype.toString.call()
toString()是Object的原型方法,调用该方法,默认返回当前对象的[[Class]] 。这是一个内部属性,其格式为[object Xxx],其中Xxx就是对象的类型。对于Object对象,直接调用toString()就能返回[object Object]。而对于其他,则需要通过 call / apply来调用才能返回正确的类型信息。
语法:Object.prototype.toString.call(value);
Object.prototype.toString(Object); // [object Object]
Object.prototype.toString.call('') ; // [object String]
Object.prototype.toString.call(1) ; // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(Symbol()); //[object Symbol]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call(new Date()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
console.log([1,2].toString()); // 1,2 不可以判断数组的类型,因为Array重写了toString()方法
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
Object.prototype.toString.call(new Error()) ; // [object Error]
Object.prototype.toString.call(document) ; // [object HTMLDocument]
Object.prototype.toString.call(window) ; //[object global] window 是全局对象 global 的引用
同样是检测对象obj调用toString方法,obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是为什么?
这是因为toString是**Object的原型方法**,而Array、function等类型作为Object的实例,都重写了toString方法。不同的对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString方法(function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串…),而不会去调用Object上原型toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object原型上的toString方法。
二、判断数组类型的方式
1. 通过Object.prototype.toString.call()做判断
Object.prototype.toString.call(obj).slice(8,-1) === 'Array';
2. 通过原型链做判断
obj.__proto__ === Array.prototype;
3. 通过ES6的Array.isArray()做判断
Array.isArrray(obj);
4. 通过instanceof判断
obj instanceof Array
5. 通过Array.prototype.isPrototypeOf
Array.prototype.isPrototypeOf(obj)