面试官:讲解下Js的判断数据类型方法和其原理

145 阅读7分钟

Js的数据类型种类

Js中一共有8种数据类型,其中有7种 原始数据 类型,分别是 string number boolean undefined null symbol bigint,一种 引用类型 叫 object。object,array,function,Data等都是object的一部分。

类型判断的方法

  1. typeof 可以准确的判断除了null以外的原始类型,不能判断引用类型,除了function
  2. instanseof只能判断引用类型,不能判断原始类型。
  3. Object.prototype.toString.call(x) 可以判断所有的类型。

typeof

typeof能够准确的判断除了null,以外的原始数据类型,而且不能区分引用类型,除了function。

  • 原因:typeof的工作原理是将数据转化为二进制字符串,检查前3个字符来区分数据的类型。因为null在转化为二进制字符串后是一串0,而object数据类型转化的二进制字符串前3个是0,null类型和object类型前3个字符都是0,所以typeof会把null数据类型输出object。function在Js的v8引擎中是一个独特的object,typeof会检查将function类型检查为fuction,而不是function。
console.log(typeof 'hello');
console.log(typeof 123);
console.log(typeof true);
console.log(typeof undefined);
console.log(typeof Symbol(1));
console.log(typeof 111n);
console.log(typeof null);  // object

console.log(typeof {});
console.log(typeof []);
console.log(typeof new Date());
console.log(typeof function () { });

image.png

instanseof

instanseof只能判断引用类型,不能判断原始类型。

  • 原因:instanseof是通过原型链来检测是否属于该类型。因为原始类型没有原型,所以不能判断是否属于某类型。
  • instanseof是通过原型链向下查找,看它的隐式原型是否和构建它的函数的现式原型相同。

instanseof的使用方法,前方放入要检测的数据结构,后方放入一个构造函数,看这个构造函数能不不能通过原型链查找到。

console.log({} instanceof Object);
console.log([] instanceof Array);
console.log(new Date() instanceof Date);
console.log(function () { } instanceof Function);
console.log(new Date() instanceof Object);

console.log('hello' instanceof String);
console.log(123 instanceof Number);
console.log(true instanceof Boolean);

image.png

原型链是什么东西呢

原型是是一种特殊的属性,存在于函数和对象里,它有继承和共享的属性。用构造函数去生成一个对象时会把构造函数的现式原型(prototype)添加给构造的对象的隐式原型__proto__。然后对象就可以调用原型里的方法和属性。对象只有隐式原型,但函数是一个特殊的对象,它同时拥有现式原型和隐式原型且它的现式原型继承于它的隐式原型(就是现式原型拥有隐式原型的所有东西,还有添加的属于自己的方法和属性)。对象由函数构造,函数由Function这个构造函数创建,Funcion这个构造函数由Object这个构造函数创建。Obeject构造函数没有构造函数,所以它没有构造它的构造函数去继承,所以它就已经到顶了,它的隐式原型是null。对象会有它的构造函数的现式原型的所有方法和属性,且放于对象的隐式原型中。

原型链的关系可以通过下图缕缕关系。

image.png

我们举个例子来展示使用原型的方法,给构造函数的原型添加属性,然后再它构造的对象里面使用它

function Car() {
  this.run = 'runing'
}

Bus.prototype = new Car();
function Bus() {
  this.name = 'BYD'
}

let bus = new Bus();
console.log(bus.run)//这里打印runing

那现在让我们来理解instanseof通过原型链检测的原理

  • 因为bus.__proto__===Bus.prototype,所以此处打印true
console.log(instanceof(bus,Bus))
  • 因为对象的隐式原型等于构造它的函数的显示原型bus.__proto__===Bus.prototype,Bus.__proto__===Function.prototype,Function.__proto__===Objct.prototype
  • 因为构造函数的隐式原型等于它的现式原型(其实这里说相等也不准确,构造函数的现式原型是以它的隐式原型为基础,添加了新的方法和属性构成的,应该是对象的隐式原型等于它构造函数的现式原型,构造函数的现式原型部继承了它的隐式原型并在隐式原型的基础上添加了新的属性和方法)Bus.prototype===Bus.__prote__,Function.prototype===Function.__proto__
  • 所以bus.__proto__.__proto__.__proto__ === Object.prototype
  • 所以输出true
