【笔试知识点总结】JS中的typeof、instance of和Object.prototype.toString.call()

108 阅读5分钟

【第1295期】浅谈 instanceof 和 typeof 的实现原理 (qq.com)

typeof - JavaScript | MDN (mozilla.org)

typeof的作用与实现原理

typeof的作用

typeof用来判断运算符的类型,将类型信息作为字符串返回,对于七个基本数据类型来说,除了typeof null会返回object,其他的都返回其本身的数据类型

类型结果
Undefined"undefined"
Null"object"原因
Boolean"boolean"
Number"number"
BigInt"bigint"
String"string"
Symbol"symbol"
Function(在 ECMA-262 中实现 [[Call]];classes也是函数)"function"
其他任何对象"object"

示例

需要注意的是:typeof NaN === "number";

// 数值
typeof 37 === "number";
typeof 3.14 === "number";
typeof 42 === "number";
typeof Math.LN2 === "number";
typeof Infinity === "number";
typeof NaN === "number"; // 尽管它是 "Not-A-Number" (非数值) 的缩写
typeof Number(1) === "number"; // Number 会尝试把参数解析成数值
typeof Number("shoe") === "number"; // 包括不能将类型强制转换为数字的值

typeof 42n === "bigint";

// 字符串
typeof "" === "string";
typeof "bla" === "string";
typeof `template literal` === "string";
typeof "1" === "string"; // 注意内容为数字的字符串仍是字符串
typeof typeof 1 === "string"; // typeof 总是返回一个字符串
typeof String(1) === "string"; // String 将任意值转换为字符串,比 toString 更安全

// 布尔值
typeof true === "boolean";
typeof false === "boolean";
typeof Boolean(1) === "boolean"; // Boolean() 会基于参数是真值还是虚值进行转换
typeof !!1 === "boolean"; // 两次调用 !(逻辑非)运算符相当于 Boolean()

// Symbols
typeof Symbol() === "symbol";
typeof Symbol("foo") === "symbol";
typeof Symbol.iterator === "symbol";

// Undefined
typeof undefined === "undefined";
typeof declaredButUndefinedVariable === "undefined";
typeof undeclaredVariable === "undefined";

// 对象
typeof { a: 1 } === "object";

// 使用 Array.isArray 或者 Object.prototype.toString.call
// 区分数组和普通对象
typeof [1, 2, 4] === "object";

typeof new Date() === "object";
typeof /regex/ === "object";

// 下面的例子令人迷惑,非常危险,没有用处。避免使用它们。
typeof new Boolean(true) === "object";
typeof new Number(1) === "object";
typeof new String("abc") === "object";

// 函数
typeof function () {} === "function";
typeof class C {} === "function";
typeof Math.sin === "function";

new操作符的判断

typeof在判断用new构造出来的对象时,会返回objectfunction,而不会告诉我们具体是哪种类型的对象,此时可以使用instanceof来判断。

只有new Function()在使用typeof判断时返回function

const str = new String("String");
const num = new Number(100);

typeof str; // "object"
typeof num; // "object"

const func = new Function();
typeof func; // "function"

typeof的实现原理

我们可以先想一个很有意思的问题,js 在底层是怎么存储数据的类型信息呢?或者说,一个 js 的变量,在它的底层实现中,它的类型信息是怎么实现的呢?

其实,js 在底层存储变量的时候,会在变量的机器码的低位1-3位存储其类型信息👉

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

but, 对于 undefined 和 null 来说,这两个值的信息存储是有点特殊的。

null:所有机器码均为0

undefined:用 −2^30 整数来表示

所以,typeof 在判断 null 的时候就出现问题了,由于 null 的所有机器码均为0,因此直接被当做了对象来看待。 然而用 instanceof 来判断的话👉

null instanceof null // TypeError: Right-hand side of 'instanceof' is not an object

null 直接被判断为不是 object,这也是 JavaScript 的历史遗留bug,可以参考typeof。

因此在用 typeof 来判断变量类型的时候,我们需要注意,最好是用 typeof 来判断基本数据类型(包括symbol),避免对 null 的判断。

还有一个不错的判断类型的方法,就是Object.prototype.toString,我们可以利用这个方法来对一个变量的类型来进行比较准确的判断

Object.prototype.toString.call(1) // "[object Number]"
Object.prototype.toString.call('hi') // "[object String]"
Object.prototype.toString.call({a:'hi'}) // "[object Object]"
Object.prototype.toString.call([1,'a']) // "[object Array]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(() => {}) // "[object Function]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(Symbol(1)) // "[object Symbol]"

instance of

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

let person = function () {
}
let nicole = new person()
nicole instanceof person // true

当然,instanceof 也可以判断一个实例是否是其父类型或者祖先类型的实例。

let person = function () {
}
let programmer = function () {
}
programmer.prototype = new person()
let nicole = new programmer()
nicole instanceof person // true
nicole instanceof programmer // true

实现原理

以下是一个类似instance of实现原理的代码,可以看出,instance of就是沿着对象实例的原型链不断向上寻找右边对象的原型对象是否存在。

function new_instance_of(leftVaule, rightVaule) { 
    let rightProto = rightVaule.prototype; // 取右表达式的 prototype 值
    leftVaule = leftVaule.__proto__; // 取左表达式的__proto__值
    while (true) {
        if (leftVaule === null) {
            return false;    
        }
        if (leftVaule === rightProto) {
            return true;    
        } 
        leftVaule = leftVaule.__proto__ 
    }
}

关于原型链,可以查看该文章中的原型链部分前端面试题目汇总 - 掘金 (juejin.cn),总结起来就几句话,

  • 当 new 一个函数的时候会创建一个对象,『函数.prototype』 等于 『被创建对象.proto

  • 一切函数都是由 Function 这个函数创建的,所以『Function.prototype === 被创建的函数.proto

  • 一切函数的原型对象都是由 Object 这个函数创建的,所以『Object.prototype === 一切函数.prototype.proto

function People(){}
var p = new People()
p.__proto__ === People.prototype 
People.__proto__ === Function.prototype
People.prototype.__proto__ === Object.prototype

Object.prototype.toString.call()

该方法能够准确输出一个实例对象的类型,但是对于那些我们自定义的对象, 该方法无法识别,只会返回object,这种情况下可以使用instance of

console.log(Object.prototype.toString.call("jerry"));//[object String]
console.log(Object.prototype.toString.call(12));//[object Number]
console.log(Object.prototype.toString.call(true));//[object Boolean]
console.log(Object.prototype.toString.call(undefined));//[object Undefined]
console.log(Object.prototype.toString.call(null));//[object Null]
console.log(Object.prototype.toString.call({name: "jerry"}));//[object Object]
console.log(Object.prototype.toString.call(function(){}));//[object Function]
console.log(Object.prototype.toString.call([]));//[object Array]
console.log(Object.prototype.toString.call(new Date));//[object Date]
console.log(Object.prototype.toString.call(/\d/));//[object RegExp]

function Person(){};
console.log(Object.prototype.toString.call(new Person));//[object Object]

三者的区别

  1. typeof可以用来判断除了null之外的基本数据类型和function,但无法识别引用数据类型
  2. instance of 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,适合判断我们自定义的对象类型,但也只能判断对象类型,无法判断基本数据类型
  3. Object.prototype.toString.call()能够准确输出一个实例对象的类型,但是对于那些我们自定义的对象, 该方法无法识别