js类型判断的底层剖析typeof/instanceof以及call底层原理
前言
今天来讨论一下js中的数据类型判断,typeof、instanceof的区别,通过查阅官方文档查看这些方法的底层逻辑,并在了解底层之后自己手撕方法,同时带小白朋友看一看call的底层逻辑。
typeof()方法
// 原始数据类型有一下7种
let s = '123' // string
let n = 123 // number
let f = true // boolean
let u = undefined // undefined
let sy = Symbol(123) // Symbol
let big = 1234n // BigInt
let nu = null // null
// --------------------------------------------
console.log(typeof(s)); // string
console.log(typeof(n)); // number
console.log(typeof(f)); // boolean
console.log(typeof(u)); // undefined
console.log(typeof(sy)); // symbol
console.log(typeof(big)); // bigint
console.log(typeof(nu)); // object
// 引用类型-------------------------------------
let obj = {}
let arr = []
let fn = function () { }
let date = new Date()
// ----------------------------------------------
console.log(typeof (obj)); // object
console.log(typeof (arr)); // object
console.log(typeof (date)); // object
console.log(typeof (fn)); // function
-
原始数据类型的
二进制
前三位都是1,0没有意义,而引用数据类型的前三位都是0,就算不是0也会补齐3个0,因此js官方在打造这个typeof方法时就利用二进制这个特性,如果前三位是0则判断为基本数据类型(原始),如果是1则为引用数据类型,判断结果为object
。 -
但是
null
这个数据结构当年被打造出来时,被转换成二进制是一串零,因此null也会被typeof方法判定为object
。
typeof()结论:
- 可以判断除了null之外的所有原始类型
- 除了funciton之外,所有的引用类型都会被判断成object
手写一个方法判断是否为对象
基于前面对typeof方法的讲解,我们利用他的特性来手写一个判断传进来的值是否为对象的方法。
function isObject(obj) {
if (typeof obj == 'object' && obj != null && !(obj instanceof Function)) {
return true;
}
return false;
}
let res = isObject({ a: 1 });
console.log(res);
- 判断是否为object的同时要把null排除在外,因为null也会被判断成object
- 如果传进去是一个函数,typeof返回一个function,因此也会被错误的判断,因此还需要判断它非函数
instanceof方法
// 7个基本数据类型
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); // true
console.log(arr instanceof Array); // true
console.log(fn instanceof Function); // true
console.log(date instanceof Date); // true
// 判断基本数据类型失败
console.log(s instanceof String); // false
console.log(n instanceof Number); // false
但是如果instanceof判断array是不是object呢?答案也是true。
arr.__proto__ = Array.prototype
Array.prototype.__proto__ = Object.prototype
- 因此我们能够观见,
instanceof
用于检查原型链,而所有数组在JavaScript中都是Object
的实例,因为Array
实际上是Object
的一个子类。这意味着arr instanceof Object
会返回true
,这同样适用于所有其他内置的复杂数据类型,如Date
,Function
,Map
,Set
, 等。 - 基本数据类型没有原型,这也就是为什么instanceof不能判断原始类型了
instanceof总结:
- 只能用来判断引用数据类型
- 通过原型链查找来判断类型
手写myInstanceOf方法
/**
* 自定义的instanceof检查函数。
* @param {Object} L - 左侧对象,需要检查是否是右侧构造函数的实例。
* @param {Function} R - 右侧构造函数,用来检查左侧对象是否是其实例。
* @returns {boolean} 如果L是R的实例,则返回true;否则返回false。
*/
function myinstanceof(L, R) {
// 当L不为null时,继续检查
while (L !== null) {
// 如果L的__proto__等于R的prototype,则说明L是R的实例
if (L.__proto__ === R.prototype) {
return true;
}
// 将L更新为其原型,继续向上遍历原型链
L = L.__proto__;
}
// 如果遍历完原型链仍未找到匹配的prototype,则L不是R的实例
return false;
}
// 测试代码
console.log(myinstanceof([], Array)); // true
console.log(myinstanceof([], Object)); // true
console.log(myinstanceof({}, Array)); // false
Object.prototype.toString(xxx)
var a = {}
Object.prototype.toString(a)
查阅官方文档:15.2.4.2
- 判断传入的是否为unll/undefined,是则返回[object Undefined]/[object Null]
- 如果不是,则调用ToObject(),将O作为ToObject(xxx)的执行结果
- 调用ToObject()传入如果是原始数据类型,则转换成对象,是Boolean,则new Boolean(),数字则new一个Number
- 如果传入的是一个对象,结果就是这个对象
- 定义一个class作为内部属性 [[class]] 的值
- 返回由 "[object" 和 class 和 "]" 组成的字符串
- 因此如果我们Object.prototype.toString(123),他会返回[object Object],因为不管传入什么进去他都会调用ToObject方法转换成一个对象,然后读取这个对象的
内部属性Class
,得到Object,因此这个方法并不能有效判断
数据类型,我们需要结合call方法
Object.prototype.toString.call(xxx)
var obj = {
a: 1
}
function foo() {
console.log(this.a);
}
foo.call(obj)
这是官方打造的call方法,将this指到了obj身上,接下来我们自己手写一个mycall,想让函数foo能直接用mycall方法,我们就需要把mycall写到函数的原型上
,但是call只有函数能用,对象是不能调用call方法的,因此我们打造一个mycall也需要进行类型判断
,判断调用它的是不是函数,foo.mycall(obj),这是一种隐式绑定规则,隐式绑定规则的mycall里的this就指向调用它的对象(foo),因此我们要判断调用mycall的那个东西是不是函数,只要判断this是不是函数就可以了。
手写mycall方法(低阶版,适合小白朋友学习)
Function.prototype.mycall = function (context) {
// 判断调用它的是不是函数体
if (typeof this !== 'function') {
return new TypeError(this + 'is not a function')
}
// this里面的this => context
// 防止与其他变量冲突
const fn = Symbol('key')
// 往对象身上挂一个属性key,值为这个函数体
context[fn] = this // 让对象拥有该函数 context={Symbol('key'): foo}
context[fn]() // 偷偷触发隐式绑定,让这个函数的this指向这个对象
delete context[fn] // 删除掉我们给对象身上挂的属性,否则this确实改变指向了,但是别人却多了一个属性出来坏事。
}
foo.mycall(obj)
console.log(obj);
那么为什么Object.prototype.toString.call(xxx)能够准确判断数据类型?
我们刚刚讨论了Object.prototype.toString方法,看到了通过这个方法一定会调用ToObject方法,转换成一个对象,而当直接在对象上调用 toString
方法时,它会返回一个表示该对象的字符串描述。
对于不同类型的对象,这个描述的格式是特定的,通常以 [object Type]
的形式,其中 Type
是对象的具体类型。
例如,对于一个数字,数组,字符串,函数,日期等,toString
方法的返回值分别如下:
- 数字:
"[object Number]"
- 字符串:
"[object String]"
- 数组:
"[object Array]"
- 函数:
"[object Function]"
- 日期:
"[object Date]"
- 对象:
"[object Object]"
- 正则表达式:
"[object RegExp]"
- 其他内置对象也有类似的返回值。
当我们使用call方法来调用toString,就显示地指定了调用这个方法的对象,最终相当于xxx.toString()
Array.isArray(x)
判断是否为数组,但是只能给数组用。比较简单,不做拓展。
小结
本文详细剖析了js中数据类型判断的几种方法(typeof、instanceof、Object.prototype.toString.call()、Array.isArray())的底层原理。
大家也可以自己去手撕一个youcall方法,明晰底层逻辑,相信你一定能更加通透。
转载自:https://juejin.cn/post/7377741132823904292