开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第19天,点击查看活动详情、
数据类型检测作为每一个前端程序员的基本功,在面试中也是会被经常问到,但能够回答的精准完善的并不多,这里我们把各种情况一一摸排一下~
了解数据类型
想要深入数据类型检测,我们就要知道 都有哪些类型?
原始值
所谓原始值,也称之为简单数据类型,便是我们常用的那几种了:null
,undefined
,boolean
,number
,string
以及 ES6 之后新增的两种不常用的: bigInt
,Symbol
简单数据类型有一个要注意的是,可以使用对象包装器声明,我们平时使用的时候都是使用字面量的方式,直接使用其包装对象的情况很少,但是使用不代表我们就可以不了解~ 需要注意的是,这些方法我们可以直接调用其函数,也可以加上 new
以构造函数的方式得到,但是这两者是有很大区别的,除了null
和undefined
其他的简单数据类型都是有包装器的,请食用代码:
// 在这里我们使用一种 数字类型来举例,其他类型原理相同
let a1 = 1;
let a2 = Number(1);
let a3 = new Number(1);
console.log(a1);
console.log(a2);
console.log(a3);
console.log(a1 == a2 == a3);
console.log(typeof a1 == typeof a2);
console.log(typeof a1 == typeof a2 == typeof a3);
console.log(typeof a3);
大家可以想一想,上面的打印都会打印什么呢? 我们在工作当中会使用这个得包装对象去得到原始值吗?下面我们来看一下打印的结果
我们可以看到, 使用 new
和不使用是不一样的,这源于构造函数的输出了,当成 类 和 普通函数肯定是有区别的,当成类,查看控制台,我们可以看到原型是 Number
的以个实例对象,而其带了一个 PrimitiveValue
的值, 顾名思义,原始值, 这个值会在我们访问这个属性的时候,去获取这个原始值,而在我们使用 typeof
运算符的时候不会去访问这个原始值,只会得到这个类,所以得到的是 object
,而使用 typeof
运算法去检测复杂数据类型得到的都是 object
,在面试过程中如果我们把这几点答上来,那无疑会给我们加不少分哦~
引用值
引用值也可以称之为复杂数据类型或者引用数据类型,便是我们常用的对象了,类型为 object
,除了我们手动创建的对象以外,还有 js 内置的对象,包括以下这些: Date
,Map
,Set
,WeakMap
,WeakSet
,Array
,Function
,Promise
,Proxy
......等等,更多可以查看JavaScript标准内置对象,所以说当我们面试的时候,被问到类型或者类型检测的时候,能够把引用类型拓展一下,说出来几点引用类型包含的内置对象的话,也是加分项哦~
检测方法
typeof
- 首先来说第一个
typeof
面试的时候基本都能回答 - 上来这个方法,但是这个方法的优缺点说的不明晰,第一点是
typeof null
为什么是object
第二点是 typeof 侦测对象时不能够准确侦测内置类型~ , 我们先来看一下MDN
给出的返回值标准:
这里我们看到 简单数据类型的判断都是准确的,更精准一点来说,简单数据类型我们在上面的原始值中也已经说了,使用字面量方式得到的简单数据类型或函数方式得到的是准确的,使用构造函数得到的是不准确的。 - 首先来看我们刚才说的第一点, 为什么
null
检测出来是object
呢, 这个我在面试的时候,大部分人也是能够答上来的: 在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于null
代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null
也因此返回"object"
。(参考来源) - 第二点是上面说到的,侦测对象类型是不能够准确的来侦测内置类型,我们都知道 在
JavaScript
中万物皆对象,而而typeof
不会去查找我们检测的对象类型的原型和构造函数,其实这也是万物皆对象这句话的由来了,在面试中我们答出这个缺点以后,就可以很平滑的过度到我们接下来要提到的检测方法~ - 关于这个方法, MDN 给了我们一个完整的方法,用来检测对象, 这个大家可以了解一下,其利用了函数原型方法,字符串方法,
Symbol
方法以及构造器方法:
function type(value) {
if (value === null) {
return "null";
}
const baseType = typeof value;
// 基本类型
if (!["object", "function"].includes(baseType)) {
return baseType;
}
// Symbol.toStringTag 通常指定对象类的“display name”
// 它在 Object.prototype.toString() 中使用。
const tag = value[Symbol.toStringTag];
if (typeof tag === "string") {
return tag;
}
// 如果它是一个函数,其源代码以 "class" 关键字开头
if (
baseType === "function" &&
Function.prototype.toString.call(value).startsWith("class")
) {
return "class";
}
// 构造函数的名称;例如 `Array`、`GeneratorFunction`、`Number`、`String`、`Boolean` 或 `MyCustomClass`
const className = value.constructor.name;
if (typeof className === "string" && className !== "") {
return className;
}
// 在这一点上,没有合适的方法来获取值的类型,因此我们使用基本实现。
return baseType;
}
constructor
这个属性也叫做构造器,并不能够称之为一种方法,这种方法利用了原型的属性和对象的访问机制,使用的时候是需要使用点语法使用这个方法的,这就巧妙了,当我们在简单数据类型中使用这个属性的时候,这个简单数据类型自身其实是没有这个方法的,这时候会把这个简单数据类型进行包装对象的操作,这时候一个简单数据类型就有了原型,就有了原型属性,我们使用字符串的方法其实就是这个原理~。
- 下面我写了一些简单的例子,大家看完便可以一通百通,需要注意的是,
null
和undefined
不可以使用这个方法,会报错,因为这个两个值没有原型。
let a = new Array([]);
let a1 = [];
let c = new Object({});
let c2 = {};
let d = 100;
let d2 = Number(100);
let d3 = new Number(100);
let e = Symbol(100);
let f = Date.now(); // 当前时间时间戳
let f2 = new Date()
console.log(a.constructor === Array);
console.log(a1.constructor === Array);
console.log(c.constructor === Object);
console.log(c2.constructor === Object);
console.log(d.constructor === Number);
console.log(d2.constructor === Number);
console.log(d3.constructor === Number);
console.log(true.constructor === Boolean);
console.log(e.constructor === Symbol);
console.log(f.constructor === Number);
console.log(f2.constructor === Date);
class DemoA {
constructor(){
this.a = 100
}
}
class DemoB {
constructor(){
this.b = 200
}
}
DemoA.constructor = DemoB
let res = new DemoA()
console.log(res.constructor); // DemoB
以上所有的打印都是 true
, 这里需要注意的是使用Date
如果返回的是时间戳,时间戳是数值类型,日期对象才是Date
类型,还需要注意的是 constructor
是可以被修改的!
instanceof
这个主要使用在实例化对象中,用来判断前面的是否是后面的实例化对象,判断不了基本数据类型,前面的对象必须是后面的构造函数的实例化对象,什么是前面? 请看语法~
- 语法:对象 instanceof 构造函数
- 返回值: true 是后面的构造的 false 不是后面的构造的
let a = new Array([]);
let a1 = [];
let c = new Object({});
let c2 = {};
let d = 100;
let d2 = Number(100);
let d3 = new Number(100);
let e = Symbol(100);
let f = Date.now();
let f2 = new Date()
console.log(a instanceof Array);
console.log(a1 instanceof Array);
console.log(c instanceof Object);
console.log(c2 instanceof Object);
console.log(d instanceof Number); // false
console.log(d2 instanceof Number); // false
console.log(d3 instanceof Number);
console.log(e instanceof Symbol); // false
console.log(f instanceof Date); // false
console.log(f2 instanceof Date);
class DemoA {
constructor(){
this.a = 100
}
}
class DemoB extends DemoA {}
let res = new DemoB()
console.log(res instanceof DemoA); // true
console.log(res instanceof Function); // false
console.log(res instanceof Object);// true
上面结果没有写注释 false
的 都是 ture
, 可以看到,简单数据类型和函数式 得到的是不生效的,这个方法只对实例对象有效,会去查找原型链,而需要注意的是使用继承的时候,最后那个 res
的原型链中 是有继承的父类的,所以是 true
。
Object.prototype.toString.call()
终极方法来了~ 这个方法是利用顶级原型对象上的
toString
方法,用call(apply)
调用,使用call
或者apply
效果一样,面试的时候大部分人也能够回答上来这个方法,但有的人竟然说返回值是数组,请记住,返回值是字符串,只是字符型里面长得像数组而已,包含两项,第一项其实就是顶级原型也就是 Object,第二项就是其类型,第一项小写开头,第二项大写开头。
- 那么这个方法是如何实现的呢?
每一个简单数据类型都有一个自己的 toString方法, 如使用 XX.proto.proto.toString() 方法 则返回的是[object Object], 因为是 XX.proto.proto 调用的 toString() , 原生对象肯定是对象, 所以需要使用 call语法 =======> Object.prototype.toString.call(* ) ==== *.toString() 只是用的是 顶级原型上的 toString 方法而已 顶级原型上的 toString 方法 实际上是返回的构造器 - 语法:
Object.prototype.toString.call(***)
- 返回值:
[object 类型]
让我们来看一下MDN
关于这个方法的描述:
段落还是比较长的,记住其用法就可以,底层是如何实现的并不重要,面试的时候我也不会去问这种方法的实现原理,因为能够把这个方法说明白的其实就只占少数了。下面来看看其返回值(返回值我没有用字符串包裹,但记得返回的是字符串哦不是数组!)
Object.prototype.toString.call(123)---------------> [object Number]
Object.prototype.toString.call('')----------------> [object String]
Object.prototype.toString.call(ture)--------------> [object Boolean]
Object.prototype.toString.call(undefined)---------> [object Undefined]
Object.prototype.toString.call(null)--------------> [object Null]
Object.prototype.toString.call(new Date())--------> [object Date]
Object.prototype.toString.call({})----------------> [object Object]
Object.prototype.toString.call([])----------------> [object Array]
Object.prototype.toString.call(function(){})------> [object Function]