类型判断在JS中大体有四种方法,这里话不多说直接上干货。
案例引用
function add(a,b) {
return a+b
}
add( '1', 2 )
在这里你要返回a与b的和,而在传入实参的过程中,一个是字符串,一个数字类型,类型不同的加和,返回值是什么类型?运算结果输出就有误。这里就要将传入给形参的值进行判断统一类型进行计算:
function add(a, b) {
if (typeof(a) === 'number' && typeof b === 'number') {
return a + b;
}
console.log('参数类型不对');
}
console.log(add(1, 2));
console.lod(add('1',2))
用typeof 方法进行一个if类型判断,如果a和b的值都是number类型执行加和操作,否则返回类型不同。
所以之后的学习中要学会类型的判断,才能计算出相应的问题,接下来详细的介绍下类型的判断方法:
类型判断
1. typeof
在JS中,原始类型有七大类:
let s = '123'
let n = 123
let f = true
let u = undefined
let nu = null
let sy = Symbol(123) // Symbol
let big = 1234n // BigInt
分别是字符串,数字,布尔值,undefined,null,Symbol和BigInt,除此之外还有一些对象:
let obj = {}
let arr = [ ]
let fn = function() {}
let date = new Date()
空对象,数组,函数体,Date...
let s = '123' // string
let n = 123 // number
let f = true // boolean
let u = undefined // undefined
let nu = null // null
let sy = Symbol(123) // Symbol
let big = 1234n // BigInt
let obj = {}
let arr = []
let fn = function () { }
let date = new Date()
console.log(typeof(s));
console.log(typeof(n));
console.log(typeof(f));
console.log(typeof(u));
console.log(typeof(sy));
console.log(typeof(big));
console.log(typeof(nu));
console.log(typeof (obj));
console.log(typeof (arr));
console.log(typeof (date));
console.log(typeof (fn));
分别执行出结果看看typeof的判断效果:
这里其他的原始类型都判断没有问题,而null确是object,所以type可以判断除 null 之外的所有原始类型。
而在对象中 typeof判断除了 function 其他所有的引用类型都会被判断成 obiect,这里原理是什么:
typeof进行判断时,typeof 是通过将值转换为二进制后判断其二进制前三位是否为0,是则为 object,所以引用类型二进制的前三位是000,function判断成function记住就好,而所有原始类型前三位是111,这里null确实存在bug,当年被打造出来转换成二进制全是0,毫无疑问判断成object,现如今存在多种因素目前无法更改。
function isObject(o) {
if (typeof (o) === 'object' && o !== null) {
return true
}
return false
}
let res = isObject({ a: 1 })
console.log(res);
所以我们这里用typeof进行是不是对象的判断,强调一点,null这个记得去除。
所以typeof:
- 可以判断除 null 之外的所有原始类型
- 判断除了 function 其他所有的引用类型都会被判断成 obiect
- typeof 是通过将值转换为二进制后判断其二进制前三位是否为0,是则为 object
2. instanceof
let s = '123' // string
let n = 123 // number
let f = true // boolean
let u = undefined // undefined
let nu = null // null
let sy = Symbol(123) // Symbol
let big = 1234n // BigInt
let obj = {}
let arr = []
let fn = function () { }
let date = new Date()
console.log(obj instanceof Object);
console.log(arr instanceof Array);
console.log(fn instanceof Function);
console.log(date instanceof Date);
console.log(s instanceof String);
console.log(n instanceof Number);
instanceof的用法是:x instanceof 某种数据类型,返回true和false。
根据运行结果你会发现,引用类型可以判断,而原始类型却不能判断,也就是说:instanceof只能判断引用类型。
但是在这里你会发现,将数组判断成对象返回的也是true。
这里的关系就是原型之间,一个实例对象的隐式原型会等于构造这个函数的显示原型:
arr.__proto__ = Array.prototype
Array.prototype.__proto__ = Object.prototype
这里原型不太明白的可以参考我的文章:原型
instanceof的构建机制是什么?这里构建一个myinstanceof来阐述一下:
function myinstanceof(L, R) {
while (L !== null) {
if (L.__proto__ === R.prototype) {
return true;
}
L = L.__proto__
}
return false
}
console.log(myinstanceof([], Array)); // true
console.log(myinstanceof([], Object)); // true
console.log(myinstanceof({}, Array)); // false
我们通过L的隐式原型等于R的显示原型来返回true 值,第一层L的隐式原型没有,那就让L的隐式运行一直构建多层迭代直到到达R的显示原型,若是没有,返回false,这里记住排除null,因为没有隐式原型,所以instanceof为什么不能判断原始数据类型,其根本就是没有原型。
所以instanceof:
- 只能判断引用类型
- 通过原型链查找来判断类型
3. Object.prototype.toString.call(xxx)
这里有 官方文档 大家可以多去了解。
先看这个方法的实现:
后面就判断除了这里的类型。先介绍一下Object.prototype.toString,在上面的官方文档中,有这样一段:
这里翻译过来就是这样:
- If the this value is undefined, return "[object Undefined]".
- If the this value is null, return "[object Null]".
- 将 O 作为 ToObject(this) 的执行结果
- 定义一个class作为内部属性 [[class]] 的值
- 返回由 "[object" 和 class 和 "]" 组成的字符串
把这里记住就行,理解成一个概念就好。在这里 Object.prototype.toString(a),ToObject(a)你把你把任何一个值传进来,它都会把他转换成对象。
所以这里也就是为什么会对象会转换成这样字符串:
但在这里仍不能判断数据类型,加上call才能判断
所以call的原理在哪里?就是用call将toString里面的值强行摆正,看个例子:
var obj = {
a: 1
}
function foo() {
console.log(this.a);
}
foo.call(a)
这里call将this里面的指向指到 obj 中,这样才能访问到 obj 中 a 的值,关于this的指向不太明白的也可以访问我的文章:this了解指向问题。
对于call的理解,这里创建一个mycall来演示call的运行机制:
Function.prototype.mycall = function (context) {
// 调用我的那个哥们是不是函数体
if (typeof this !== 'function') {
return new TypeError(this + 'is not a function')
}
// this里面的this => context
const fn = Symbol('key')
context[fn] = this // 让对象拥有该函数 context={Symbol('key'): foo}
context[fn]() // 触发了隐式绑定
delete context[fn]
}
foo.mycall(obj)
console.log(obj);
通俗的来讲,就是让在调用的对象中创建该函数,再调用这个函数(让其触发隐式绑定),最后在删除该函数
-
context [fn] = this // 让对象拥有该函数 context={Symbol('key'): foo}
-
context [fn] () // 触发了隐式绑定
-
delete context [fn]
至于整段代码来说,在函数体中就行调用,判断调用mycall的是不是函数体,返回一个值,再将this中的context创建出对象Key值为函数体this,最后执行上面三部的操作。说到底,这里就是将foo的方法借到obj中去用,再删除,这就是call的运行原理。
这里还有一个疑问,对于 foo.mycall(obj)中如何在obj中传入参数?可以自行手操一下,关注我,后面来进行解答。
所以为什么Object.prototype.toString.call(xxx)可以判断一个变量的类型?
在这个方法中 Object.prototype.toString.call(xxx),是call将()里面的值让toString进行调用即: xxx.toString的调用,由于其调用导致无论如何某某的值都会是对象,最后来判断出对象的类型。
4. Array.isArray(x)
这只能判断数组,数组返回true 否则返回false 。
总结:
类型判断的四种方法可以搞清楚对应优点,找出特例,这会在之后的学习中事半功倍。分享结束,感谢支持!