zoukankan      html  css  js  c++  java
  • 原型继承问题迷失

    发现问题

    在开发中需要实现一个自定义Error,它继承自Error,按照经验,一般会这样来做:

    function CustomError(message) {
      this.message = message;
    }
    
    CustomError.prototype = Object.create(Error.prototype, {
      name: {
        value: 'customError'
      },
      constructor: {
        value: Error,
        enumerable: false,
        writable: true,
        configurable: true
      }
    });
    
    var myError = new CustomError('f**k error!');
    
    console.log(myError instanceof CustomError) // true
    console.log(myError instanceof Error) // true
    
    

    它工作的很好,没有明显的bug,所以长期以来,我都这么干。直到某天试着这样:

    > console.log(CustomError instanceof Error)
    < false
    

    为什么这里返回的是false

    再一个,回到题目本身,我想和大家讨论的是函数之间的继承关系。具体而言,就是CustomError通过原型链继承于Error,实现预期不仅表现在CustomError的每一个实例上,也表现在其本身——CustomError instanceof Error === true

    原型与原型链

    在回答问题之前,需要理一理原型和原型链是什么、干什么、以及它们之间的关系。

    是什么

    • 原型 prototype
      每个函数有一个prototype属性,指向函数的原型对象。
      它的值是一个对象(属性的集合),默认只有一个constructor属性,指向函数本身。
    • 原型链 __proto__
      1、每个对象默认有一个__proto__属性,指向创建该对象的函数的prototype。
      2、访问一个对象的属性时,先在该对象的基本属性中查找,如果没有,在沿着__proto__这条链向上查找,这就是原型链。
      3、函数也是对象,也默认有一个__proto__属性,指向Function.prototype。

    干什么

    • prototype:用来实现基于原型的继承和属性共享。
    • __proto__:构成原型链,同样用于实现基于原型的继承。

    什么关系

    emm,好像还是不太清楚它们之间的联系。但如果从内存的角度上看,就简单了许多。

    当一个函数CustomError被创建成功时,默认带有prototype__proto__两个属性,此时,它们之间的关系如下:

                                  +-------------+    
    Function.prototype     ------->             |
                                  |   内存(堆1)  |
    CustomError.__proto__  ------->             |
    |                             +-------------+
    |
    |                             +-------------+
    +----------.prototype  ------->             |
                                  |   内存(堆2)  |
    myError.__proto__ ------------>             |                               
                                  +-------------+
                          (图1)       
    

    解决之道

    有了以上的铺垫,解答之前的问题就简单多了。

    首先,我们通过CustomError.prototype = Object.create(Error.prototype)操作,改变了CustomError.prototype的指向。

                                  +-------------+    
    Function.prototype     ------->             |
                                  |   内存(堆1)  |
    CustomError.__proto__  ------->             |
    |                             +-------------+
    |                             
    +----------.prototype--+      +-------------+
                           |      |   内存(堆2)  |
    myError.__proto__ +    |      +-------------+                             
                      |    |      
                      |    |      +-------------+
                      |    +------>             |
                      |           |   内存(堆3)  |
                      +----------->             |
                                  +-------------+ 
                          (图2)       
    

    其次,这里的instanceof操作,就是判断CustomError的原型链(__proto__)上是否存在Error.prototype,其结果返回一个Boolean。

    所以结果false并不令人感到奇怪,因为没有任何迹象表明CustomError.__proto__改变了指向,且指向Error.prototype

    回过头,比较一下图1、2,思考两秒钟。

    上面的回答也暗含了解决之道:

    > CustomError.__proto__ = Error.prototype
    > CustomError instanceof Error
    < true
    

    总结

    尽管这样解决了问题,但MDN并不推荐这么做。

    一来,如MDN所讲,这个操作会很慢,可能有性能影响。

    二来,若CustomError.prototype = Object.create(Object.assign({},F1.prototype, F2.prototype)),还要继续改变CustomError.__proto__么?

    三来,我并不保证以上内容绝对正确。譬如说:通过Function.prototype.bind方法构造出来的函数,它没有prototype属性;Object.prototype.__proto__=== null

    烦请勘误指正~

    延展

    • Object.create()
      使用指定的原型对象及其属性去创建一个新的对象(依然维持着对原型对象的引用)。
    • instanceof 运算符
      用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。
    • isPrototypeOf()
      用于测试一个对象是否存在于另一个对象的原型链上。
      与 instanceOf 不同,在表达式object instanceOf AFunction中, object 的原型链是针对AFunction.prototype进行检查,而不是针对AFunction本身。
  • 相关阅读:
    DI的3种实现方式
    spring ioc的实现方式
    异常:This application has no explicit mapping for /error, so you are seeing this as a fallback.
    maven项目 集成SSM框架
    org.xml.sax.SAXParseException错误
    Redis在web中的应用
    上传下载文件实例(vsftp服务器+nginx)
    Redis的安装与启动
    修饰器-2
    修饰器练习
  • 原文地址:https://www.cnblogs.com/fayin/p/8432621.html
Copyright © 2011-2022 走看看