• [js高手之路]从原型链开始图解继承到组合继承的产生


    于javascript原型链的层层递进查找规则,以及原型对象(prototype)的共享特性,实现继承是非常简单的事情

    一、把父类的实例对象赋给子类的原型对象(prototype),可以实现继承

     1         function Person(){
     2             this.userName = 'ghostwu';
     3         }
     4         Person.prototype.showUserName = function(){
     5             return this.userName;
     6         }
     7         function Teacher (){}
     8         Teacher.prototype = new Person();
     9 
    10         var oT = new Teacher(); 
    11         console.log( oT.userName ); //ghostwu
    12         console.log( oT.showUserName() ); //ghostwu

    通过把父类(Person)的一个实例赋给子类Teacher的原型对象,就可以实现继承,子类的实例就可以访问到父类的属性和方法

    如果你不会画这个图,你需要去看下我的这篇文章:[js高手之路]一步步图解javascript的原型(prototype)对象,原型链

    第11行,执行oT.userName, 首先去oT对象上查找,很明显oT对象上没有任何属性,所以就顺着oT的隐式原型__proto__的指向查找到Teacher.prototype,

    发现还是没有userName这个属性,继续沿着Teacher.prototype.__proto__向上查找,找到了new Person() 这个实例上面有个userName,值为ghostwu

    所以停止查找,输出ghostwu.

    第12行,执行oT.showUserName前面的过程同上,但是在new Person()这个实例上还是没有查找到showUserName这个方法,继续沿着new Person()的

    隐式原型__proto__的指向( Person.prototype )查找,在Person.prototype上找到了showUserName这个方法,停止查找,输出ghostwu.

    二、把父类的原型对象(prototype)赋给子类的原型对象(prototype),可以继承到父类的方法,但是继承不到父类的属性

     1         function Person(){
     2             this.userName = 'ghostwu';
     3         }
     4         Person.prototype.showUserName = function(){
     5             return 'Person::showUserName方法';
     6         }
     7         function Teacher (){}
     8         Teacher.prototype = Person.prototype;
     9 
    10         var oT = new Teacher(); 
    11         console.log( oT.showUserName() ); //ghostwu
    12         console.log( oT.userName ); //undefined, 没有继承到父类的userName

    因为Teacher.prototype被Person.protoype替换了( 第8行代码 ),所以,Teacher的prototype属性就直接指向了Person.prototype. 所以获取不到Person实例的属性

    三、发生继承关系后,实例与构造函数(类)的关系判断

    还是通过instanceofisPrototypeOf判断

     1         function Person(){
     2             this.userName = 'ghostwu';
     3         }
     4         Person.prototype.showUserName = function(){
     5             return this.userName;
     6         }
     7         function Teacher (){}
     8         Teacher.prototype = new Person();
     9         
    10         var oT = new Teacher();
    11         console.log( oT instanceof Teacher ); //true
    12         console.log( oT instanceof Person ); //true
    13         console.log( oT instanceof Object ); //true
    14         console.log( Teacher.prototype.isPrototypeOf( oT ) ); //true
    15         console.log( Person.prototype.isPrototypeOf( oT ) ); //true
    16         console.log( Object.prototype.isPrototypeOf( oT ) ); //true

    四,父类存在的方法和属性,子类可以覆盖(重写),子类没有的方法和属性,可以扩展

     1         function Person() {}
     2         Person.prototype.showUserName = function () {
     3             console.log('Person::showUserName');
     4         }
     5         function Teacher() { }
     6         Teacher.prototype = new Person();
     7         Teacher.prototype.showUserName = function(){
     8             console.log('Teacher::showUserName');
     9         }
    10         Teacher.prototype.showAge = function(){
    11             console.log( 22 );
    12         }
    13         var oT = new Teacher();
    14         oT.showUserName(); //Teacher::showUserName
    15         oT.showAge(); //22

    五、重写原型对象之后,其实就是把构造函数的原型属性(prototype)的指向发生了改变

    构造函数的原型属性(prototype)的指向发生了改变,会把原本的继承关系覆盖(切断)

     1         function Person() {}
     2         Person.prototype.showUserName = function () {
     3             console.log('Person::showUserName');
     4         }
     5         function Teacher() {}
     6         Teacher.prototype = new Person();
     7         Teacher.prototype = {
     8             showAge : function(){
     9                 console.log( 22 );
    10             }
    11         }
    12         var oT = new Teacher();
    13         oT.showAge(); //22
    14         oT.showUserName();

    上例,第7行,Teacher.prototype重写了Teacher的原型对象(prototype),原来第6行的Teacher构造函数的prototype属性指向new Person()的关系切断了

    所以在第14行,oT.showUserName() 就会发生调用错误,因为Teacher构造函数上的prototype属性不再指向父类(Person)的实例,继承关系被破坏了.

    六、在继承过程中,小心处理实例的属性上引用类型的数据

     1         function Person(){
     2             this.skills = [ 'php', 'javascript' ];
     3         }
     4         function Teacher (){}
     5         Teacher.prototype = new Person();
     6 
     7         var oT1 = new Teacher();
     8         var oT2 = new Teacher();
     9         oT1.skills.push( 'linux' );
    10         console.log( oT2.skills ); //php, java, linux

    oT1的skills添加了一项linux数据,其他的实例都能访问到,因为其他实例中共享了skills数据,skills是一个引用类型

    七、借用构造函数

    为了消除引用类型影响不同的实例,可以借用构造函数,把引用类型的数据复制到每个对象上,就不会相互影响了

     1         function Person( uName ){
     2             this.skills = [ 'php', 'javascript' ];
     3             this.userName = uName;
     4         }
     5         Person.prototype.showUserName = function(){
     6             return this.userName;
     7         }
     8         function Teacher ( uName ){
     9             Person.call( this, uName );
    10         }
    11         var oT1 = new Teacher();
    12         oT1.skills.push( 'linux' );
    13         var oT2 = new Teacher();
    14         console.log( oT2.skills ); //php,javascript
    15         console.log( oT2.showUserName() );

     虽然oT1.skills添加了一项Linux,但是不会影响oT2.skills的数据,通过子类构造函数中call的方式,去借用父类的构造函数,把父类的属性复制过来,而且还能

    传递参数,如第8行,但是第15行,方法调用错误,因为在构造中只复制了属性,不会复制到父类原型对象上的方法

    八、组合继承(原型对象+借用构造函数)

    经过以上的分析, 单一的原型继承的缺点有:

    1、不能传递参数,如,

     Teacher.prototype = new Person();

    有些人说,小括号后面可以跟参数啊,没错,但是只要跟了参数,子类所有的实例属性,都是跟这个一样,说白了,还是传递不了参数

    2、把引用类型放在原型对象上,会在不同实例上产生相互影响

    单一的借用构造函数的缺点:

    1、不能复制到父类的方法

    刚好原型对象方式的缺点,借用构造函数可以弥补,借用构造函数的缺点,原型对象方式可以弥补,于是,就产生了一种组合继承方法:

     1         function Person( uName ){
     2             this.skills = [ 'php', 'javascript' ];
     3             this.userName = uName;
     4         }
     5         Person.prototype.showUserName = function(){
     6             return this.userName;
     7         }
     8         function Teacher ( uName ){
     9             Person.call( this, uName );
    10         }
    11         Teacher.prototype = new Person();
    12 
    13         var oT1 = new Teacher( 'ghostwu' );
    14         oT1.skills.push( 'linux' );
    15         var oT2 = new Teacher( 'ghostwu' );
    16         console.log( oT2.skills ); //php,javascript
    17         console.log( oT2.showUserName() ); //ghostwu

    子类实例oT2的skills不会受到oT1的影响,子类的实例也能调用到父类的方法.

  • 相关阅读:
    Getting Started with Recovery Manager (RMAN) (文档 ID 360416.1)
    enctype的2个值
    laravel 去掉资源的顶层包裹 withoutWrapping方法
    hash_equals(),防止时序攻击,字符串比较函数
    moment.js 处理“几天前”,“几个月前”
    redis hash 应用场景
    vue 3个插槽示例(具名插槽)
    vue 插槽的基本使用
    redis hash
    redis 分布式系统全局序列号
  • 原文地址:https://www.cnblogs.com/ghostwu/p/7439690.html
Copyright © 2020-2023  润新知