zoukankan      html  css  js  c++  java
  • JS面向对象的类 实例化与继承

    JS中 类的声明有两种形式:

      

        // 类的声明
        function Animal() {
          this.name = 'name'
        }
    
        // ES6中的class声明
        class Animal2 {
          constructor() {
            this.name = name;
          }
        }

    而实例化类,就是一个简单的 new 就完了

        // 实例化
        console.log(new Animal(), new Animal2());
    View Code

    类的创建都是很简单的,主要是类的继承;

    JS中类的继承是通过原型链来达到这样的目的;所以在面试过程中问到继承这样的问题,就是在考察你的原型链的掌握水平。

    分别像每种继承中运用到的方式、每种继承的缺点和优点等。

    先附上一张,我认为很好,但是需要一定了解的人才能看懂的图(原型链的指向)。

     接下来开始上继承的代码和相关的一些见解:

    首先ES5,也是面试重点考察的点

    以下继承中折叠起来的都是红宝书的内容,非折叠的为简单实现继承。

    原型链继承

    将原型对象等于另一个类型的实列。原型对象将包含一个指向另一个原型的指针,相应的,另一个原型中也包含着一个指向另一个构造函数的指针。它们的关系层层递进,构成实列与原型的链条。

     1     // 原型链继承
     2     function Person1() {
     3       this.name = "person1";
     4       this.arr = [1, 2, 3];
     5     }
     6     function Child1() {
     7       this.type = 'child1';
     8     }
     9     // 重点就是这句,通过将子类的原型指针指向了超类的构造函数
    10     Child1.prototype = new Person1();
    11     console.log(new Child1().__proto__); //Person1 {name: "person1"}
    12     var s1 = new Child1();
    13     var s2 = new Child1();
    14     s1.arr.push(4);
    15     console.log(s1.arr, s2.arr); // (4) [1, 2, 3, 4] (4) [1, 2, 3, 4]
     1 function SuperType(){
     2             this.property = true;
     3         }
     4         SuperType.prototype.getSuperValue = function(){
     5             return this.property;
     6         };
     7 
     8         function SubType(){
     9             this.subproperty = false;
    10         }
    11 
    12         SubType.prototype = new SuperType();
    13         //继承了SuperType,将SubType原本指向原型的指针,改为指向SuperType的原型。
    14 
    15         SubType.prototype.getSubValue = function(){
    16             return this.subproperty;
    17         };//添加一个新方法
    18 
    19         var instance = new SubType();
    20         alert(instance.getSuperValue());//true
    21         alert(instance.subproperty);//true
    22         alert(instance instanceof Object);//true
    23         //所有函数的默认原型都是Object的实例,因此默认的原型都会包含一个内部指针,指向最高层:Object.orototype;
    View Code

    注意点:通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样会重写原型链。

    原型链的缺点?优点?:一是:会实现为各个实例所共享;二是:在创建子类型的实例时,不能向超类型的构造函数中传递参数。

    借用构造函数: 主要用在解决原型链的问题上

    就是用子类型构造函数的内部去调用超类型的构造函数。构造函数去继承构造函数,就不存在共享和无法传递参数的问题了。

     1     // 借用构造函数继承
     2     function Person2() {
     3       this.name = 'person2';
     4       this.arr = [1, 2, 3];
     5     }
     6     function Child2() {
     7       this.type = 'child2';
     8       // 重点是这句: 用子类的构造函数的内部去调用超类的构造函数。构造函数去继承构造函数
     9       Person2.call(this);
    10     }
    11     // 在超类上加一个方法
    12     Person2.prototype.sayName = function () {
    13       return 'person2'
    14     }
    15     // console.log(new Child2().sayName()); // error
    16     console.log(new Child2());
    17     var s3 = new Child2();
    18     var s4 = new Child2();
    19     s3.arr.push(4);
    20     console.log(s3.arr, s4.arr); // (4) [1, 2, 3, 4] (3) [1, 2, 3]
     1             function SuperType(){
     2                 this.colors = ["red","blue","green"];
     3             }
     4             function SubType(){
     5                 SuperType.call(this);//继承了SuperType
     6                 //因为要在内部进行继承,且函数都有自己的作用环境,所以要用call()方法来调用SuperType;
     7             }
     8 
     9             var instance1 = new SubType();
    10             instance1.colors.push('black');
    11             alert(instance1.colors);//red,blue,green,black
    12             var instance2 = new SubType();
    13             alert(instance2.colors);//red,blue,green
    14             //instance2是因为子类型的原型是构造函数,当instance1创建时,它会先创建一个新的副本,以供使用,所以不会影响
    View Code

    它的最大的优势: 就是可以传递参数 

    1             function SubType(){
    2                 //继承了SuperType,同时还传递了参数
    3                 SuperType.call(this,"Nicholas");
    4                 //实例属性
    5                 this.age = 29;
    6             }
    View Code

    问题是:

    1.它通过超类的构造函数来显示继承. 所以超类的原型中的方法,它并没有继承到。即并没有真正的实现继承。

    2.都是使用的构造函数来完成继承的,那么不可避免的,它也具有着与构造函数相同的问题: 每个方法都要在每个实例上重新创建一遍。即每次工作量都很大。 

    组合继承:  将 原型链 和 借用构造函数 的技术组合到一块。

    原型链来实现对原型属性和方法的继承(实现属性和方法的共用),而通过借用构造函数来实现对实例属性的继承(实现私有的实例化)。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例有自己的属性。

     1     // 组合继承
     2     function Person3() {
     3       this.name = 'person3';
     4     }
     5     function Child3() {
     6       this.type = 'child3';
     7       // 第一次构造函数调用
     8       Person3.call(this); // 构造函数继承
     9     }
    10     // 第二次构造函数调用
    11     Child3.prototype = new Person3(); // 原型链继承
     1             function SuperType(name){
     2                 this.name = name;
     3                 this.colors = ["red","blue","green"];
     4             }
     5             SuperType.prototype.sayName = function(){
     6                 alert(this.name);
     7             };//添加新的方法
     8             //借用构造函数模式,用来实现对实例属性的继承
     9             function SubType(name,age){//继承SuperType
    10                 SuperType.call(this,name);
    11                 this.age = age;
    12             }
    13 
    14             SubType.prototype = new SuperType();
    15             SubType.prototype.constructor = SubType;
    16             //它们是原型链模式,不过多了一个实例指向。因为在经过借用构造函数继承后,它实际上指向了SuperType,需要再将它原型的实例指向自身。相当与绕了一圈
    17             //对继承后的原型进行添加方法,必须放在继承语句之后。
    18             SubType.prototype.sayAge = function(){
    19                 alert(this.age);
    20             };
    21 
    22             var instance1 = new SubType("Nicholas",29);
    23             instance1.colors.push("black");
    24             alert(instance1.colors);//red,blue,green,black
    25             instance1.sayName();//Nicholas
    26             instance1.sayAge();//29
    27 
    28             var instance2 = new SubType("Greg",27);
    29             alert(instance2.colors);//red,blue,green
    30             instance2.sayName();//Greg
    31             instance2.sayAge();//27
    32                         
    View Code

    组合继承的最大问题就是,要调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。

     

     组合继承需要在继承后,重新引用一下constructor, 因为在propotype继承时,它的constructor也跟着改变了;但是这样的情况下,虽然实现了对象继承,但是超类的原型对象都变成了子类的原型对象,最终导致无法去区分超类的原型对象了。

    组合继承优化版: 

     优化版1: 修改原型链继承方式,作为继承超类原型来用,完善借用构造函数继承无法继承到超类原型的缺点。

     1     // 组合继承优化1 
     2     function Person4() {
     3       this.name = 'person3';
     4     }
     5     function Child4() {
     6       this.type = 'child3';
     7       Person4.call(this); // 构造函数继承
     8     }
     9     // 将子类原型改成超类的原型
    10     Child4.prototype = Person4.prototype;
    11     var s5 = new Child4();
    12     console.log(s5 instanceof Child4, s5 instanceof Person4);
    13     console.log(s5.constructor); 

    现在引出了一个新的问题: 无法判断到底是 子类 还是 超类 进行的直接对象实例化。 这个问题会出现在 原型链继承、 组合继承 和 组合继承优化1 中。

    根据原型链的原理,不难看出这样的问题是因为: 子类原型指向了超类的原型,从而导致子类的constructor也成了超类的constructor;

    如果只是这样的话,是否重新对 子类进行constructor进行重新指向就好了呢?

    优化版2:修改子类的constructor指向

     利用Object.create() 去 创建中间对象从而将子类和超类区分开;

     

     1     // 组合继承优化2
     2     function Person5() {
     3       this.name = 'person3';
     4     }
     5     function Child5() {
     6       this.type = 'child3';
     7       Person5.call(this); // 构造函数继承
     8     }
     9     // 利用Object.create来作为中间链,将子类和超类区分开,且还保持这链接。
    10     Child5.prototype = Object.create(Person5.prototype);
    11     // 重新修改了constructor指向
    12     Child5.prototype.constructor = Child5;
    13     var s7 = new Child5();
    14     console.log(s7 instanceof Child5, s7 instanceof Person5);
    15     console.log(s7.constructor);

    此继承已经是非常好的继承了,下面的继承可以不看了。

    原型式继承

    Object.create方法规范化了原型式继承;这个方法接受两个参数,一个用作新对象原型的对象和(可选)一个为新对象定义额外属性的对象。 即将 新对象 链到 新对象原型的对象的原型链上,以解决上面 组合继承 导致的

     constructor改变问题。

     1            //创建一个对象,并将其传送到Object()函数中,然后函数就会返回一个新对象。
     2              var Person = {
     3                 name : "Nicholas",
     4                 friends : ["Shelby","Court","Van"]
     5             };
     6             //这个新对象以Peron为原型,所以他的原型中就包含一个基本类型值属性和一个引用类值属性。这意味着Person.friends不久属于Person,而且也被person1和person2所共享。
     7 
     8             var person1 = Object.create(Person);
     9             //原型继承这种方式,就是要求有一个对象能作为另一个对象的基础,从而进行修改。
    10             person1.name = "Greg";//定义的新name属性值,会“屏蔽”原本的name;
    11             person1.friends.push("Rob");
    12 
    13             var person2 = Object.create(Person);
    14             person2.name = "Linda";//定义的新name属性值,会“屏蔽”原本的name;
    15             person2.friends.push("Barbie");
    16 
    17             alert(Person.friends);//Shelby,Court,Van,Rob,Barbie
    18             alert(person1.friends);//Shelby,Court,Van,Rob,Barbie
    19             alert(person2.friends);//Shelby,Court,Van,Rob,Barbie
    20             //直接应用了friend的内容,从而导致相互影响。
    21             alert(person1.name);//Greg
    22             alert(person2.name);//Linda
    23             //因为定义了一个name,在查找name时,会先在实例中查找名为name的属性,从而导致了原型中的name被屏蔽。    
    View Code

    这里也引出了Object.create中继承的参数的内容并不是直接放到子类中,而是存在与子类的原型中; 所以会出现覆盖和共享属性的特征

    寄生式继承: 

    寄生式继承是与原型式继承紧密相关的一种思路; 即创建一个仅用域封装过程的函数, 该函数在内部以某种方式来增强对象, 最后就像真的是它做了所有工作一样返回对象;

     1             function createAnother(original){ //很像工厂模式
     2                 var clone = Object.create(original);//通过调用函数创建一个新对象
     3                 clone.sayHi = function(){  //通过某种方法来增强这个对象
     4                     alert("hi");
     5                 };
     6                 return clone;               //返回该对象
     7             }
     8             var person = {
     9                 name : "Nichloas",
    10                 friends : ["Sheldy","Court", "Van"]
    11             };
    12             var anotherperson = createAnother(person);
    13             person.friends.push("Linda");
    14             alert(anotherperson.friends);//Sheldy,Court,Van,Linda
    15             anotherperson.sayHi();       //hi
    View Code

    任何能够返回新对象的函数都适用于此模式;

    问题: 使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率;这一点与构造函数类似。

     

    寄生组合式继承

    本质上就是使用寄生式继承来继承超类的原型. 然后再将结果指定给子类的原型;

     1         function inheritPrototype(subtype,supertype){
     2             var midlle = Object.create(supertype.prototype);//创建对象
     3             midlle.constructor = subtype;//增强对象
     4             //为创建的副本添加constructor属性,从而弥补重写原型而失去的默认construcor属性。
     5             subtype.prototype = midlle;//指定对象。
     6             //将新创建的对象(即副本)赋值给子类型的原型。
     7         }
     8 
     9         function SuperType(name){
    10             this.name = name;
    11             this.colors = ["red","blue","green"];
    12         }
    13 
    14         SuperType.prototype.sayName = function(){
    15             alert(this.name);
    16         };
    17 
    18         //使用借用构造函数,来继承属性
    19         function SubType(name,age){
    20             SuperType.call(this,name);
    21             this.age = age;
    22         }
    23 
    24         //调用原型链的混成形式,来完成继承。
    25         inheritPrototype(SubType,SuperType);
    26 
    27         //在完成继承后的原型中添加新方法,不影响SuperType中的属性
    28         SubType.prototype.sayAge = function(){
    29             alert(this.age);
    30         }
    31 
    32         var instance1 = new SubType("Nicholas",29);
    33         instance1.colors.push("black");
    34         
    35         var instance2 = new SubType("Greg",27);
    36 
    37         instance1.sayName();//Nicholas
    38         instance1.sayAge();//29
    39         alert(instance1.colors);//red,blue,green,black
    40         instance2.sayName();//Greg
    41         instance2.sayAge();//27
    42         alert(instance2.colors);//red,blue,green
    View Code

    以上的ES5继承很全了。 面试时,主要说前四种即可;不要直接写出最佳答案,最好是由浅到深的让面试官了解你的基础知识是扎实的。

    面试最重要的就是: 要让面试官看到你的知识深度  和 对新技术的掌握

    ES6的类继承, 都封装好了; extends 所以不会作为重点去问

  • 相关阅读:
    查前端资料的一些网站
    10.18号笔记
    10.17号笔记
    10.16号笔记
    10.13号笔记
    10.12号笔记
    10.11号笔记
    10.10号笔记
    10.9号笔记
    理想VS现实
  • 原文地址:https://www.cnblogs.com/xuhua123/p/12044440.html
Copyright © 2011-2022 走看看