console.log(instanceof(bus,Object))

这里面试官可能就会问要你手写instanseof的代码

这里考察基础的算法和原型链的使用

  • 这里首先分析输入内容和输出结果 先后输入一个数据类型和一个构造函数,输出它的原型链上是否有它
  • 然后分析它如何输出true,一个构造函数如果在这个数据类型的原型链上,则输出true
  • 设置结束条件,放置一个whlie循环,把判断方法放进去,并让该数据类型等于它的隐式原型,设置结束条件,当原型链到末尾还没找到(当它找到null时(原型链的末尾是构造函数Object,因为它没有构造函数,所以它的隐式原型是null))结束循环,看下面代码。
function myinstanseof(L, R) {
  while (L !== null) {
    L = L.__proto__
    if (L === R.prototype) {
      return true
    }
  }
  return false
}
console.log(myinstanseof([], Array));

此处有个问题,用new生成的原始类型和原始类型不一样

console.log(new String('hello') instanceof String);//此处打印true
console.log('hello' instanceof String);//此处打印false

打印结果不同的原因:不用new生成的原始类型默认认为它是一个字面量,在Js中字面量是没有属性和方法的。用new生成的原始类型,Js会认为它是一个对象,所以它有属性和方法和原型。原始类型用new生成的对象叫 包装类

  • 如果要给字面量添加属性的话,能够添加成功,但系统认为你是一个字面量,又会马上给你删了
let num = 123
num.a = 1//-------添加后马上会被delete
console.log(num.a);//-----此处打印undefined,因为这个属性在num不存在
console.log(num); //------此处打印的是num的[[PrimitiveValue]](原始值)

Object.prototype.toString.call(x)

Object.prototype.toString.call(x)的使用涉及this的绑定和原型的使用。

  • 它的原理是通过.call这个方法将Object原型上的方法绑定到数据类型x上,分析它的[[class]]的属性,并返回[object,[[class]]],其中[[class]]是存放该数据的类型的地方且不能直接访问,只有Js能读懂,所以调用官方构造的方法来访问它。
  • 这里是官方文档,解释了toString的执行过程 image.png 翻译过来
  1. 如果 this 值为 undefined,则返回 “[object Undefined]”。
  2. 如果 this 值为 null,则返回 “[object Null]”。
  3. 设 O 为调用 ToObject 的结果,将 this 值作为参数传递 ToObject(this)
  4. 设 class 为 O 的 [[Class]] 内部属性的值。 // 得到了 O 的类型
  5. 返回由“[object ”、class 和 “]” 三块拼接的结果。

.call的作用是让这个方法绑定在这个对象上,也就是使用this隐式绑定的方法绑定上去

function test(){
  console.log(this.b);
}
let obj = {
  b: 1
}
test.call(obj)
//绑定的原理是隐式绑定原理,也就是方法被这个对象拥有且调用后,方法的this就会绑定在这上面
//绑定的过程
// 1. 让 obj 拥有 test
// 2. obj.test()
// 3. delete obj.test//此处删除后方法依然指向该对象

Object.prototype.toString.call(x)的使用方法

let a = function () { }
let b = {}
let c = 1
console.log(Object.prototype.toString.call(a)); // '[object Fanctioin]'
console.log(Object.prototype.toString.call(b)); // '[object Object]'
console.log(Object.prototype.toString.call(c)); // '[[object Number]]'

此处有个问题,.call的执行需要把方法添加到对象中并调用,但c这是个字面量怎么办

c虽然是字面量,但它是个对象,但它的对象中添加东西后就会被删除。.call在执行的时候更加优先,且添加和调用方法不会被影响,所以.call能够正常绑定

结语

本文讲解了Js的数据类型,Js类型判断方法和原理。需要了解的原型链,和call的绑定原理。写文章不易,还请给个赞。