面试官问:typeof 可以判断哪些类型?instanceof 做了什么?null为什么被typeof错误的判断为object

前端瓶子君

共 6644字,需浏览 14分钟

 ·

2021-04-11 23:02

一、typeof

typeof 操作符唯一的目的就是检查数据类型


类型typeof 结果
基本类型undefined"undefined"

Boolean"boolean"

Number"number"

String"string"

BigInt (ECMAScript 2020 新增)"bigint"

Symbol"symbol"

null"object"
引用类型Object(Object、Array、Map、Set等)"object"

Function"function"

所以,但我们使用 typeof 来判断引用类型变量时,无论是什么类型的变量,它都会返回 Object 。

为此,引入了instanceof

二、instanceof

instanceoftypeof 相比,instanceof方法要求开发者明确的确认对象为某特定类型。即 instanceof 用于判断引用类型属于哪个构造函数的方法。

var arr = []
arr instanceof Array // true
typeof arr // "object"
// typeof 是无法判断类型是否为数组的

instanceof 操作符检测过程中也会将继承关系考虑在内,所以instanceof 可以在继承关系中用来判断一个实例是否属于它的父类型。

// 判断 f 是否是 Foo 类的实例 , 并且是否是其父类型的实例
function Aoo(){} 
function Foo(){} 
//JavaScript 原型继承
Foo.prototype = new Aoo();
 
var foo = new Foo(); 
console.log(foo instanceof Foo) // true 
console.log(foo instanceof Aoo) // true

f instanceof Foo 的判断逻辑是:

  • f 的 __proto__一层一层往上,是否对应到 Foo.prototype
  • 再往上,看是否对应着Aoo.prototype
  • 再试着判断 f instanceof Object

instanceof 可以用于判断多层继承关系。

三、instanceof 的内部实现原理

instanceof 的内部实现机制是:通过判断对象的原型链上是否能找到对象的 prototype,来确定 instanceof 返回值

1. 内部实现原理

// instanceof 的内部实现 
function instance_of(L, R{//L 表左表达式,R 表示右表达式,即L为变量,R为类型
  // 取 R 的显示原型
  var prototype = R.prototype
  // 取 L 的隐式原型
  L = L.__proto__
  // 判断对象(L)的类型是否严格等于类型(R)的显式原型
  while (true) { 
    if (L === null) {
      return false
    }
   
    // 这里重点:当 prototype 严格等于 L 时,返回 true
    if (prototype === L) {
      return true
    } 
 
    L = L.__proto__
  } 
}

instanceof 运算符用来检测 constructor.prototype是否存在于参数 object 的原型链上。

看下面一个例子,instanceof 为什么会返回 true?很显然,an 并不是通过 Bottle() 创建的。

function An({}
function Bottle({}
An.prototype = Bottle.prototype = {};

let an = new An();
console.log(an instanceof Bottle); // true

这是因为 instanceof 关心的并不是构造函数,而是原型链。

an.__proto__ === An.prototype; // true
An.prototype === Bottle.prototype; // true
// 即
an.__proto__ === Bottle.prototype; // true

即有 an.__proto__ === Bottle.prototype 成立,所以 an instanceof Bottle 返回了 true

所以,按照 instanceof 的逻辑,真正决定类型的是 prototype,而不是构造函数。

2. Object.prototype.toString(扩展)

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

默认情况下(不覆盖 toString 方法前提下),任何一个对象调用 Object 原生的 toString 方法都会返回 "[object type]",其中 type 是对象的类型;

let obj = {};

console.log(obj); // {}
console.log(obj.toString()); // "[object Object]"
[[Class]]

每个实例都有一个 [[Class]] 属性,这个属性中就指定了上述字符串中的 type (构造函数名)。[[Class]] 不能直接地被访问,但通常可以间接地通过在这个值上借用默认的 Object.prototype.toString.call(..) 方法调用来展示。

Object.prototype.toString.call("abc"); // "[object String]"
Object.prototype.toString.call(100); // "[object Number]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call([1,2,3]); // "[object Array]"
Object.prototype.toString.call(/\w/); // "[object RegExp]"

可以通过 Object.prototype.toString.call(..) 来获取每个对象的类型。

function isFunction(value{
  return Object.prototype.toString.call(value) === "[object Function]"
}
function isDate(value{
  return Object.prototype.toString.call(value) === "[object Date]"
}
function isRegExp(value{
  return Object.prototype.toString.call(value) === "[object RegExp]"
}

isDate(new Date()); // true
isRegExp(/\w/); // true
isFunction(function(){}); //true

或者可写为:

function generator(type){
  return function(value){
    return Object.prototype.toString.call(value) === "[object "+ type +"]"
  }
}

let isFunction = generator('Function')
let isArray = generator('Array');
let isDate = generator('Date');
let isRegExp = generator('RegExp');

isArray([]));    // true
isDate(new Date()); // true
isRegExp(/\w/); // true
isFunction(function(){}); //true
Symbol.toStringTag

Object.prototype.toString 方法可以使用 Symbol.toStringTag 这个特殊的对象属性进行自定义输出。

举例说明:

let bottle = {
  [Symbol.toStringTag]: "Bottle"
};

console.log(Object.prototype.toString.call(bottle)); // [object Bottle]

大部分和环境相关的对象也有这个属性。以下输出可能因浏览器不同而异:

// 环境相关对象和类的 toStringTag:
console.log(window[Symbol.toStringTag]); // Window
console.log(XMLHttpRequest.prototype[Symbol.toStringTag]); // XMLHttpRequest

console.log(Object.prototype.toString.call(window)); // [object Window]
console.log(Object.prototype.toString.call(new XMLHttpRequest())); // [object XMLHttpRequest]

输出结果和 Symbol.toStringTag(前提是这个属性存在)一样,只不过被包裹进了 [object ...] 里。

所以,如果希望以字符串的形式获取内置对象类型信息,而不仅仅只是检测类型的话,可以用这个方法来替代 instanceof

3. 总结


适用于返回
typeof基本数据类型string
instanceof任意对象true/false
Object.prototype.toString基本数据类型、内置对象以及包含 Symbol.toStringTag 属性的对象string

Object.prototype.toString 基本上就是一增强版 typeof

instanceof 在涉及多层类结构的场合中比较实用,这种情况下需要将类的继承关系考虑在内。

四、null为什么被typeof错误的判断为了'object'

// JavaScript 诞生以来便如此
typeof null === 'object';

在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null 也因此返回 "object"。(参考来源)

曾有一个 ECMAScript 的修复提案(通过选择性加入的方式),但被拒绝了。该提案会导致 typeof null === 'null'

如果用 instanceof 来判断的话:

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

来自:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/452

最后

欢迎关注「三分钟学前端」,回复「交流」自动加入前端三分钟进阶群,每日一道编程算法面试题(含解答),助力你成为更优秀的前端开发!

》》面试官也在看的前端面试资料《《
浏览 14
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报