深入剖析 instanceof 运算符

一、instanceof

1、引入 instanceof

  在JS中,判断一个变量的类型,常常会用到 typeof 运算符,但当用 typeof 来判断引用类型变量时,无论是什么类型的变量,它都会返回 Object

// 基本类型
console.log(typeof 100) // number
console.log(typeof 'dylan') // string
console.log(typeof true) // boolean
console.log(typeof undefined) // undefined

console.log(typeof null) // object,这里注意 null 的结果是 object

// 引用类型
console.log(typeof {}) // object
console.log(typeof [1,2,3]) // object

为此引入了 instanceof

instanceof 操作符用于检测对象是否属于某个 class,同时,检测过程中也会将继承关系考虑在内

class Parent{}

let parent = new Parent()
console.log(parent instanceof Parent) // true

// 也可以是构造函数
function Child(){}
let child = new Child()
console.log(child instanceof Child) // true

2、instanceof 与 typeof

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

var arr = []
console.log(arr instanceof Array) // true
console.log(typeof arr) // "object"
// typeof是无法判断类型是否为数组

3、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 可以用于判断多层继承关系

下面看一组复杂例子:

console.log(Object instanceof Object) // true
console.log(Function instanceof Function) // true
console.log(Number instanceof Number) // false
console.log(String instanceof String) // false
console.log(Array instanceof Array) // false

console.log(Function instanceof Object) // true

console.log(Foo instanceof Function) // true
console.log(Foo instanceof Foo) //false

在这组数据中,Object、Function 来 instanceof 自己均为 true,其他的 instanceof 自己都为false,这就要从 instanceof 的内部实现机制以及JS原型继承机制讲起了。

二、instanceof的内部实现机制

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

1、内部实现

解释:prototype 是挂在构造函数(类)下面的显式原型,__proto__ 是挂在实例上的隐式原型

例如:

var arr = []
console.log(arr.__proto__ === Array.prototype) // true

下面是instanceof的内部实现代码:

// instanceof 的内部实现
function instance_of(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 的隐式原型
    // 赋值之后,L为隐式原型的隐式原型(递归向上查找)
    L = L.__proto__
  }
}

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

2、你真的了解 instanceof 了吗

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

function Child(){}
function Parent(){}
Child.prototype = Parent.prototype = {}

let child = new Child()
console.log(child instanceof Parent) // true

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

console.log(child.__proto__ === Child.prototype) // true
console.log(Child.prototype === Parent.prototype) // true
//
console.log(child.__proto__ === Parent.prototype) // true

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

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

三、JS原型链继承关系

显示原型:prototype

隐式原型:__proto__

  在JavaScript原型继承结构里面,规范中用 [Prototype] 表示对象隐式的原型,在JavaScript中用 __proto__ 表示,并且在 Firefox 和 Chrome 浏览器中是可以访问得到这个属性的,但是IE下不行。所有JavaScript对象都有 __proto__ 属性,但只有 Object.prototype.__proto__ 为 null,前提是没有在Firefox或者Chrome下修改过这个属性。这个属性指向它的原型对象。至于显示的原型,在JavaScript里用 prototype 属性表示,这个是JavaScript原型继承的基础知识:https://github.com/sisterAn/blog/issues/5

下面介绍几个例子及推理过程,加深理解:

1、Object instanceof Object

// 为了方便表述,首先区分左侧表达式和右侧表达式
var ObjectL = Object
var ObjectR = Object
// 下面根据规范逐步推演(左侧是实例,右侧是类)
var L = ObjectL.__proto__ = Function.prototype
var R = ObjectR.prototype = Object.prototype
// 第一次判断
R != L
// 循环查找L是否还有 __proto__
L = Function.prototype.__proto__ = Object.prototype
// 第二次判断
R === L
// 返回true

个人理解:函数(类)都是显式原型(prototype),实例都是隐式原型(__proto__)

2、Function instanceof Function

// 为了方便表述,首先区分左侧表达式和右侧表达式
var FunctionL = Function
var FunctionR = Function
// 下面根据规范逐步推演(左侧是实例,右侧是类)
var L = FunctionL.__proto__ = Function.prototype
var R = FunctionR.prototype = Function.prototype
// 判断
console.log(R === L);
// 返回true

3、Foo instanceof Foo

// 为了方便表述,首先区分左侧表达式和右侧表达式
var FooL = Foo
var FooR = Foo
// 下面根据规范逐步推演(左侧是实例,右侧是类)
var L = FooL.__proto__ = Function.prototype
var R = FooR.prototype = Foo.prototype
// 第一次判断
L != R
// 循环再次寻找 L 是否还有 __proto__
L = Function.prototype.__proto__ = Object.prototype
// 第二次判断
L != R
// 再次查找L是否还有 __proto__
L = Object.prototype.__proto__ = null
// 第三次判断
L == null
// 返回false

四、扩展:Object.prototype.toString 方法

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

let obj = {}
console.log(obj) // {}
console.log(obj.toString()) // "[object Object]"

1、[[Class]]

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

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

2、使用 Object.prototype.toString.call(..) 检测对象类型

function isFunc(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 isFunc = generator("Function")
let isArray = generator("Array")
let isDate = generator("Date")
let isRegExp = generator("RegExp")

isArray([]) // true
isDate(new Date()) // true
isRegExp(/w/) // true
isFunc(function(){}) // true

五、总结

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

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

原文地址:https://www.cnblogs.com/haishen/p/12052751.html