手写实现typeof和instanceof,并了解原理

3,669 阅读3分钟

手写实现typeof和instanceof,并了解原理

最近在和实习生讲这两的原理,并让他们手写实现,他们中间遇到了些困难,此处顺便整理一下

目录:

  1. 手写实现typeof
    • typeof原理解析
  2. Object.prototype.toString.call 生效原理是什么?
  3. 递归实现instanceof
    • instanceof原理解析

1. 手写实现typeof

实习生会去网上抄答案

function myTypeof(obj){
  return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
}

但实际上,这并不是typeof,上面的功能,超出了typeof的能力,例如:

typeof [] === 'array' // false

myTypeof([]) === 'array' // true

我们都知道typeof是判断基本类型的,基本类型有7种: Undefined, null, Boolean, Number, String, bigint, symbol(es6),还能判断出function 然后其他都是object(null是object)

  • 思考:穷举法一一判断是否可取?
    • 不可取,没这么多方法

那只能用 Object.prototype.toString.call ,需要做一个map匹配

function myTypeof(params){
  const type = Object.prototype.toString.call(params).slice(8, -1).toLowerCase()
  const map = {
    'number': true,
    'string': true,
    'boolean': true,
    'undefined': true,
    'bigint': true,
    'symbol': true,
    'function': true
  }
  return map[type] ? type : 'object'
}

// 测试用例
myTypeof(1)
myTypeof('')
myTypeof(false)
myTypeof(null)
myTypeof(undefined)
myTypeof(10n) // bigint
myTypeof(Symbol())
myTypeof(() => {})
myTypeof([])
myTypeof({})

typeof原理解析

猜想js的源码肯定不会这么实现(这样有点挫ー ー;)

参考其他的语言,猜想:不同的 基础类型 在内存中都以二进制形式存在,例如:0x00000000。然后为了区分不同的类型会有标识位,比如 (下面的是末位数)

  • 000: 对象
  • 010: 浮点数
  • 100:字符串
  • 110: 布尔
  • 1: 整数

另外:typeof null 为"object"

  • 猜想原因是因为 不同的对象在底层都表示为二进制,在Javascript中二进制前(低)三位都为0的话会被判断为Object类型,null的二进制表示全为0,自然前三位也是0,所以执行typeof时会返回"object"。

举个一个不恰当的例子,假设所有的Javascript对象都是16位的,也就是有16个0或1组成的序列,猜想如下: (末尾数都是 000,会被识别为object)

Array: 1000100010001000
null:  0000000000000000

typeof []  // "object"
typeof null // "object"

为什么Array的前三位不是100?

  • 因为二进制中的“前”一般代表低位, 比如二进制00000011对应十进制数是3,它的前三位是011。

2. Object.prototype.toString.call 生效原理是什么?

首先了解一个概念:在js中,一切皆对象。一切皆来自一个”根“原型

  • 可以理解成有个“根“原型,创造各类构造函数(包括Object构造函数)。“根“原型类似祖先,在最顶层,在往上就是null
  • “根”原型 === Object.prototype
    • Object.prototype === Array.prototype.__proto__
    • Array.prototype.__proto__ === String.prototype.__proto__

ObjectRoot.png

Object.prototype.toString.call原理是:

  • “根”原型(Object.prototype)下,有个toString的方法,记录着所有 数据类型(构造函数)
  • .call作用是改this指向。让传入的对象,执行 “根”原型的toString方法

3. 递归实现instanceof

const myInstanceof = (obj, Fn) => {
  if (typeof obj !== 'object' || !obj) return false
  const structure = obj.__proto__
  if (structure !== Fn.prototype) {
    return myInstanceof(structure, Fn)
  } else {
    return true
  }
}

// 测试用例
myInstanceof([], Array) // true
myInstanceof({}, Object) // true
myInstanceof(/1/, RegExp) // true
const Fn1 = function () {}
const a = new Fn1()
myInstanceof(a, Fn1) // true
myInstanceof(a, Object) // true
myInstanceof(1, Number) // false
myInstanceof('', String) // false
myInstanceof(new String('11'), String) // true

instanceof原理解析

构造函数.prototype(例如 Array.prototype) 可以得到 构造函数的原型

  • 构造函数指的是: 例如:Array,RegExp,Object 对象.__proto__ (例如:[].__proto__) 也可以得到 构造函数的原型

举例:Array.prototype === [].__proto__ // true

举例:Object.prototype === {}.__proto__ // true

其他的就是递归:如果没找到匹配的,就会递归往上层去找。

  • 如果找到,返回true。
  • 一直没找到的话,最终会到达null,结束递归,返回false

码字不易,点赞鼓励