typeof /instanceof /Object.prototype.toString.call( )的内部机制

317 阅读6分钟

数据类型

原始数据类型

字符串、数字、布尔值、undefined、null、symbol、bigint

引用数据类型

object array function Date...

typeof

  • 对于原始类型,包括undefined、boolean、number、symbol、bigint和string,typeof操作符可以准确地判断类型,除了null

image.png

  • 对于引用类型,如对象(Object)、数组(Array)和函数(Function)等,typeof操作符会将它们的类型判断为 "object",除了函数类型会返回 "function" 外。

  • 这是因为引用类型在JavaScript中并没有具体的标识,所以typeof只能将它们归类为 "object",这是一种不够精确的类型判断方式。

image.png

  • 在解释typeof操作符的执行机制时,需要了解到JavaScript引擎对值的内部表示方式。在ECMAScript规范中,所有的值都是以二进制形式存储的。在进行类型判断时,typeof操作符首先会将值转换为对应的二进制形式,然后判断这个二进制值的前三位来确定其类型。

  • 具体来说,对于对象、数组和函数等引用类型,它们在二进制形式上的前三位并不是 "000",而是 "1",因此typeof操作符会将它们判断为 "object" 或 "function"。

  • 而typeof操作符之所以将函数类型判断为 "function",是因为ECMAScript规范中对函数类型的处理与其他引用类型不同。在JavaScript中,函数是一种特殊的对象,具有调用和执行代码的能力。因此,在进行类型判断时,typeof操作符会将函数类型单独判断,并返回 "function"。

instanceof

  • instanceof操作符只能用于判断引用类型,不能用于判断原始类型。

image.png

  • instanceof操作符通过判断一个对象是否属于某个构造函数的实例来进行类型判断。它会检查对象的原型链,判断原型链中是否有该构造函数的原型对象。
// 定义一个构造函数
function Animal(name) {
  this.name = name;
}

// 创建一个Animal的实例
var dog = new Animal('旺财');

// 使用instanceof操作符判断实例的类型
console.log(dog instanceof Animal); // 输出 true,表示dog是Animal的实例

// 定义另一个构造函数,继承自Animal
function Dog(name, breed) {
  Animal.call(this, name); // 调用Animal构造函数来初始化name属性
  this.breed = breed;
}

// 使用原型链继承
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

// 创建一个Dog的实例
var myDog = new Dog('小白', '金毛');

// 使用instanceof操作符判断实例的类型
console.log(myDog instanceof Animal); // 输出 true,表示myDog是Animal的实例
console.log(myDog instanceof Dog); // 输出 true,表示myDog是Dog的实例

  • 当使用obj instanceof Constructor进行判断时,obj是要判断的对象,Constructor是要判断的构造函数。instanceof操作符会检查obj的原型链上是否存在Constructor.prototype,如果存在,则返回true,表示obj是Constructor的实例;如果不存在,则返回false,表示obj不是Constructor的实例。

  • instanceof操作符会遍历obj的原型链,直到找到Constructor.prototype或到达原型链的末尾(即obj的原型为null)。如果在原型链上找不到Constructor.prototype,则instanceof操作符会返回false。

  • 需要注意的是,instanceof操作符不能准确判断对象的具体类型,而是判断对象是否属于某个构造函数的实例或其派生类的实例。换句话说,instanceof操作符只能判断对象是否与某个构造函数有继承关系。

手写instanceof

根据上述instanceof的内部机制,我们可以自己手模一个myInstanceof去深入理解其内核。

function myInstanceof(obj, constructor) {
// 首先检查参数是否合法 
  if (obj === null || typeof obj !== 'object'){ 
    return false; 
  }
  let proto = obj.__proto__; // 获取obj的原型

  while (proto) {
    if (proto === constructor.prototype) {
      return true; // 如果找到了constructor的原型,则返回true
    }
    proto = proto.__proto__; // 继续沿着原型链向上查找
  }
  return false; // 如果整个原型链都没有找到constructor的原型,则返回false
}

Object.prototype.toString.call( )

要理解这个类型转换的方式,首先要理解Object.prototype.toString 和Function.prototype.call这两个方法的执行机制!

Object.prototype.toString

Object.prototype.toString 方法是 JavaScript 中一个基本的内置方法,这个方法的执行机制包含几个步骤,详细解释如下:

  1. 检查 this值是否为 undefined:

    当 toString 方法在 undefined上被调用时,会直接返回字符串 "[object Undefined]"。这是一个特殊的情况,因为 undefined 不是对象。

  2. 检查 this值是否为 null:

    当 toString 方法在 null上被调用时,会直接返回字符串 "[object Null]"。这是另一个特殊情况,因为 null也不是对象。

image.png

  1. 将 this值转换为对象:

    如果 this不是 undefined 或 null,那么 toString方法会通过调用 ToObject 将 this 值转换为对象。ToObject 是一个抽象操作,它将原始值(如数字、布尔值、字符串)转换为对应的包装对象(如 Number、Boolean、String),如果 this本身是对象,则直接返回它。

  2. 获取对象的 [[Class]] 内部属性:

    每个对象在内部都有一个 [[Class]] 属性,用来表示对象的类型。这个属性在规范中表示为一个字符串,比如 "[object Array]" 表示数组,"[object Date]" 表示日期对象等等。

  3. 返回结果字符串

    最后,toString 方法会将字符串 "[object "、[[Class]] 属性值和 "]" 拼接在一起,并返回这个结果。

Function.prototype.call

Function.prototype.call 是 JavaScript 中的一个内置方法,它允许你在调用函数的同时显式地指定函数内部的 this 值,并且可以传递参数列表给被调用的函数。这个方法的内部机制可以通过以下几个步骤来详细解释:

  1. 获取函数对象和 this 值:当你调用 someFunction.call(thisArg, ...args)时,它首先获取 someFunction 函数对象和 thisArg 参数。这里的 thisArg 就是你希望函数内部的 this指向的对象。

  2. 设置 this值: call 方法会将函数内部的 this设置为传递的 thisArg值。这意味着在函数内部,this将引用 thisArg 对象。

  3. 执行函数: 然后,call 方法会立即调用 someFunction 函数,并且将 this值设置为 thisArg。函数会在这个上下文中执行。(也就是thisArg会执行someFunction 函数)

  4. 传递参数: 除了设置 this 值之外,call 方法还可以传递其他参数给被调用的函数。这些参数通过 ...args 传递给被调用的函数。

  5. 返回结果: call 方法返回被调用函数的执行结果。如果被调用的函数有返回值,则 call方法也会返回这个值。

手写call方法

Function.prototype.myCall = function(thisArg, ...args) {
  // 如果 this 不是函数,则抛出错误
  if (typeof this !== 'function') {
    throw new TypeError('Function.prototype.myCall - this is not callable');
  }
  
  // 将 this 设置为 thisArg
  const context = thisArg || window;

  // 创建一个唯一的属性名称以避免污染全局对象
  const fnSymbol = Symbol('fn');

  // 将函数作为上下文对象的属性
  context[fnSymbol] = this;

  // 调用函数并传递参数
  const result = context[fnSymbol](...args);

  // 删除临时属性
  delete context[fnSymbol];

  // 返回执行结果
  return result;
};

示例分析

通过上述我们理解了那两个方法后,我们来通过案例解释其中类型转换的执行过程

image.png

步骤如下:

  • call方法将 42 传递给 toString 作为 this。

  • toString方法接收到 this 值 42。

  • toString 方法将 42 转换为 Number对象。

  • 获取 Number 对象的 [[Class]] 属性值为 "Number"。

  • 返回字符串 "[object Number]"