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

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

目录:

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

1. 手写实现typeof

实习生会去网上抄答案

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

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

  1. typeof [] === 'array' // false
  2. myTypeof([]) === 'array' // true

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

  • 思考:穷举法一一判断是否可取?

    • 不可取,没这么多方法


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

  1. function myTypeof(params){
  2. const type = Object.prototype.toString.call(params).slice(8, -1).toLowerCase()
  3. const map = {
  4. 'number': true,
  5. 'string': true,
  6. 'boolean': true,
  7. 'undefined': true,
  8. 'bigint': true,
  9. 'symbol': true,
  10. 'function': true
  11. }
  12. return map[type] ? type : 'object'
  13. }
  14. // 测试用例
  15. myTypeof(1)
  16. myTypeof('')
  17. myTypeof(false)
  18. myTypeof(null)
  19. myTypeof(undefined)
  20. myTypeof(10n) // bigint
  21. myTypeof(Symbol())
  22. myTypeof(() => {})
  23. myTypeof([])
  24. 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)

  1. Array: 1000100010001000
  2. null: 0000000000000000
  3. typeof [] // "object"
  4. typeof null // "object"

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

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

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

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

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

ObjectRoot.png

Object.prototype.toString.call原理是:

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

3. 递归实现instanceof

  1. const myInstanceof = (obj, Fn) => {
  2. if (typeof obj !== 'object') return false
  3. const structure = obj.__proto__
  4. if (structure === null) return false
  5. if (structure !== Fn.prototype) {
  6. return myInstanceof(structure, Fn)
  7. } else {
  8. return true
  9. }
  10. }
  11. // 测试用例
  12. myInstanceof([], Array) // true
  13. myInstanceof({}, Object) // true
  14. myInstanceof(/1/, RegExp) // true
  15. const Fn1 = function () {}
  16. const a = new Fn1()
  17. myInstanceof(a, Fn1) // true
  18. myInstanceof(a, Object) // true
  19. myInstanceof(1, Number) // false
  20. myInstanceof('', String) // false
  21. 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

码字不易,点赞鼓励