zoukankan      html  css  js  c++  java
  • 原型和原型链

    继承

    对于使用过面向对象语言【基于类 class】的语言的开发者来说, javascript有点一言难尽,因为 js是动态的,不具备正真意义class 语法,并且不具备一个class实现, es2016/es6 之后引入了  class这个概念,但也只是一个语法糖的结构,本身还是基于原型的。

    当谈到继承时,JavaScript 只有一种结构:对象。每个构造函数实例对象[会自动继承原型中的属性和方法]( object )都有一个私有属性【隐式原型】(称之为 __proto__ )指向它的构造函数的原型对象(prototype【显示原型】,函数这个特殊对象除了有__proto__ 这个原型属性外,还有一个 prototype,指向函数的原型对象!!! )。该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

    几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。

    结合生活中的例子,原型就可以理解为 现实生活中的 父与子这种关系的 父,  每个javascript 对象 a{null 除外,null 没有原型,并作为这个原型链中的最后一个环节} 在被定义出来,自动就会有一个 原型b与之关联,这个对象就是子, 原型就是父。这种关系, 这个b原型可能也会有父亲 原型c,  那么c也就相当于a的爷爷,  a b c  这种链式关系就组成了 原型链 , a的原型b以及 a原型b的原型c   中 有的属性和方法 a都可以访问使用。 就和现实生活中  你没有属于自己的车,但是你父亲有  你就可以使用你父亲的车来代步。 有可能你父亲和你都没有房子,你爷爷有, 那么你和你父亲都有你爷爷的房子的使用权。 

    用实际例子来说明的话:

    根据div的id 获取到div DOM, console.dir()  打印dom ,__proto__ 指向的是 HTMLDIVELEMENT 

    HTMLDIVELEMENT  也是一个对象,对象也有自己的原型  也就是 HTMLElement

    HTMLElement 原型是 Node

    Node 原型是 EventTarget

    EventTarget 原型是 Object 

    Object 原型 没有  为 null

    这一层层的嵌套 就形成一条原型链!!!

    访问 a对象上的属性 a.attr /a.func() 时, 如果自身没有 这个属性或者方法, 那么就会形成原型链攀升, 会去a的原型b中找是否存在属性或者方法, b没有 就会去b的原型c中找,直至原型链顶端,都找不到就报错 ,找到了就执行。 注意  并且 this对象始终指向访问这个属性或者方法的对象!!!

     原型的继承 才算做正在意义上的继承。 改变构造函数的原型不叫继承

      function User2(){} //普通用户函数
         let user22 = new User2();
        //  console.dir(User2);
         User2.prototype.view = function(){
             console.log('给 user2 原型加上view方法');
         }
        //  console.dir(user22);
         /* 假如还有一个Admin构造函数,也想用veiw方法,那么可以使用继承,
         这里就会有 改变构造函数的原型的操作
         Admin.prototype = User2.prototype;
         这样其实就把 -- Admin构造函数原来的原型换成User2的原型了 --  ,
         虽然Admin new 出来的实例确实可以用到view()方法了,但是会有问题
    
         当管理员中要新增一个获取只有管理员能获取到的信息的方法 
         getAdminInfo()方法时,你也想写在原型中,避免每次实例化 getInfo()方法都会占用内存
         这里其实就和普通用户的view() 写到一起了,那这样普通用户执行这个GetAdminInfo()就也能获取到权限之外的数据,!!!
         这是有问题的,因为 直接改变了构造函数的原型其实不是继承。
         【可以这么理解,张三继承李四的财产,但是自己的财产应该得到保留,而改变构造函数的原型,就相当于继承别人的 ,自己的却给丢弃了,这不叫真正的继承!!】
      /* 正确的继承 
         1 Admin.prototype.__proto__ = User2.prototype;
          把admin的原型的原型设置为 User2的原型,这样 admin实例就可以用到view()方法,构造函数原型也不会被改变!!!
         2 Admin.prototype = Object.create(User2.prototype);
         这里跟直接把User2.prototype 赋值给Admin.prototype 不一样,直接赋值由于对象赋值都是引用地址赋值,地址一样,那么添加删除方法都会在一块内存区域导致逻辑混乱,权限失效。
         Object.create()时开辟一块新的地址,然后把User2.prototype放进去,这样不会改变原本的构造函数原型。只是Admin的原型指针暂时到新的新的地址中的对象上,但是会丢失 constructor 不能通过constructor 找到构造函数,除非补上这个属性【 补也不能乱补, 关于 for-in 遍历 in关键字会有原型攀升,所以基本上会把constructor等原型链上的属性设置为不可遍历【DefineProperty(Admin.prototype,"constructor",{在这里设置enumerable:false不可遍历})】】
         而且在这个 Object.create(User2.prototype); 这个操作之前的Admin实例的prototype不会随着改变!
          */

    多态:

    /*继承=》多态!!! 面对不同的对象,响应不同的结果 */
    
        function Dt(name,age){
            this.name = name;
            this.age = age;
            
        }
        Dt.prototype.show = function(){
            console.log(this.description());
            console.log(this.name,this.age);
        };
        console.dir(Dt);
    
        function Aadmin(...args){
            Dt.apply(this,args);
            /*实现在继承Dt的子级中调用父级Dt中的方法
            就不用每个子级要用的时候都去 重复定义
            this.name = name;
            this.age = age; 
        }
        Aadmin.prototype = Object.create(Dt.prototype);
            Aadmin.prototype.description = function(){
                return '管理员态';
        }
        function Member(...args){
            Dt.apply(this,args);
        }
        Member.prototype = Object.create(Dt.prototype);
            Member.prototype.description = function(){
                return '组员态';
            }
        function Enterprise(...args){
            
           Dt.apply(this,args);//因为 对象中this指向的就是调用方法的那个对象,
           /* 下面new 一个实例时,this就指向的是那个实例对象, Dt.apply(this,args)就实现把这个实例对象传入到Dt构造方法中,因为直接调用函数的话【非严格模式下,严格模式this为undefined】,this指向就是window对象 */
            
            /* 这样就可以实现在继承Dt的子级中调用父级Dt中的方法!!! */
        }
        Enterprise.prototype = Object.create(Dt.prototype);
            Enterprise.prototype.description = function (){
                return '企业账户态';
            }
        /* 让aadmin。menber。enterprise 函数的原型 继承dt的原型 ,然后同一个父级方法面对不同的子级状态,相应不同的结果
        【就好比大儿子,二儿子,还有小儿子同时找你要零花钱买零食,你会根据年龄分别给他们不同数量的钱,大儿子买的东西贵,就相应的多给一点,最小的儿子可能就要个仔仔棒,你可能就给的最少!!!】 */
    
        for (const obj of [new Aadmin("管理员",100), new Member("组员",25), new Enterprise("百度",20)]) {
           obj.show() ;
        } 
        /* 没有继承特性的话,就需要分别在三个构造函数的原型上定义不同名字方法,然后分别判断实例是哪个构造函数实现的【obj instanceOf 构造函数name 】然后调用相应的方法 */

     

    /* js 没有多继承这一特性,
       a继承了b,你就没办法让a取继承c的同时,保留a继承b,
        
        有一种办法就是a先继承b,然后让b继承c 这样 a就可以同时用到b,c中功能了,
        但是这会有个问题,本来b,c是两个毫无关联的两个模块,为了方便a,硬给b,c加上了一层关联,这是我们不希望看到的!!!
    
        可以使用 mixin 这个混合功能解决这个问题,因为原型也就是对象,可以往里面压入属性
        具体实现 :就是把某个模块定义为一个对象,
        const b = {
            bfunc(){}
        };
    
        const c = {
            __proto__:b 设置c的原型为b
            cfunc(){
                this._proto__.bfunc();b 设置c的原型为b后,在c中就可以使用b中的方法!!!
                == super.bfunc(); super == this._proto__  super指向的是当前对象的原型
            }
        };
    
        然后 a需要用什么功能就用 a.prototype =  Object.Assign(a.prototype,b,...);
        就把什么功能模块中的对象方法压入到 a的原型中,
        这样 b,c,等其他模块之间就不会出现强行被加上关联的情况!!!
    
        */
  • 相关阅读:
    Essential Phone PH1官方刷机方法
    科普一下bl锁的知识,没解锁的必看!
    谷歌pixel手机解BL锁、刷机、破解电信(史上最详细的帖子)
    Fiddler4入门——手机抓包
    Windows10远程报错:由于CredSSP加密Oracle修正
    简单的利用JS来判断页面是在手机端还是在PC端打开的方法
    C# 之 比较两个word文档的内容
    Browsers 之 弹出窗口阻止问题
    Asp.Net 之 二维码生成
    windows Server2012 之 IIS8.0配置安装完整教程
  • 原文地址:https://www.cnblogs.com/Hijacku/p/14836860.html
Copyright © 2011-2022 走看看