likes
comments
collection
share

js类型判断的底层剖析typeof/instanceof以及call底层原理

作者站长头像
站长
· 阅读数 33

前言

今天来讨论一下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()结论:

  1. 可以判断除了null之外的所有原始类型
  2. 除了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总结:

  1. 只能用来判断引用数据类型
  2. 通过原型链查找来判断类型

手写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)

js类型判断的底层剖析typeof/instanceof以及call底层原理

查阅官方文档:15.2.4.2

js类型判断的底层剖析typeof/instanceof以及call底层原理

  1. 判断传入的是否为unll/undefined,是则返回[object Undefined]/[object Null]
  2. 如果不是,则调用ToObject(),将O作为ToObject(xxx)的执行结果

js类型判断的底层剖析typeof/instanceof以及call底层原理

  • 调用ToObject()传入如果是原始数据类型,则转换成对象,是Boolean,则new Boolean(),数字则new一个Number
  • 如果传入的是一个对象,结果就是这个对象
  1. 定义一个class作为内部属性 [[class]] 的值
  2. 返回由 "[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
评论
请登录