zoukankan      html  css  js  c++  java
  • 深入剖析 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 在涉及多层类结构的场合中比较实用,这种情况下需要将类的继承关系考虑在内。

  • 相关阅读:
    【BZOJ1089】[SCOI2003]严格n元树(高精度,动态规划)
    【BZOJ1083】[SCOI2005]繁忙的都市(最小生成树)
    【BZOJ1082】[SCOI2005]栅栏(搜索)
    【BZOJ1081】[SCOI2005]超级格雷码(搜索)
    【BZOJ1078】[SCOI2008]斜堆(性质题)
    【BZOJ1077】天平(差分约束)
    【BZOJ1071】[SCOI2007]组队(神仙题)
    【BZOJ1862】[ZJOI2006]游戏排名系统 (Splay)
    【BZOJ1056】[HAOI2008]排名系统(Splay)
    【BZOJ1055】[HAOI2008]玩具取名(动态规划)
  • 原文地址:https://www.cnblogs.com/haishen/p/12052751.html
Copyright © 2011-2022 走看看