美文网首页
再看原型继承

再看原型继承

作者: 刘程源 | 来源:发表于2019-07-30 23:52 被阅读0次

    关于语法中的代码复用,有两大方向,一种为宏,一种为继承,就js而言,选择了后者

    代码复用 - 继承(对象系统)

    继承是面向对象中的概念,表示子类具有父类的属性与方法
    对象系统的继承特性,又有三种实现方案

    • 基于类 - class-bassed
    • 基于元类 - metaclass-based
    • 基于原型 - prototype-based

    js使用的是基于原型的实现方案

    js对象系统 - 构造器(无类)

    js是一个标准的无类语言,其实现抽取类中构造器(constructor)来实现继承的功能。类似于类,构造器是用于描述对象的组织结构的语法(正如面向对象中类与实例的关系,构造器与对象也是这样一个关系)

    js对象系统 - 原型

    js最终通过[构造器].prototype 来描述对象的组织结构
    针对对象系统,可知对象并没有原型,只有构造器有原型

    js对象系统 - 复用实现

    对象来自于原型,构造器与原型最终实现了语义(语法)上的代码实现,针对最终代码的实现,又有三种策略可以选择

    • 原型复制
      即深度copy

    实现简单,但会占用过多的内存,尤其是相同的函数都会开辟新的空间,不可能作为底层实现

    • 写时复制(prototype)
      类似于dll,在访问时,访问的都是同一指向,但是在第一次写入时,从新分配空间,copy prototype

    针对大量读写,会与1一样

    • 写时复制(属性)
      与上条类似,但在写入时,重新开辟的空间为属性,故可以节约更多的内存,但此处写只

    1.原型污染
    同样的,针对指向没有改变的修改,会直接修改原型,引起全局污染

    function User(){}
    User.prototype.baseinfo = {
        job:'',
        name:'',
        age:1
    }
    
    let u1 = new User();
    let u2 = new User();
    
    //读时
    console.log(User.prototype.baseinfo === u1.baseinfo)
    
    u1.baseinfo.age = 18;
    
    console.log(u2.baseinfo.age)
    

    就如上的表现,可以用Proxy去理解,即js针对属性set进行拦截,如果set修改,则对当前对象的属性就行修正,但不同于Proxy,prototype可以动态添加
    2.原型动态添加
    即原型链可动态更新,此为不同于第一类(原型复制)的特性

    function User(){}
    
    let u1 = new User();
    
    console.log(u1.baseinfo)
    
    User.prototype.baseinfo = {
        job:'',
        name:'',
        age:1
    }
    
    console.log(u1.baseinfo)
    

    js对象系统 - 构建过程

    对于关键字function来讲,其有两层意义

    • 声明函数
      尽管拥有prototype,但他没有任何意义,也不应该存在
    • 声明构造器construcor
      可以有如下理解
    {
      prototype:{
        get(){
          if(!this.__proto__){
            this.__proto__=new object();
            this.__proto__.construcor=this;
          }
        }  
    }
    

    prototype只有在被使用后才进行创建,此时constructor属性默认会指向函数本身

    做为一个快速实现的语言,js选择了无节操的复用形式,即原型用于创建实例,但原型又是一种实例这种衔尾蛇的形式,以至于生出各种奇巧淫技

    js对象系统 - 构造器的维护

    由单个原型构造过程可知,构造器指向创建函数本身
    多个原型连接在一起成为原型链

    实现如下,他可以准确的实现继承的关系(复用行为)

    function Parent(){}
    
    function Son(){}
    
    //构建原型链
    Son.prototype = new Parent();
    
    let u1 = new Parent();
    let u2 = new Son();
    //true
    console.log(u1.constructor == u2.constructor)
    

    但有一个简单的逻辑问题,即u1与u2来自不同的构造器,但其内部构造器描述却指向了相同的构造器
    原因只是因为new创建后,默认构造器来自于其本身,而在创建原型链时,并不需要这一特性,简单的修正即可

    Son.prototype.constructor = Son;
    

    但此时已然有问题

    function Parent(){}
    
    function Son(){}
    
    //构建原型链
    Son.prototype = new Parent();
    Son.prototype.constructor = Son
    var u1 = new Parent();
    var u2 = new Son();
    
    //u2的构造器 == (来自)Son
    console.log( u2.constructor == Son)
    //u2的构造器(即Son)的原型 == parent的实例
    console.log(  u2.constructor.prototype instanceof Parent)
    console.log(  Son.prototype instanceof Parent)
    //u2的构造器的原型的构造器 应该为parent实例的构造器 即Parent
    console.log(  u2.constructor.prototype.constructor == Parent)
    console.log(  Son.prototype.constructor == Parent) //与上列相同,显然这里被修正了
    console.log(  new Parent().constructor == Parent) //原型链中应有的表现
    

    我们希望原型链如下进行显示


    1.png

    即我们手动修复Son.prototype.constructor = Son后,在自动?处也受到了影响,此时原型链中断
    这里又有两个问题
    1.为什么能成功
    构造器修复可以说是"官方"提供的一种实现继承的方式
    2.如何解决
    这与我们理解的原型链不一样

    js对象系统 - 内部原型链(proto)

    实例拥有构造器的指向 【实例】.constructor
    构造器拥有原型的指向 【构造器】.prototype
    即实例若想寻找原型,需要通过构造器进行查询,但其内部还有一个属性__proto__即内部原型链
    实际上,当如下代码生效时,继承关系就已经实现,具体原因则是对js来讲,真正实现原型链的属性为__proto__

    function Parent(){}
    function Son(){}
    //构建原型链
    Son.prototype = new Parent();
    var u2 = new Son();
    
    console.log(u2.__proto__ == Son.prototype);
    console.log(Son.prototype.__proto__==Parent.prototype);
    console.log(Parent.prototype.__proto__==Object.prototype);
    
    2.png

    内部原型链是js原型继承机制实现的底层实现
    通过constructor与prototype所维护的构造器原型链,则是用户代码要回溯时才需要的
    如果不需要代码回溯,不进行维护也是可以的

    __proto__最早为火狐提供的属性,目的是为解决,js无法真正实现继承的属性。
    js无法使用构造器原型链的另一个原因是,js内部本身对构造器原型链的维护异常

    实际上,一个构造器function的真正指向如下所示(盗图,画的比我好)

    3.png
    可以明显看到,没有proto,原型链就会中断,null是一切对象的基础,这一真理也将不复存在

    吐槽1
    我们的目的是为了代码复用顺便进行优化(内存复用),原型继承通过两条原型链进行实现

    • 构造器原型链
      辅助开发人员回溯
    • 内部原型链
      真正的实现

    没有需求的话,让构造器原型链见鬼去吧,否则,instanceof/typeof都是框架级继承实现需要考虑的重灾区

    吐槽2
    es6早都普及了,原型链也该下岗了吧

    相关文章

      网友评论

          本文标题:再看原型继承

          本文链接:https://www.haomeiwen.com/subject/czlgrctx.html